【问题标题】:F# generic constraint union typeF# 泛型约束联合类型
【发布时间】:2016-11-13 17:51:19
【问题描述】:

我已经使用 F# 工作了几个月,但没有为我的问题找到任何令人满意的解决方案。 我想将一系列操作描述为值的有区别的联合,或对这些值的操作。 这样,我的类型 Val 定义如下:

type Val<'o> =
    | Val of 'o
    | Func1 of ('a->'o) * Val<'a>
    | Func2 of ('a->'b->'o) * Val<'a> * Val<'b>

类型 Val 可以通过递归应用所有操作转换为 'o 类型,并且仍然保留操作列表。

但如果我不使用 Val,我将无法定义泛型类型 'a 和 'b 及其约束。 如果我这样做了,我必须定义 sub-Val 泛型类型,我想保持泛型:

type Val<'a, 'b, 'o> =
    | Val of 'o
    | Func1 of ('a->'o) * Val<?, ?, 'a>
    | Func2 of ('a->'b->'o) * Val<?, ?, 'a> * Val<?, ?, 'b>

是否有任何 F# 结构可以适应这个问题?

非常感谢

[编辑]

为了进一步描述我的问题,我试图获得 FRP 结构的详尽表示(但通用性问题对于事件/信号与值相同)。
表示可以被序列化以用于数据库存储,翻译成文本以供显示和用户编辑或评估以获得结果:

"Func (x -> x²) (Val(3.4))" <--> representation <--> 11.56
             |
            user 

我使用 PrimitiveValue 联合类型制作了一个运行良好的原型,并将函数字符串在运行时编译为通用 obj[] -&gt; obj 函数,但评估在类型检查和强制转换方面非常繁重(特别是因为我还使用数组和PrimitiveValue 中的选项),所以我一直在寻找更优雅和强类型的解决方案。

【问题讨论】:

    标签: generics f# discriminated-union


    【解决方案1】:

    这里的基本问题是F#不允许你说在可区分联合的情况下'a'b是存储在情况下的数据的“参数”。其他一些语言支持这一点(在 Haskell 中称为广义代数数据类型),但通常需要权衡取舍,使语言变得更复杂。

    您实际上可以在 F# 中模拟它,但它很丑 - 所以在采用这种方法之前我会三思而后行。这个想法是,您可以定义一个具有通用方法的接口,该方法使用适当的类型参数'a'b 调用。

    type Val<'T> =
      | Val of 'T
      | Func of IFunc<'T>
    
    and IFunc<'T> = 
      abstract Invoke<'R> : IFuncOperation<'T, 'R> -> 'R
    
    and IFuncOperation<'T2, 'R> =
      abstract Invoke<'T1> : ('T1 -> 'T2) * Val<'T1> -> 'R
    

    包裹在Func 中的值可以指定为IFuncOperation,它会使用'a 作为泛型方法的类型参数(我的命名中的'T1)来调用它。

    您可以合理地构造这些值:

    let makeFunc f v =
      Func({ new IFunc<_> with member x.Invoke(op) = op.Invoke(f, v) })    
    let makeVal v = Val(v)
    
    let valString = makeFunc (fun n -> sprintf "Got: %d" n) (makeVal 42)
    

    现在,valString 表示应用于Val&lt;int&gt;int -&gt; string 转换。

    Func 上进行模式匹配需要编写的代码非常难看:

    let rec eval<'T> (value:Val<'T>) : 'T = 
      match value with
      | Val r -> r
      | Func f -> 
          { new IFuncOperation<'T, 'T> with
              member x.Invoke<'S>(f, value:Val<'S>) = f (eval<'S> value) }
          |> f.Invoke
    
    eval valString
    

    我在 Deedle 的一些内部结构中使用了类似的模式,但从未在与最终用户编写的代码更接近的代码中使用过。我认为这在一些非常隐蔽的内部级别是可以接受的,但我肯定会避免在经常调用的东西中使用它。

    根据您最初的问题,可能有更好的方法 - 您可以定义一个有区别的联合 PrimitiveValue 来保存您的计算可以产生的各种原始值,或者您可以只使用接口表示操作 -但是在不了解上下文的情况下,很难说出哪种情况更好。

    【讨论】:

    • 请注意,您可能还需要第二个 Invoke 重载来启用 unit-returning 操作。
    • 另请注意,通过Val&lt;_&gt; 的另一个应用程序将'T1 -&gt; 'T2 包装在IFuncOperation&lt;_,_&gt; 中,您可以以一种很好的方式将任意咖喱元数的函数应用于相应数量的Val&lt;_&gt;s。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-24
    • 1970-01-01
    • 2019-09-23
    • 1970-01-01
    • 2021-11-01
    相关资源
    最近更新 更多