B. *_* J. 5 generics f# discriminated-union
我和F#一起工作了几个月,但没有找到任何令人满意的解决方案来解决我的问题.我想将一系列操作描述为有价值的价值联合或对这些价值的操作.这样,我的类型Val <'o>定义如下:
type Val<'o> =
| Val of 'o
| Func1 of ('a->'o) * Val<'a>
| Func2 of ('a->'b->'o) * Val<'a> * Val<'b>
Run Code Online (Sandbox Code Playgroud)
Val <'o类型可以通过递归应用所有操作转换为'o类型,并且仍然保留操作列表.
但是,如果我不使用Val <'a,'b,'o>,我无法定义泛型类型'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>
Run Code Online (Sandbox Code Playgroud)
是否有任何F#结构可以适应这个问题?
非常感谢
[编辑]
为了进一步描述我的问题,我试图详尽地描述FRP结构(但是对于值的事件/信号,通用性问题是相同的).
表示可以序列化以用于数据库存储,转换为文本以供显示和用户编辑或评估以获得结果:
"Func (x -> x²) (Val(3.4))" <--> representation <--> 11.56
|
user
Run Code Online (Sandbox Code Playgroud)
我使用PrimitiveValueunion类型使一个原型工作得很好,并将在运行时编译的字符串obj[] -> obj函数化为泛型函数,但是在类型检查和转换时评估非常繁重(特别是因为我也在使用数组和选项PrimitiveValue),所以我一直在寻找更优雅,更强大的解决方案.
这里的基本问题是,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
Run Code Online (Sandbox Code Playgroud)
包含的值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)
Run Code Online (Sandbox Code Playgroud)
现在,valString表示int -> string应用的转换Val<int>.
你需要编写代码进行模式匹配的代码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
Run Code Online (Sandbox Code Playgroud)
我在Deedle的一些内部使用了类似的模式,但从未在代码中使用甚至接近最终用户所写的内容.我认为这在一些非常隐蔽的内部级别是可以接受的,但我肯定会避免在经常调用的内容中使用它.
根据你原来的问题,可能有一个更好的方法 - 你可以定义一个有区别的联合PrimitiveValue来保存你的计算可以产生的各种原始值,或者你可以只使用一个接口来表示操作 - 但是很难说是什么在不知道上下文的情况下更好.
| 归档时间: |
|
| 查看次数: |
638 次 |
| 最近记录: |