【问题标题】:Where did bind come from?绑定是从哪里来的?
【发布时间】:2014-02-28 15:20:14
【问题描述】:

使用lambdabot的pl插件,

let iterate f x = x : iterate f (f x) in iterate

转换为

fix ((ap (:) .) . ((.) =<<))

(=&lt;&lt;) 在这里是什么意思?我以为它只与 monads 一起使用。

【问题讨论】:

  • 是的。函数形成一个单子。 ;-)

标签: haskell types monads pointfree


【解决方案1】:

是的,这里的monad是((-&gt;) a)iterate f x显然构造了一个列表,所以iterate的类型是(a-&gt;a)-&gt;a-&gt;[a],所以给定(a-&gt;a),它就产生(a-&gt;[a])

可以看到ap被赋予了一个函数(:) :: a-&gt;[a]-&gt;[a],必须是m (a-&gt;b),所以m这里是(-&gt;) a

(ap (:) .) . ((.) =<<) =
\f -> (ap (:) .) . ((.) =<<) $ f =
\f -> ap (:) . ((.) =<< f) = -- at this stage we can see =<< is of (->) a monad,
                       -- whose bind is the S-combinator: s f g x = f (g x) x
\f -> ap (:) . (f >>= (.)) =
\f g -> ap (:) (f g . g) = -- ok, ap is also the S-combinator with some
                           -- arguments swapped around
-- you see, for monad ((->) r), ap needs function of type (r->(a->b)) = (r->a->b)
-- whereas >>= needs (a->(r->b)) = (a->r->b) - the same function flipped
\f g -> (flip (:)) =<< (f g . g) =
\f g -> \x -> x : (f g $ g x)

这是一个有两个参数的函数,a-&gt;(b-&gt;c),所以当它传递给fix :: (a-&gt;a)-&gt;a时,一定是a=(b-&gt;c)fix (ap...) :: b-&gt;c。反过来,既然这一切都和iterate一样,那肯定是b-&gt;c = (x-&gt;x)-&gt;(x-&gt;[x]),所以b=(x-&gt;x)c=(x-&gt;[x])

确实:

Prelude Control.Monad> :t ((ap (:) .) . ((.) =<<))
((ap (:) .) . ((.) =<<))
  :: (Monad ((->) (a -> b)), Monad ((->) a)) =>
     ((a -> b) -> b -> [a]) -> (a -> b) -> a -> [a]

它将在传递给fix 后绑定b=a

现在,fix 向上面提供了第一个参数,如下所示:

fix h = let x = h x in x
hence, iterate = fix h = let iterate = h iterate in iterate 
-- supply iterate as the first
-- argument to the function passed to fix

所以我们有:

iterate =
fix (\f g -> \x -> x : (f g $ g x)) =
\g x -> x : (iterate g $ g x)

