F# 泛型约束联合类型

8

我已经使用F#工作了几个月,但没有找到满意的解决方案来解决我的问题。 我想把一系列操作描述为值的区分联合,或对这些值进行的操作。 以这种方式,我的类型Val<'o>定义如下:

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

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

但是,如果我不使用Val<'a,'b,'o>,我无法定义通用类型'a'和'b'及其约束条件。 如果这样做,我必须定义子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[] -> obj函数的字符串函数,制作了一个表现相当不错的原型。但是,由于我还在PrimitiveValue中使用了数组和选项,所以评估过程对类型检查和转换的要求非常高,因此我正在寻找一种更优雅和强类型的解决方案。

1个回答

8
基本问题在于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<int>int -> 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来保存您的计算可以生成的各种原始值,或者您可以只使用接口表示操作 - 但是在不了解上下文的情况下很难说哪个更好。


1
请注意,您可能还需要第二个“Invoke”重载以启用返回“unit”的操作。 - kvb
还要注意,在 IFuncOperation<_,_> 中再次应用 Val<_>'T1 -> 'T2 包装起来,可以以一种不错的方式将任意柯里化度数的函数应用于相应数量的 Val<_> - kvb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接