Rodzaj opcji - Option type
W językach programowania (zwłaszcza funkcyjnych językach programowania ) i teorii typów , typ opcji lub może typ jest typem polimorficznym, który reprezentuje hermetyzację wartości opcjonalnej; np. jest używany jako typ zwracania funkcji, które mogą zwracać znaczącą wartość lub nie, gdy są stosowane. Składa się z konstruktora, który jest pusty (często nazywany None
lub Nothing
) lub który zawiera oryginalny typ danych A
(często zapisywany Just A
lub Some A
).
Odrębna, ale pokrewna koncepcja poza programowaniem funkcjonalnym, która jest popularna w programowaniu obiektowym , nazywana jest typami dopuszczającymi wartość null (często wyrażanymi jako A?
). Podstawowa różnica między typami opcji i typami dopuszczającymi wartość null polega na tym, że typy opcji obsługują zagnieżdżanie ( Maybe (Maybe A)
≠ Maybe A
), podczas gdy typy dopuszczające wartość null nie obsługują ( String??
= String?
).
Aspekty teoretyczne
W teorii typów , może być zapisany jako: . Wyraża to fakt, że dla danego zestawu wartości w typ opcji dodaje dokładnie jedną dodatkową wartość (pustą wartość) do zestawu prawidłowych wartości dla . Znajduje to odzwierciedlenie w programowaniu poprzez fakt, że w językach, które mają oznaczone związki , typy opcji można wyrazić jako oznaczoną sumę typu hermetyzowanego plus typ jednostki .
W korespondencji Curry-Howard typy opcji są powiązane z prawem anihilacji dla ∨: x∨1 = 1.
Typ opcji można również postrzegać jako kolekcję zawierającą jeden lub zero elementów.
Typ opcji to także monada, gdzie:
return = Just -- Wraps the value into a maybe
Nothing >>= f = Nothing -- Fails if the previous monad fails
(Just x) >>= f = f x -- Succeeds when both monads succeed
Monadyczny charakter typu opcji jest przydatny do efektywnego śledzenia awarii i błędów.
Nazwy i definicje
W różnych językach programowania typ opcji ma różne nazwy i definicje.
- W Agdzie nazywa
Maybe
się to wariantaminothing
ijust a
. - W C ++ 17 jest zdefiniowana jako klasa szablonu , może służyć do tworzenia pustej opcji. ( Może złamać prawa monady z powodu dużego przeciążenia konstruktorów. )
std::optional<T>
optional()
- W C # jest zdefiniowany jako, ale ogólnie jest zapisywany jako . ( Łamie prawa monady. )
Nullable<T>
T?
- W Coq jest zdefiniowany jako .
Inductive option (A:Type) : Type := | Some : A -> option A | None : option A.
- W Elm ma nazwę
Maybe
i jest zdefiniowany jako .type Maybe a = Just a | Nothing
- W Haskell nazywa się to
Maybe
i definiuje jako .data Maybe a = Nothing | Just a
- W Idrisie jest zdefiniowany jako .
data Maybe a = Nothing | Just a
- W Javie od wersji 8 jest definiowana jako sparametryzowana klasa końcowa . ( Łamie prawa monady (mapa jest zaimplementowana nieprawidłowo). )
Optional<T>
- W Julii to się nazywa . ( Jednak ta opcja została wycofana ).
Nullable{T}
- W OCaml jest zdefiniowany jako .
type 'a option = None | Some of 'a
- W Perlu 6 jest to ustawienie domyślne, ale możesz dodać „buźkę”, aby wybrać typ bez opcji. ( Łamie prawa monady (nie obsługuje zagnieżdżania. ))
:D
- W Rust jest definiowany jako .
enum Option<T> { None, Some(T) }
- W Scali jest definiowany jako typ rozszerzony o i .
sealed abstract class Option[+A]
final case class Some[+A](value: A)
case object None
- W Standard ML jest zdefiniowany jako .
datatype 'a option = NONE | SOME of 'a
- W języku Swift jest definiowany jako, ale ogólnie jest zapisywany jako .
enum Optional<T> { case none, some(T) }
T?
Przykłady
Ada
Ada nie implementuje bezpośrednio typów opcji, jednak zapewnia typy rozróżniane, których można użyć do parametryzacji rekordu. Aby zaimplementować typ Option, jako dyskryminator używany jest typ logiczny; Poniższy przykład przedstawia ogólny sposób tworzenia typu opcji z dowolnego nieograniczonego typu ograniczonego:
Generic
-- Any constrained & non-limited type.
Type Element_Type is private;
Package Optional_Type is
-- When the discriminant, Has_Element, is true there is an element field,
-- when it is false, there are no fields (hence the null keyword).
Type Optional( Has_Element : Boolean ) is record
case Has_Element is
when False => Null;
when True => Element : Element_Type;
end case;
end record;
end Optional_Type;
Scala
Scala implementuje się Option
jako typ sparametryzowany, więc zmienna może być zmienną Option
dostępną w następujący sposób:
object Main {
// This function uses pattern matching to deconstruct `Option`s
def computeV1(opt: Option[Int]): String =
opt match {
case Some(x) => s"The value is: $x"
case None => "No value"
}
// This function uses the built-in `fold` method
def computeV2(opt: Option[Int]): String =
opt.fold("No value")(x => s"The value is: $x")
def main(args: Array[String]): Unit = {
// Define variables that are `Option`s of type `Int`
val full = Some(42)
val empty: Option[Int] = None
// computeV1(full) -> The value is: 42
println(s"computeV1(full) -> ${computeV1(full)}")
// computeV1(empty) -> No value
println(s"computeV1(empty) -> ${computeV1(empty)}")
// computeV2(full) -> The value is: 42
println(s"computeV2(full) -> ${computeV2(full)}")
// computeV2(empty) -> No value
println(s"computeV2(empty) -> ${computeV2(empty)}")
}
}
Istnieją dwa główne sposoby używania Option
wartości. Pierwszym, nie najlepszym, jest dopasowanie wzorców , tak jak w pierwszym przykładzie. Po drugie, najlepszą praktyką jest podejście monadyczne, tak jak w drugim przykładzie. W ten sposób program jest bezpieczny, ponieważ nie może generować wyjątku ani błędu (np. Próbując uzyskać wartość Option
zmiennej, która jest równa None
). W związku z tym zasadniczo działa jako bezpieczna dla typu alternatywa dla wartości null.
OCaml
OCaml implementuje Option
jako sparametryzowany typ wariantu. Option
są konstruowane i dekonstruowane w następujący sposób:
(* This function uses pattern matching to deconstruct `option`s *)
let compute_v1 = function
| Some x -> "The value is: " ^ string_of_int x
| None -> "No value"
(* This function uses the built-in `fold` function *)
let compute_v2 =
Option.fold ~none:"No value" ~some:(fun x -> "The value is: " ^ string_of_int x)
let () =
(* Define variables that are `option`s of type `int` *)
let full = Some 42 in
let empty = None in
(* compute_v1 full -> The value is: 42 *)
print_endline ("compute_v1 full -> " ^ compute_v1 full);
(* compute_v1 empty -> No value *)
print_endline ("compute_v1 empty -> " ^ compute_v1 empty);
(* compute_v2 full -> The value is: 42 *)
print_endline ("compute_v2 full -> " ^ compute_v2 full);
(* compute_v2 empty -> No value *)
print_endline ("compute_v2 empty -> " ^ compute_v2 empty)
FA#
// This function uses pattern matching to deconstruct `option`s
let compute_v1 = function
| Some x -> sprintf "The value is: %d" x
| None -> "No value"
// This function uses the built-in `fold` function
let compute_v2 =
Option.fold (fun _ x -> sprintf "The value is: %d" x) "No value"
// Define variables that are `option`s of type `int`
let full = Some 42
let empty = None
// compute_v1 full -> The value is: 42
compute_v1 full |> printfn "compute_v1 full -> %s"
// compute_v1 empty -> No value
compute_v1 empty |> printfn "compute_v1 empty -> %s"
// compute_v2 full -> The value is: 42
compute_v2 full |> printfn "compute_v2 full -> %s"
// compute_v2 empty -> No value
compute_v2 empty |> printfn "compute_v2 empty -> %s"
Haskell
-- This function uses pattern matching to deconstruct `Maybe`s
computeV1 :: Maybe Int -> String
computeV1 (Just x) = "The value is: " ++ show x
computeV1 Nothing = "No value"
-- This function uses the built-in `foldl` function
computeV2 :: Maybe Int -> String
computeV2 = foldl (\_ x -> "The value is: " ++ show x) "No value"
main :: IO ()
main = do
-- Define variables that are `Maybe`s of type `Int`
let full = Just 42
let empty = Nothing
-- computeV1 full -> The value is: 42
putStrLn $ "computeV1 full -> " ++ computeV1 full
-- computeV1 full -> No value
putStrLn $ "computeV1 empty -> " ++ computeV1 empty
-- computeV2 full -> The value is: 42
putStrLn $ "computeV2 full -> " ++ computeV2 full
-- computeV2 full -> No value
putStrLn $ "computeV2 empty -> " ++ computeV2 empty
Szybki
// This function uses a `switch` statement to deconstruct `Optional`s
func computeV1(_ opt: Int?) -> String {
switch opt {
case .some(let x):
return "The value is: \(x)"
case .none:
return "No value"
}
}
// This function uses optional binding to deconstruct `Optional`s
func computeV2(_ opt: Int?) -> String {
if let x = opt {
return "The value is: \(x)"
} else {
return "No value"
}
}
// Define variables that are `Optional`s of type `Int`
let full: Int? = 42
let empty: Int? = nil
// computeV1(full) -> The value is: 42
print("computeV1(full) -> \(computeV1(full))")
// computeV1(empty) -> No value
print("computeV1(empty) -> \(computeV1(empty))")
// computeV2(full) -> The value is: 42
print("computeV2(full) -> \(computeV2(full))")
// computeV2(empty) -> No value
print("computeV2(empty) -> \(computeV2(empty))")
Rdza
// This function uses a `match` expression to deconstruct `Option`s
fn compute_v1(opt: &Option<i32>) -> String {
match opt {
Some(x) => format!("The value is: {}", x),
None => "No value".to_owned(),
}
}
// This function uses an `if let` expression to deconstruct `Option`s
fn compute_v2(opt: &Option<i32>) -> String {
if let Some(x) = opt {
format!("The value is: {}", x)
} else {
"No value".to_owned()
}
}
// This function uses the built-in `map_or` method
fn compute_v3(opt: &Option<i32>) -> String {
opt.map_or("No value".to_owned(), |x| format!("The value is: {}", x))
}
fn main() {
// Define variables that are `Option`s of type `i32`
let full = Some(42);
let empty: Option<i32> = None;
// compute_v1(&full) -> The value is: 42
println!("compute_v1(&full) -> {}", compute_v1(&full));
// compute_v1(&empty) -> No value
println!("compute_v1(&empty) -> {}", compute_v1(&empty));
// compute_v2(&full) -> The value is: 42
println!("compute_v2(&full) -> {}", compute_v2(&full));
// compute_v2(&empty) -> No value
println!("compute_v2(&empty) -> {}", compute_v2(&empty));
// compute_v3(&full) -> The value is: 42
println!("compute_v3(&full) -> {}", compute_v3(&full));
// compute_v3(&empty) -> No value
println!("compute_v3(&empty) -> {}", compute_v3(&empty))
}
Nim
import options
# This proc uses the built-in `isSome` and `get` procs to deconstruct `Option`s
proc compute(opt: Option[int]): string =
if opt.isSome:
"The Value is: " & $opt.get
else:
"No value"
# Define variables that are `Optional`s of type `Int`
let
full = some(42)
empty = none(int)
# compute(full) -> The Value is: 42
echo "compute(full) -> ", compute(full)
# compute(empty) -> No value
echo "compute(empty) -> ", compute(empty)