在类中使用两种泛型类型键入约束不匹配,重载以使两种类型相等

时间:2018-01-11 21:58:57

标签: generics f#

我遇到泛型类型约束的问题。

背景:我刚开始使用WPF并正在创建一个类来帮助验证。目标是从XAML轻松使用以及从后端进行验证时的类型安全性。

为此,我创建了一个包装另一个值的类。它有一个我在XAML中绑定的属性Raw,以及一个属性Valid,它给我一个最终的,有效的和转换的结果。该类使用转换器和转换后的值检查列表进行实例化。当然,转换器以及检查都会失败。

我想创建两个重载:一个没有检查(用于创建仅转换的包装)和一个没有转换器(用于创建仅检查和不转换的包装器)。我遇到了后者。这是类(删除了INotifyPropertyChanged和IDataErrorInfo):

type Validated<'raw, 'converted>
      (init:'raw,
       convert:'raw->Result<'converted,string>,
       checks:('converted->Result<unit,string>) list) =

  let getValidationError converted validate =
    match validate converted with
    | Ok () -> None
    | Error err -> Some err

  new(init:'raw, convert:'raw->Result<'converted,string>) = 
    Validated(init, convert, [])

  // This is the one I'm having trouble with  
  new(init:'raw, checks:('converted->Result<unit,string>) list) = 
    Validated(init, Ok, checks)

  member val Raw = init with get, set

  member private this.Converted = convert this.Raw

  member this.Errors =
      match this.Converted with
      | Ok x -> checks |> List.choose (getValidationError x)
      | Error x -> [x]

  member this.ValidValue = 
    match this.Converted, this.Errors with
    | Ok x, [] -> Some x
    | _ -> None

在第二次重载中,我收到以下错误:This construct code causes code to be less generic than indicated by type annotations. The type variable 'raw has been constrained to be type 'converted.当然这是有道理的 - 因为它没有转换,所以类型必须相同。

是否可以为此类创建这样的重载,或者我是否必须使用具有单个类型参数的另一个类?

请注意,我可以使用例如new Validated("", Ok, []),其类型为Validated<string, string>。这只是我遇到问题的实际构造函数重载。

1 个答案:

答案 0 :(得分:1)

您要做的是让您的一个类构造函数构造一个不同类型的类的实例。当然,这不可能发生:类的构造函数只能构造该类的实例。

如果你想拥有专门的构造函数,只需将它们放在函数外面:

let validatedNoChecks init convert = Validated( init, convert, [] )
let validatedNoConvert init checks = Validated( init, Ok, checks )

另请注意,您的实施效率有点低:您的属性正文将在每次访问时运行,而不仅仅是在构建时。

一般来说,我建议不要使用课程。它们仅存在于F#中以支持与C#和其他.NET语言的互操作。它们会产生问题(比如代码中的问题),并且不会添加任何内容。绝对没有理由使用它们。

例如,您的Validated类可以构建为记录+构造函数:

type Validated 'raw 'converted = { raw: 'raw; errors: string list; validValue: 'converted option }

let validated init convert checks =
    let getValidationError converted validate =
        match validate converted with
        | Ok () -> None
        | Error err -> Some err

    let converted = convert init
    let errors = match converted with
        | Ok x -> checks |> List.choose (getValidationError x)
        | Error x -> [x]
    let validValue = match converted, errors with
        | Ok x, [] -> Some x
        | _ -> None

    { raw = init; errors = errors; validValue = validValue }

然后是专门的构造函数:

let validatedNoChecks init convert = validated init convert []
let validatedNoConvert init checks = validated init Ok checks

如果您确实需要将“面向对象”的接口扩展到某些外部C#代码,请使用接口:只需将上述记录转换为接口并使用对象表达式构造它。仍然没有理由使用课程。

如果您希望对结果进行延迟评估,请使用Lazy<'t>作为errorsvalidValue的类型。仍然没有理由使用课程。