F#
Tipos de opciones
Buscar..
Definición de Opción
Una Option
es una unión discriminada con dos casos, None
o Some
.
type Option<'T> = Some of 'T | None
Utilice la opción <'T> sobre valores nulos
En los lenguajes de programación funcional, como F#
null
valores null
se consideran potencialmente dañinos y de estilo deficiente (no idiomático).
Considere este código C#
:
string x = SomeFunction ();
int l = x.Length;
x.Length
lanzará si x
es null
, agreguemos protección:
string x = SomeFunction ();
int l = x != null ? x.Length : 0;
O:
string x = SomeFunction () ?? "";
int l = x.Length;
O:
string x = SomeFunction ();
int l = x?.Length;
En null
valores null
F#
idiomáticos no se utilizan, por lo que nuestro código se ve así:
let x = SomeFunction ()
let l = x.Length
Sin embargo, a veces es necesario representar valores vacíos o no válidos. Entonces podemos usar la Option<'T>
:
let SomeFunction () : string option = ...
SomeFunction
devuelve Some
valor de string
o None
. Extraemos el valor de la string
utilizando el patrón de coincidencia
let v =
match SomeFunction () with
| Some x -> x.Length
| None -> 0
La razón por la cual este código es menos frágil que:
string x = SomeFunction ();
int l = x.Length;
Es porque no podemos llamar Length
en una string option
. Necesitamos extraer el valor de la string
utilizando la coincidencia de patrones y al hacerlo, tenemos la garantía de que el valor de la string
es seguro de usar.
El módulo opcional habilita la programación orientada al ferrocarril
El manejo de errores es importante pero puede convertir un algoritmo elegante en un desastre. La Programación Orientada a Ferrocarriles ( ROP
) se utiliza para hacer que el manejo de errores sea elegante y componible.
Considere la función simple f
:
let tryParse s =
let b, v = System.Int32.TryParse s
if b then Some v else None
let f (g : string option) : float option =
match g with
| None -> None
| Some s ->
match tryParse s with // Parses string to int
| None -> None
| Some v when v < 0 -> None // Checks that int is greater than 0
| Some v -> v |> float |> Some // Maps int to float
El propósito de f
es analizar el valor de la string
entrada (si hay Some
) en un int
. Si el int
es mayor que 0
lanzamos en un float
. En todos los demás casos, rescatamos con None
.
Aunque es una función extremadamente simple, la match
anidada disminuye significativamente la legibilidad.
ROP
observa que tenemos dos tipos de vías de ejecución en nuestro programa
- Camino feliz - eventualmente calcularemos
Some
valor - Ruta de error: todas las demás rutas producen
None
Como las rutas de error son más frecuentes, tienden a tomar el control del código. Nos gustaría que el código de ruta feliz sea la ruta de código más visible.
Una función equivalente g
usa ROP
podría verse así:
let g (v : string option) : float option =
v
|> Option.bind tryParse // Parses string to int
|> Option.filter ((<) 0) // Checks that int is greater than 0
|> Option.map float // Maps int to float
Se parece mucho a cómo tendemos a procesar listas y secuencias en F#
.
Uno puede ver una Option<'T>
como una List<'T>
que solo puede contener 0
o 1
elemento donde Option.bind
comporta como List.pick
(conceptualmente Option.bind
asigna mejor a List.collect
pero List.pick
podría ser Más fácil de entender).
bind
, filter
y map
manejan las rutas de error g
solo contienen el código de ruta feliz.
Todas las funciones que aceptan directamente la Option<_>
y devuelven la Option<_>
pueden componer directamente con |>
y >>
.
ROP
por lo tanto aumenta la legibilidad y la composibilidad.
Usando tipos de opciones de C #
No es una buena idea exponer los tipos de opción al código C #, ya que C # no tiene una forma de manejarlos. Las opciones son introducir FSharp.Core
como una dependencia en su proyecto de C # (que es lo que tendría que hacer si está consumiendo una biblioteca de F # no diseñada para interoperar con C #), o cambiar los valores de None
a null
.
Pre-F # 4.0
La forma de hacerlo es crear una función de conversión propia:
let OptionToObject opt =
match opt with
| Some x -> x
| None -> null
Para los tipos de valor tendría que recurrir a boxearlos o usar System.Nullable
.
let OptionToNullable x =
match x with
| Some i -> System.Nullable i
| None -> System.Nullable ()
F # 4.0
En F # 4.0, las funciones ofObj
, toObj
, ofNullable
y toNullable
introdujeron en el módulo de Option
. En F # interactivo se pueden utilizar de la siguiente manera:
let l1 = [ Some 1 ; None ; Some 2]
let l2 = l1 |> List.map Option.toNullable;;
// val l1 : int option list = [Some 1; null; Some 2]
// val l2 : System.Nullable<int> list = [1; null; 2]
let l3 = l2 |> List.map Option.ofNullable;;
// val l3 : int option list = [Some 1; null; Some 2]
// Equality
l1 = l2 // fails to compile: different types
l1 = l3 // true
Tenga en cuenta que None
compila a null
internamente. Sin embargo, en lo que respecta a F #, es un None
.
let lA = [Some "a"; None; Some "b"]
let lB = lA |> List.map Option.toObj
// val lA : string option list = [Some "a"; null; Some "b"]
// val lB : string list = ["a"; null; "b"]
let lC = lB |> List.map Option.ofObj
// val lC : string option list = [Some "a"; null; Some "b"]
// Equality
lA = lB // fails to compile: different types
lA = lC // true