【问题标题】:Trying to get bind working for combination of State and Delayed monad试图让绑定为状态和延迟单子的组合工作
【发布时间】:2016-02-02 18:09:32
【问题描述】:

如果这个问题的答案是“你做错了”,一定要让我知道一个更合适的方法。我的代码结构如下:

type Res<'T> =
    | E of (DC -> 'T)
    | V of 'T

现在这种类型主要是直接使用的,有很多内联代码进行手动绑定,这是我试图摆脱的很多样板,所以我想我把它变成一个计算表达式。 mapbindapply 并不难。

State 是通过 DC 进行的,但这样做很麻烦,所以我将其更改为以下内容,这是我在与 state monad 讨论时看到的常见签名。

type Res<'T> =
    | E of (DC -> DC * 'T)
    | V of 'T

然后我决定将它提升到一个新的水平并引入延迟单子(或最终或其他名称)。所以我改变了我的类型,使其递归:

type Res<'T> =
    | E of (DC -> DC * Res<'T>)
    | V of 'T

我尝试了以下的一些变体,但不断得到:

当统一 ''a' 和 'Res -> Res'时,结果类型将是无限的

(通常发生在我不调用 return 时,即下面的 Res.E &lt;|

或者我似乎无法正确输入类型,以下(可以理解)引发类型错误,但我有一个盲点来修复它:

这个表达式的类型应该是'Res',但这里的类型是'DC * Res'。

let rec bind k res =
    match res with
    | Res.V v -> k v      // not delayed, should it be?
    | Res.E e -> 
        Res.E <| fun dc -> bind k (e dc)   // here's my error, on 'e dc'
         //(fun dc -> dc, bind f (e dc))
bind k res

我知道签名应该是('a -&gt; Res&lt;'b&gt;) -&gt; Res&lt;'a&gt; -&gt; XRes&lt;'b&gt;。问题来自正确的递归,至少在我的脑海中。我什至不确定为什么要将结果提供给Res.E,据我所知,k 延续无论如何都应该已经返回此类型。

另外,我目前的return如下(跟随Steve Horsfield):

let result v = Res.V v

但在一些帖子(尤其是hilarious Frankenfunctor)中也注意到了这一点:

let result v = Res.E <| fun dc -> dc, Res.V v

也许我遵循了我不应该遵循的模式,但当时这似乎是个好主意,尤其是在尝试重构大量样板代码并将其替换为计算表达式时,但也许我应该坚持直接评估,它适合我的想法;)。

但我觉得我很接近了……有什么想法吗?

【问题讨论】:

  • 在 Haskell 中,当想要同时应用两个 monad 时,使用 Monad Transformers。我不确定这些是否在 F# 中可用(或者它们是否甚至可以在 - 我自己没有实现)中实现。为了理解这个概念,我听说Monad Transformers Step by Step 的论文很好。
  • @paul,谢谢,我去看看。但不知何故,要求有一个一维实现来保持状态 and 对其进行延迟评估似乎并不过分。
  • 在 github 上有一个项目可以完成其中的一些工作(我现在找不到它,但我认为它使用 FsControl - 我相信有人记得它

标签: f# monads free-monad recursive-type


【解决方案1】:

我不太确定这个 monad 的含义是什么,但是
好的,经过一番思考并阅读了问题的标题:-),我确实理解了它的含义,并且我可以看到如何为它构建bind

您的错误是包装在Res.E 中的函数应该返回一个元组,但您通过递归调用bind 让它只返回Res&lt;'b&gt;。为了反映这一点,您将e 的结果传递给递归bind 调用以代替Res&lt;'b&gt; 参数,而e 实际上返回一个元组。

要正确执行此操作,您需要解构e 的结果,然后将第二部分传递给递归bind 调用,并将其结果与第一部分配对:

let rec bind (k: 'a -> 'b Res) (res: 'a Res) : 'b Res =
  match res with
  | V a -> k a
  | E e -> 
      E <| fun dc -> 
        let dc', res' = e dc 
        dc', bind k res'

终审案件,我认为也不应该延迟。也就是说,我看不出有什么理由。据我了解,V x 案例的解释应该是“一个不改变状态并返回 x 的计算”,在这种情况下延迟不会添加任何东西但是一个额外的 lambda 表达式。

【讨论】:

  • 谢谢,你是对的,这行得通!我不是(去)元组;)。至于它的含义,我使用嵌套类型是因为这里有关于构造延迟 monad(带有 unit 参数)的建议。添加的dc 参数在元组中是一个状态,每次执行都会被标记并弹出/推送。它遵循(如果我做得正确的话)State monad 的模式。整件事是两者的结合。
  • 顺便说一句,我不完全确定它现在是否仍然是尾递归的,因为递归调用 bind 现在依赖于 e dc 的结果。但是由于这本身不是递归的,所以我可能是安全的(虽然这里的深度不是很深,但最终可能无关紧要)。
  • 首先,我编辑了答案。其次,我不认为在这个定义下你可以让它成为尾递归的,但好消息是,如果你仔细想想,这个东西实际上根本不是递归的。尽管它在bind 的定义内调用bind,但从bind 不将其自身称为其自身评估的一部分的意义上说,该调用并不是真正的“递归”。相反,它返回一个再次调用bind闭包
  • Tx 进行编辑。是的,你完全正确。虽然它是递归的(rec 关键字),但它使用延续,这是编写这种绑定表达式的全部想法。在评估时,闭包会递归地扩展/调用,但就像你说的,这最终不是真正的递归;)。顺便说一句,Jon Harrop 曾经告诉我,一切都可以通过通常使用延续来实现尾递归。
  • 乔恩·哈罗普是正确的。但要做到这一点,你最终会得到不同的类型。这就是为什么我说“在这个定义下不能使尾递归”。
猜你喜欢
  • 2017-09-12
  • 2012-06-29
  • 2011-03-30
  • 2016-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多