【发布时间】:2017-05-30 18:43:05
【问题描述】:
我正在尝试应用 F# for fun and profit 中描述的免费 monad 模式来实现数据访问(用于 Microsoft Azure 表存储)
示例
假设我们有三个数据库表和三个 dao 的 Foo、Bar、Baz:
Foo Bar Baz
key | col key | col key | col
--------- --------- ---------
foo | 1 bar | 2 |
我想选择带有 key="foo" 的 Foo 和带有 key="bar" 的 Bar 以插入带有 key="baz" 和 col=3 的 Baz
Select<Foo> ("foo", fun foo -> Done foo)
>>= (fun foo -> Select<Bar> ("bar", fun bar -> Done bar)
>>= (fun bar -> Insert<Baz> ((Baz ("baz", foo.col + bar.col), fun () -> Done ()))))
在解释器函数中
-
Select产生一个函数调用,该函数调用采用key : string并返回obj -
Insert产生一个函数调用,它接受obj并返回unit
问题
除了Done之外,我还定义了两个操作Select和Insert来终止计算:
type StoreOp<'T> =
| Select of string * ('T -> StoreOp<'T>)
| Insert of 'T * (unit -> StoreOp<'T>)
| Done of 'T
为了链接 StoreOp,我正在尝试实现正确的绑定功能:
let rec bindOp (f : 'T1 -> StoreOp<'T2>) (op : StoreOp<'T1>) : StoreOp<'T2> =
match op with
| Select (k, next) ->
Select (k, fun v -> bindOp f (next v))
| Insert (v, next) ->
Insert (v, fun () -> bindOp f (next ()))
| Done t ->
f t
let (>>=) = bindOp
但是,f# 编译器正确地警告我:
The type variable 'T1 has been constrained to be type 'T2
对于 bindOp 的这个实现,类型在整个计算过程中是固定的,所以不是:
Foo > Bar > unit
我只能说:
Foo > Foo > Foo
我应该如何修改 StoreOp 和/或 bindOp 的定义以在整个计算过程中使用不同的类型?
【问题讨论】:
-
我可以在您的
bindOp代码中指出此错误的确切原因,但根本原因是您的StoreOp类型。如果你仔细观察,你会发现它只能表达同一类型的操作链。 -
难道不能避免所有这些间接级别并在Transaction Script 中执行简单的 CRUD 操作吗?这与 Tomas Petricek 在其answer 的最后一段中所描述的相似。另见Why the free Monad isn't free。
-
当前实现是一组简单的命令式 CRUD 函数。请参阅下面的评论以获取动力。
标签: f# monads free-monad