F#中的免费Monad,具有通用输出类型

dto*_*now 7 monads f# free-monad

我正在尝试应用F#中描述的免费monad模式以实现数据访问的乐趣和利益(对于Microsoft Azure表存储)

假设我们有三个数据库表和三个dao的Foo,Bar,Baz:

Foo          Bar          Baz

key | col    key | col    key | col
---------    ---------    ---------
foo |  1     bar |  2         |
Run Code Online (Sandbox Code Playgroud)

我想用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 ()))))
Run Code Online (Sandbox Code Playgroud)

在解释器功能内

  • 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
Run Code Online (Sandbox Code Playgroud)

为了链接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
Run Code Online (Sandbox Code Playgroud)

但是,f#编译器正确警告我:

The type variable 'T1 has been constrained to be type 'T2
Run Code Online (Sandbox Code Playgroud)

对于bindOp的这个实现,类型在整个计算过程中是固定的,因此不是:

Foo > Bar > unit
Run Code Online (Sandbox Code Playgroud)

我只能表达的是:

Foo > Foo > Foo
Run Code Online (Sandbox Code Playgroud)

在整个计算过程中,如何修改StoreOp和/或bindOp的定义以使用不同的类型?

Tom*_*cek 5

正如Fyodor在评论中提到的那样,问题在于类型声明。如果要以牺牲类型安全为代价进行编译,则可以obj在两个地方使用-至少可以显示问题出在哪里:

type StoreOp<'T> =
  | Select of string * (obj -> StoreOp<'T>)
  | Insert of obj * (unit -> StoreOp<'T>)
  | Done of 'T
Run Code Online (Sandbox Code Playgroud)

我不完全确定这两个操作应该建模的方式-但是我想这Select意味着您正在读取某些内容(使用string键?),并且Insert意味着您正在存储一些值(然后继续进行unit)。因此,在这里,您要存储/读取的数据将是obj

有多种使这种类型安全的方法,但是我认为如果您解释了通过使用Monadic结构要实现的目标,将会得到更好的答案。

如果不了解更多信息,我认为使用免费的monad只会使您的代码非常混乱并且难以理解。F#是一种功能优先的语言,这意味着您可以使用不变的数据类型以优美的功能风格编写数据转换,并使用命令式编程来加载数据和存储结果。如果您正在使用表存储,为什么不编写普通的命令性代码以从表存储中读取数据,将结果传递给纯函数转换,然后存储结果呢?

  • 我同意分离纯净的代码和不纯净的代码是可取的,但是免费的monad是这样做的一种糟糕方法。最终,该代码无论如何都是不纯净的-免费的monad允许您做的是抽象出杂质的处理方式-我认为这样做没有任何好处。(您可能会说这对测试很有用,但我认为这只是掩盖了您应该测试的关键部分的测试,而这正是对数据的操作。)如果要实现特定的目标,那么有更好的选择做到这一点的方法。 (3认同)