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 wariantami nothing i just 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)

Zobacz też

Bibliografia