首页 > 解决方案 > 在 F# 中模拟多态变体?

问题描述

我是 F# 的新手,所以如果这是一个愚蠢的问题或者语法可能有点不对,请提前原谅我。希望无论如何都能理解问题的要点。

我想要实现的是组合 egResult的(或Either类似的)具有不同错误类型(可区分联合)的可能性,而无需创建包含其他两个可区分联合的联合的显式可区分联合。

让我举一个例子。

假设我有一个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>

现在我想编写validateNameand validateEmail,但问题是 中的错误类型Result有不同的类型。我想要实现的是一个函数(或运算符),它允许我做这样的事情:

let validatedPerson = person |> validateName |>>> validateEmail

|>>>是“魔术运算符”)

通过使用|>>>错误类型validatedPerson将是NameValidationError和的联合EmailValidationError

Result<Person, NameValidationError | EmailValidationError>

为了清楚起见,应该可以在组合链中使用任意数量的函数,即:

let validatedPerson : Result<Person, NameValidationError | EmailValidationError | XValidationError | YValidationError> = 
       person |> validateName |>>> validateEmail |>>> validateX |>>> validateY

在像ReasonML这样的语言中,您可以使用称为多态变体的东西,但这在 F# 中不可用

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

标签: functional-programmingf#polymorphic-variants

解决方案


这可能比您想要的更冗长,但它确实允许您将内容放入 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等等。


推荐阅读