Joh*_*han 7 f# functional-programming polymorphic-variants
我是 F# 新手,所以如果这是一个愚蠢的问题或者语法可能有点不对,请提前原谅我。希望无论如何都可以理解问题的要点。
我想要实现的是组合 eg Result's (或 anEither或类似的东西)具有不同错误类型(受歧视联合)的可能性,而无需创建包括其他两个受歧视联合的并在内的明确受歧视联合。
让我举一个例子。
假设我有一个这样Person定义的类型:
type Person =
{ Name: string
Email: string }
Run Code Online (Sandbox Code Playgroud)
假设您有一个验证名称的函数:
type NameValidationError =
| NameTooLong
| NameTooShort
let validateName person : Result<Person, NameValidationError>
Run Code Online (Sandbox Code Playgroud)
另一个验证电子邮件地址:
type EmailValidationError =
| EmailTooLong
| EmailTooShort
let validateEmail person : Result<Person, EmailValidationError>
Run Code Online (Sandbox Code Playgroud)
现在我想编写validateNameand validateEmail,但问题是 中的错误类型Result有不同的类型。我想要实现的是一个函数(或运算符),它允许我做这样的事情:
let validatedPerson = person |> validateName |>>> validateEmail
Run Code Online (Sandbox Code Playgroud)
(|>>>是“魔术运算符”)
通过使用|>>>错误类型validatedPerson将是NameValidationError和的并集EmailValidationError:
Result<Person, NameValidationError | EmailValidationError>
Run Code Online (Sandbox Code Playgroud)
为了清楚起见,应该可以在组合链中使用任意数量的函数,即:
Result<Person, NameValidationError | EmailValidationError>
Run Code Online (Sandbox Code Playgroud)
在ReasonML 之类的语言中,您可以使用称为多态变体的东西,但这在 F# 中作为 afaict 不可用。
是否有可能使用具有联合类型(或任何其他技术)的泛型以某种方式模拟多态变体?!或者这是不可能的?
对于擦除类型联合有一些有趣的建议,允许 Typescript 风格的匿名联合约束。
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)
Run Code Online (Sandbox Code Playgroud)
为您提供 a 的魔术运算符的NameValidationError | EmailValidationError类型仅在编译时存在。它会在运行时被擦除object。
但它仍然在铁砧上,所以也许我们仍然可以通过自己擦除来获得一些可读的代码?
组合运算符可以“擦除”(实际上是框)结果错误类型:
let (|>>) input validate =
match input with
| Ok(v) -> validate v |> Result.mapError(box)
| Error(e) -> Error(box e)
Run Code Online (Sandbox Code Playgroud)
我们可以有一个部分活动模式来使类型匹配的 DU 案例变得容易接受。
let (|ValidationError|_|) kind = function
| Error(err) when Object.Equals(kind, err) -> Some ()
| _ -> None
Run Code Online (Sandbox Code Playgroud)
示例(具有超级偏差验证):
let person = { Name = "Bob"; Email = "bob@email.com "}
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"
| _ -> ()
Run Code Online (Sandbox Code Playgroud)
这将分流validateVibe