Soy nuevo en F # así que perdónenme con antelación si se trata de una pregunta estúpida o si la sintaxis puede ser un poco apagado. Esperemos que sea posible entender la esencia de la cuestión de todos modos.
Lo que me gustaría lograr es la posibilidad de componer por ejemplo, Result
's (o un Either
o algo similar) que tienen diferentes tipos de error (uniones discriminadas) sin la creación de una explícita unión discriminada que incluye la unión de los otros dos sindicatos discriminados.
Permítanme presentar un ejemplo.
Digamos que tengo un tipo Person
definido de esta manera:
type Person =
{ Name: string
Email: string }
Imagine que tiene una función que valida el nombre de:
type NameValidationError =
| NameTooLong
| NameTooShort
let validateName person : Result<Person, NameValidationError>
y otro que valida una dirección de correo electrónico:
type EmailValidationError =
| EmailTooLong
| EmailTooShort
let validateEmail person : Result<Person, EmailValidationError>
Ahora quiero componer validateName
y validateEmail
, pero el problema es que el tipo de error en la Result
cuenta con diferentes tipos. Lo que me gustaría conseguir es una función (o el operador) que me permite hacer algo como esto:
let validatedPerson = person |> validateName |>>> validateEmail
( |>>>
Es el "operador mágica")
Al utilizar |>>>
el tipo de error de validatedPerson
que sería una unión de NameValidationError
y EmailValidationError
:
Result<Person, NameValidationError | EmailValidationError>
Sólo para que quede claro, debería ser posible para un número arbitrario uso de las funciones de la cadena de composición, es decir:
let validatedPerson : Result<Person, NameValidationError | EmailValidationError | XValidationError | YValidationError> =
person |> validateName |>>> validateEmail |>>> validateX |>>> validateY
En lenguajes como ReasonML se puede usar algo llamado variantes polimórficas , pero esto es no disponible en C # como afaict.
¿Sería posible que las variantes polimórficas de alguna manera imitan utilizando los genéricos con tipos de unión (o cualquier otra técnica) ?! O es imposible?
Hay algunas interesantes propuestas de uniones de tipo borrados , lo que permite limitaciones unión anónima de tipo de imprenta.
type Goose = Goose of int
type Cardinal = Cardinal of int
type Mallard = Mallard of int
// a type abbreviation for an erased anonymous union
type Bird = (Goose | Cardinal | Mallard)
El operador de la magia que le daría una NameValidationError | EmailValidationError
tendría su tipo sólo existen en tiempo de compilación. Que sería borrado de object
en tiempo de ejecución.
Pero sigue siendo en el yunque, así que tal vez todavía podemos tener algo de código legible por hacer el borrado de nosotros mismos?
El operador de composición podría 'borrado' (caja, en realidad) el tipo de error de resultado:
let (|>>) input validate =
match input with
| Ok(v) -> validate v |> Result.mapError(box)
| Error(e) -> Error(box e)
y podemos tener un patrón activo parcial para hacer casos du type-juego agradable al paladar.
let (|ValidationError|_|) kind = function
| Error(err) when Object.Equals(kind, err) -> Some ()
| _ -> None
Ejemplo (con validaciones súper sesgada):
let person = { Name = "Bob"; Email = "[email protected] "}
let validateName person = Result.Ok(person)
let validateEmail person = Result.Ok(person)
let validateVibe person = Result.Error(NameTooShort)
let result = person |> validateName |>> validateVibe |>> validateEmail
match result with
| ValidationError NameTooShort -> printfn "Why is your name too short"
| ValidationError EmailTooLong -> printfn "That was a long address"
| _ -> ()
Esto derivará en validateVibe