【讨论】:

    【解决方案2】:

    让我们从定义开始

    iterate f x = x : iterate f (f x) 
    

    我们希望将其转换为无点形式。我们可以一步一步来,但要理解它,你首先要知道

    函数形成一个单子

    或者,更具体地说,类型构造函数(-&gt;) r,您应该将其视为“来自类型r 的函数”或(r -&gt;),是一个monad。查看它的最好方法是定义返回和绑定操作。 monad m 的一般形式是

    return :: a -> m a
    (>>=)  :: m a -> (a -> m b) -> m b
    

    专精于功能,你有

    return :: a -> r -> a
    (>>=)  :: (r -> a) -> (a -> r -> b) -> r -> b
    

    你可以说服自己唯一合理的定义是

    return x = \r -> x -- equivalent to 'const x'
    f >>=  g = \r -> g (f r) r
    

    这完全等同于Reader monad,也称为环境单子。这个想法是你有一个r 类型的附加参数(有时称为环境),它正在通过计算线程化 - 每个函数都隐式接收r 作为附加参数。

    现在我们知道了开始使我们的函数变得毫无意义所需的一切。

    使用fix 摆脱递归

    首先要删除对iterate 的递归引用。我们可以使用fix 来做到这一点,它有定义

    fix :: (t -> t) -> t
    fix f = f (fix f)
    

    您可以将fix 视为规范递归函数,因为它可用于定义其他递归函数。标准习惯用法是定义一个非递归函数g,并带有一个名为func 的附加参数,它表示您要定义的函数。将fix 应用于g 计算g 的不动点,这是您想要的递归函数。

    iterate = fix g where g func f x = x : func f (f x)
    

    我们可以将其转换为 lambda 形式

    iterate = fix (\func f x -> x : func f (f x))
            = fix (\func f x -> (:) x (func f (f x)))
    

    其中第二行刚刚删除了中缀: 并用前缀(:) 替换它。现在没有自引用,我们可以继续。

    ap删除一些点

    我们可以使用ap 来提取对x 的引用。 ap 的类型是

    ap :: (Monad m) => m (a -> b) -> m a -> m b
    

    它在某个 monadic 上下文中获取一个函数,并将其应用于另一个 monadic 上下文中的值。请注意,这已经使用了函数(-&gt;) r 形成一个单子的事实!将m 专业化为(-&gt;) r 你得到

    ap :: (r -> a -> b) -> (r -> a) -> (r -> b)
    

    使类型起作用的唯一方法是ap(专门用于函数)具有以下定义

    ap f g = \r -> f r (g r)
    

    这样您就可以使用第二个函数g 来构建第一个函数f 的第二个参数。请注意,ap 的这个定义完全等同于 SKI combinator calculus 中的组合子 S

    对我们来说,这允许我们将参数x 提供给第一个函数(:) 并使用另一个函数\y -&gt; func f (f y) 来构建第二个参数,它是列表的尾部。另外,我们可以使用 eta 缩减删除所有对 x 的引用。

    iterate = fix (\func f x -> ap (:) (\y -> func f (f y)) x)
            = fix (\func f   -> ap (:) (\y -> func f (f y))  )
    

    我们现在也可以删除对y 的引用,因为我们认识到func f (f y) 只是func fy 的组合。

    iterate = fix (\func f   -> ap (:) (      func f . f)    )
    

    使用(&gt;&gt;=) 线程化参数

    现在我们有了表达式(func f . f),如果我们使用前缀表示法,则为(.) (func f) f。我们想将此描述为应用于f 的某个函数,但这需要我们在两个地方将f 线程化到表达式中。

    幸运的是,这正是 (-&gt;) r 的 monad 实例所做的!如果您还记得函数 monad 与 reader monad 完全等价,那么这完全有道理,而 reader monad 的工作是将一个附加参数线程化到每个函数调用中。

    函数专用绑定的定义是

    f >>= g = \r -> g (f r) r
    

    参数r首先通过绑定的左侧参数进行线程化,右侧参数使用其结果创建一个可以使用另一个r的函数。助记符是参数r先穿入左侧参数,再穿入右侧参数。

    在我们的例子中,我们写 (.) (func f) f = (func &gt;&gt;= (.)) f 来获取(使用 eta 缩减)

    iterate = fix (\func f   -> ap (:)  ((func >>= (.)) f))
            = fix (\func     -> ap (:) . (func >>= (.))   )
    

    链式组合

    最后,我们使用另一个技巧,重复合成,拉出参数func。这个想法是,如果你有一个表达式

    f . g a
    

    然后你可以替换为

    f . g a = (.) f (g a)
            = (((.) f) . g) a
            = ((f .) . g) a
    

    所以你已经将它表达为一个应用于参数的函数(准备好减少 eta!)。在我们的例子中,这意味着进行替换

    iterate = fix (\func     -> (ap (:) .) . (>>= (.)) func)
            = fix (            ((ap (:) .) . (>>= (.))     )
    

    最后,删除内括号并用(=&lt;&lt;) 代替(&gt;&gt;=) 描述该部分

    iterate = fix ((ap (:) .) . ((.) =<<))
    

    这与 lambdabot 提出的表达式相同。

    【讨论】:

    • 顺便说一句,fix f = f (fix f) 的定义将(可能?..)导致每次递归调用 时间 重新计算 (fix f);定义fix f = x where x = f x 将导致在进行递归调用的每个place 处计算fix f。这是(在 Prolog 中)rec(f(G)) :- rec(G).rec(G) :- G=X, X=f(X). 或在 Lisp 中 (defun rec () (cons 'f (rec)))(defun rec () (let ((g (list 'f))) (setf (cdr g) g) g)) 之间的区别。
    • (很抱歉进行了额外的编辑;我确信它会与前一个合并,仅相隔 1 分钟,但由于某种原因它没有)。
    • @WillNess - 不知道是谁批准了排版破坏性的编辑,但我没有注意到,所以谢谢!
    【解决方案3】:

    这是一个简短、直接的combinators 风格推导:

    iterate f x 
       = x : iterate f (f x) 
       = (:) x ((iterate f . f) x)
       = ap (:) (iterate f . f) x            -- ap g f x = g x (f x)       (1)
       = ap (:) ((.) (iterate f) f) x
       = ap (:) ( ((.) =<< iterate) f) x     -- (g =<< f) x = g (f x) x    (2)
       = ap (:) ( ((.) =<<) iterate f) x
       = ((ap (:) .) . ((.) =<<)) iterate f x 
           -- ((f .) . g) x y = (f .) (g x) y = (f . g x) y = f (g x y)    (3)
    

    所以,通过 eta 收缩,

    iterate = ((ap (:) .) . ((.) =<<)) iterate
            = fix ((ap (:) .) . ((.) =<<))    -- fix f = x where x = f x   (4)
    

    QED。 (1)和(2)来自你所问的,作为单子的功能,已经在克里斯的回答中解释过:

      ap :: (Monad m) => m (a->b) ->   m a  ->  m b              m ~ (r ->)
      that's            (r->a->b) -> (r->a) -> r->b 
      so            ap     g           f       x   = g x (f x)
    
      (=<<) :: (Monad m) => (a-> m b) ->   m a  ->  m b          m ~ (r ->)
      that's                (a->r->b) -> (r->a) -> r->b
      so            (=<<)      g           f       x   = g (f x) x
    

    (3) 被讨论,例如herehere 详细。

    【讨论】:

      猜你喜欢
      • 2012-10-20
      • 1970-01-01
      • 2018-02-26
      • 2023-04-08
      • 2016-07-23
      • 2019-02-07
      • 1970-01-01
      • 2017-12-20
      • 2020-07-31
      相关资源
      最近更新 更多