让我们从定义开始
iterate f x = x : iterate f (f x)
我们希望将其转换为无点形式。我们可以一步一步来,但要理解它,你首先要知道
函数形成一个单子
或者,更具体地说,类型构造函数(->) r,您应该将其视为“来自类型r 的函数”或(r ->),是一个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 上下文中的值。请注意,这已经使用了函数(->) r 形成一个单子的事实!将m 专业化为(->) 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 -> 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 f 和y 的组合。
iterate = fix (\func f -> ap (:) ( func f . f) )
使用(>>=) 线程化参数
现在我们有了表达式(func f . f),如果我们使用前缀表示法,则为(.) (func f) f。我们想将此描述为应用于f 的某个函数,但这需要我们在两个地方将f 线程化到表达式中。
幸运的是,这正是 (->) r 的 monad 实例所做的!如果您还记得函数 monad 与 reader monad 完全等价,那么这完全有道理,而 reader monad 的工作是将一个附加参数线程化到每个函数调用中。
函数专用绑定的定义是
f >>= g = \r -> g (f r) r
参数r首先通过绑定的左侧参数进行线程化,右侧参数使用其结果创建一个可以使用另一个r的函数。助记符是参数r先穿入左侧参数,再穿入右侧参数。
在我们的例子中,我们写 (.) (func f) f = (func >>= (.)) 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 (:) .) . (>>= (.)) )
最后,删除内括号并用(=<<) 代替(>>=) 描述该部分
iterate = fix ((ap (:) .) . ((.) =<<))
这与 lambdabot 提出的表达式相同。