我正在尝试应用F#中描述的免费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", fun foo -> Done foo) >>= (fun foo -> Select ("bar", fun bar -> Done bar) >>= (fun bar -> Insert ((Baz ("baz", foo.col + bar.col), fun () -> Done ()))))
在解释器功能内
Select
导致一个函数调用,它接受一个key : string
并返回一个obj
Insert
导致一个函数调用,它接受obj
并返回unit
问题
我定义了两个操作Select
,Insert
除了Done
终止计算:
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的定义以使用不同的类型?
正如Fyodor在评论中提到的那样,问题在于类型声明。如果要以牺牲类型安全为代价进行编译,则可以obj
在两个地方使用-至少可以显示问题出在哪里:
type StoreOp<'T> = | Select of string * (obj -> StoreOp<'T>) | Insert of obj * (unit -> StoreOp<'T>) | Done of 'T
我不完全确定这两个操作应该建模的方式-但是我想这Select
意味着您正在读取某些内容(使用string
键?),并且Insert
意味着您正在存储一些值(然后继续进行unit
)。因此,在这里,您要存储/读取的数据将是obj
。
有多种使这种类型安全的方法,但是我认为如果您解释了通过使用Monadic结构要实现的目标,将会得到更好的答案。
如果不了解更多信息,我认为使用免费的monad只会使您的代码非常混乱并且难以理解。F#是一种功能优先的语言,这意味着您可以使用不变的数据类型以优美的功能风格编写数据转换,并使用命令式编程来加载数据和存储结果。如果您正在使用表存储,为什么不编写普通的命令性代码以从表存储中读取数据,将结果传递给纯函数转换,然后存储结果呢?