Tipo de opción - Option type

En los lenguajes de programación (especialmente los lenguajes de programación funcional ) y la teoría de tipos , un tipo de opción o tal vez tipo es un tipo polimórfico que representa la encapsulación de un valor opcional; por ejemplo, se utiliza como el tipo de retorno de funciones que pueden o no devolver un valor significativo cuando se aplican. Consiste en un constructor que está vacío (a menudo llamado None o Nothing ), o que encapsula el tipo de datos original A (a menudo escrito Just A o Some A ).

Un concepto distinto, pero relacionado fuera de la programación funcional, que es popular en la programación orientada a objetos , se denomina tipos que aceptan valores NULL (a menudo expresados ​​como A? ). La principal diferencia entre los tipos de opciones y los tipos que aceptan valores NULL es que los tipos de opciones admiten la anidación ( Maybe (Maybe A)Maybe A ), mientras que los tipos que aceptan valores NULL no ( String?? = String? ).

Aspectos teóricos

En la teoría de tipos , puede ser escrito como: . Esto expresa el hecho de que para un conjunto dado de valores en , un tipo de opción agrega exactamente un valor adicional (el valor vacío) al conjunto de valores válidos para . Esto se refleja en la programación por el hecho de que en los lenguajes que tienen uniones etiquetadas , los tipos de opciones se pueden expresar como la unión etiquetada del tipo encapsulado más un tipo de unidad .

En la correspondencia Curry-Howard , los tipos de opciones están relacionados con la ley de aniquilación para ∨: x∨1 = 1.

Un tipo de opción también puede verse como una colección que contiene uno o cero elementos.

El tipo de opción también es una mónada donde:

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

La naturaleza monádica del tipo de opción es útil para rastrear de manera eficiente fallas y errores.

Nombres y definiciones

En diferentes lenguajes de programación, el tipo de opción tiene varios nombres y definiciones.

  • En Agda , se nombra Maybe con variantes nothing y just a .
  • En C ++ 17 se define como la clase de plantilla , se puede usar para crear una opción vacía. ( Podría romper las leyes de las mónadas debido a la gran sobrecarga de los constructores ) .std::optional<T>optional()
  • En C # , se define como, pero generalmente se escribe como . ( Rompe las leyes de las mónadas ) .Nullable<T>T?
  • En Coq , se define como . Inductive option (A:Type) : Type := | Some : A -> option A | None : option A.
  • En Elm , se nombra Maybe y se define como . type Maybe a = Just a | Nothing
  • En Haskell , se nombra Maybe y se define como . data Maybe a = Nothing | Just a
  • En Idris , se define como . data Maybe a = Nothing | Just a
  • En Java , desde la versión 8, se define como clase final parametrizada . ( Rompe las leyes de las mónadas (el mapa está implementado incorrectamente). ) Optional<T>
  • En Julia , se llama . ( Sin embargo, esto ha quedado obsoleto ) .Nullable{T}
  • En OCaml , se define como . type 'a option = None | Some of 'a
  • En Perl 6 , este es el valor predeterminado, pero puede agregar un "emoticón" para optar por un tipo sin opción. ( Rompe las leyes de las mónadas (no admite el anidamiento )) :D
  • En Rust , se define como . enum Option<T> { None, Some(T) }
  • En Scala , se define como un tipo extendido por y . sealed abstract class Option[+A]final case class Some[+A](value: A)case object None
  • En ML estándar , se define como . datatype 'a option = NONE | SOME of 'a
  • En Swift , se define como, pero generalmente se escribe como . enum Optional<T> { case none, some(T) }T?

Ejemplos

Ada

Ada no implementa tipos de opción directamente, sin embargo, proporciona tipos discriminados que se pueden usar para parametrizar un registro. Para implementar un tipo de Opción, se utiliza un tipo booleano como discriminante; el siguiente ejemplo proporciona un genérico para crear un tipo de opción a partir de cualquier tipo restringido no limitado:

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 se implementa Option como un tipo parametrizado, por lo que una variable puede ser una Option , a la que se accede de la siguiente manera:

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)}")
  }
}

Existen dos formas principales de utilizar un Option valor. La primera, no la mejor, es la coincidencia de patrones , como en el primer ejemplo. La segunda, la mejor práctica es un enfoque monádico, como en el segundo ejemplo. De esta forma, un programa es seguro, ya que no puede generar ninguna excepción o error (por ejemplo, al intentar obtener el valor de una Option variable que sea igual a None ). Por lo tanto, esencialmente funciona como una alternativa de tipo seguro al valor nulo.

OCaml

OCaml se implementa Option como un tipo de variante parametrizado. Option s se construyen y deconstruyen de la siguiente manera:

(* 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)

F#

// 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

Rápido

// 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))")

Oxido

// 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)

Ver también

Referencias