在 F# 中模拟多态变体?
Simulate polymorphic variants in F#?
我是 F# 的新手,所以如果这是一个愚蠢的问题或者语法可能有点不对,请提前原谅我。希望无论如何都能理解问题的要点。
我想要实现的是能够作曲,例如Result
(或 Either
或类似的东西)具有不同的错误类型(可区分的联合)without 创建 explicit discriminated union 包括其他两个discriminated unions 的联合。
让我举个例子。
假设我有一个类型 Person
定义如下:
type Person =
{ Name: string
Email: string }
假设您有一个验证名称的函数:
type NameValidationError =
| NameTooLong
| NameTooShort
let validateName person : Result<Person, NameValidationError>
和另一个验证电子邮件地址的:
type EmailValidationError =
| EmailTooLong
| EmailTooShort
let validateEmail person : Result<Person, EmailValidationError>
现在想合成validateName
和validateEmail
,但问题是Result
中的错误类型不同。我想要实现的是一个函数(或运算符),它允许我做这样的事情:
let validatedPerson = person |> validateName |>>> validateEmail
(|>>>
是 "magic operator")
通过使用 |>>>
,validatedPerson
的错误类型将是 NameValidationError
和 EmailValidationError
:
的并集
Result<Person, NameValidationError | EmailValidationError>
为了清楚起见,应该可以在组合链中使用任意数量的函数,即:
let validatedPerson : Result<Person, NameValidationError | EmailValidationError | XValidationError | YValidationError> =
person |> validateName |>>> validateEmail |>>> validateX |>>> validateY
在像 ReasonML you can use something called polymorphic variants but this is not available in F# 这样的语言中。
是否有可能以某种方式使用具有联合类型的泛型(或任何其他技术)来模仿多态变体?!或者这是不可能的?
有一些有趣的东西 proposals for erased type unions,允许 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)
给你一个 NameValidationError | EmailValidationError
的神奇运算符将使其类型仅在编译时存在。它会在运行时被擦除为 object
。
但它还在铁砧上,所以也许我们可以通过自己擦除来获得一些可读的代码?
组合运算符可以'erase'(框,真的)结果错误类型:
let (|>>) input validate =
match input with
| Ok(v) -> validate v |> Result.mapError(box)
| Error(e) -> Error(box e)
我们可以有一个部分活跃的模式来使类型匹配的 DU 案例变得可口。
let (|ValidationError|_|) kind = function
| Error(err) when Object.Equals(kind, err) -> Some ()
| _ -> None
示例(具有超偏验证):
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"
| _ -> ()
这将在 validateVibe
上分流
这可能比您希望的更冗长,但它确实允许您将内容放入 DU 而无需明确定义它。
F# 具有 Choice
类型,其定义如下:
type Choice<'T1,'T2> =
| Choice1Of2 of 'T1
| Choice2Of2 of 'T2
type Choice<'T1,'T2,'T3> =
| Choice1Of3 of 'T1
| Choice2Of3 of 'T2
| Choice3Of3 of 'T3
// Going up to ChoiceXOf7
对于您现有的函数,您可以像这样使用它们:
// This function returns Result<Person,Choice<NameValidationError,EmailValidationError>>
let validatePerson person =
validateName person
|> Result.mapError Choice1Of2
|> Result.bind (validateEmail >> Result.mapError Choice2Of2)
这是您使用结果的方式:
let displayValidationError person =
match person with
| Ok p -> None
| Error (Choice1Of2 NameTooLong) -> Some "Name too long"
| Error (Choice2Of2 EmailTooLong) -> Some "Email too long"
// etc.
如果你想在 validatePerson
中添加第三个验证,你需要切换到 Choice<_,_,_>
DU 案例,例如Choice1Of3
等等。
我是 F# 的新手,所以如果这是一个愚蠢的问题或者语法可能有点不对,请提前原谅我。希望无论如何都能理解问题的要点。
我想要实现的是能够作曲,例如Result
(或 Either
或类似的东西)具有不同的错误类型(可区分的联合)without 创建 explicit discriminated union 包括其他两个discriminated unions 的联合。
让我举个例子。
假设我有一个类型 Person
定义如下:
type Person =
{ Name: string
Email: string }
假设您有一个验证名称的函数:
type NameValidationError =
| NameTooLong
| NameTooShort
let validateName person : Result<Person, NameValidationError>
和另一个验证电子邮件地址的:
type EmailValidationError =
| EmailTooLong
| EmailTooShort
let validateEmail person : Result<Person, EmailValidationError>
现在想合成validateName
和validateEmail
,但问题是Result
中的错误类型不同。我想要实现的是一个函数(或运算符),它允许我做这样的事情:
let validatedPerson = person |> validateName |>>> validateEmail
(|>>>
是 "magic operator")
通过使用 |>>>
,validatedPerson
的错误类型将是 NameValidationError
和 EmailValidationError
:
Result<Person, NameValidationError | EmailValidationError>
为了清楚起见,应该可以在组合链中使用任意数量的函数,即:
let validatedPerson : Result<Person, NameValidationError | EmailValidationError | XValidationError | YValidationError> =
person |> validateName |>>> validateEmail |>>> validateX |>>> validateY
在像 ReasonML you can use something called polymorphic variants but this is not available in F# 这样的语言中。
是否有可能以某种方式使用具有联合类型的泛型(或任何其他技术)来模仿多态变体?!或者这是不可能的?
有一些有趣的东西 proposals for erased type unions,允许 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)
给你一个 NameValidationError | EmailValidationError
的神奇运算符将使其类型仅在编译时存在。它会在运行时被擦除为 object
。
但它还在铁砧上,所以也许我们可以通过自己擦除来获得一些可读的代码?
组合运算符可以'erase'(框,真的)结果错误类型:
let (|>>) input validate =
match input with
| Ok(v) -> validate v |> Result.mapError(box)
| Error(e) -> Error(box e)
我们可以有一个部分活跃的模式来使类型匹配的 DU 案例变得可口。
let (|ValidationError|_|) kind = function
| Error(err) when Object.Equals(kind, err) -> Some ()
| _ -> None
示例(具有超偏验证):
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"
| _ -> ()
这将在 validateVibe
这可能比您希望的更冗长,但它确实允许您将内容放入 DU 而无需明确定义它。
F# 具有 Choice
类型,其定义如下:
type Choice<'T1,'T2> =
| Choice1Of2 of 'T1
| Choice2Of2 of 'T2
type Choice<'T1,'T2,'T3> =
| Choice1Of3 of 'T1
| Choice2Of3 of 'T2
| Choice3Of3 of 'T3
// Going up to ChoiceXOf7
对于您现有的函数,您可以像这样使用它们:
// This function returns Result<Person,Choice<NameValidationError,EmailValidationError>>
let validatePerson person =
validateName person
|> Result.mapError Choice1Of2
|> Result.bind (validateEmail >> Result.mapError Choice2Of2)
这是您使用结果的方式:
let displayValidationError person =
match person with
| Ok p -> None
| Error (Choice1Of2 NameTooLong) -> Some "Name too long"
| Error (Choice2Of2 EmailTooLong) -> Some "Email too long"
// etc.
如果你想在 validatePerson
中添加第三个验证,你需要切换到 Choice<_,_,_>
DU 案例,例如Choice1Of3
等等。