我和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&lt;&gt; o&gt;可以通过递归应用所有操作转换为&o; o类型,并仍保留操作列表。
但如果我不使用Val&lt;&#39; a,&b; b,我就无法定义通用类型&#39; a和&b; b及其约束条件, &#39;○取代。 如果我这样做,我必须定义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[] -> obj
函数,但对类型检查和转换的评估非常繁重(特别是因为我是也使用PrimitiveValue
中的数组和选项,所以我一直在寻找一种更优雅,更强类型的解决方案。
答案 0 :(得分:6)
这里的基本问题是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
表示应用于int -> string
的{{1}}转换。
在Val<int>
上编写模式匹配所需的代码非常难看:
Func
我在Deedle的一些内部使用了类似的模式,但从未在代码中使用甚至接近最终用户所写的内容。我认为这在一些非常隐蔽的内部水平上是可以接受的,但我肯定会避免在经常被称为的东西中使用它。
根据原始问题的原因,可能有一种更好的方法 - 你可以定义一个有区别的联合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
来保存你的计算可以产生的各种原始值,或者你可以只使用一个来表示操作。界面 - 但是如果不了解背景,很难说你的情况会更好。