在 F# 中模拟多态变体?

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 不可用。

是否有可能使用具有联合类型(或任何其他技术)的泛型以某种方式模拟多态变体?!或者这是不可能的?

Ast*_*sti 3

对于擦除类型联合有一些有趣的建议,允许 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