loeb 的主要来源(我认为)是Dan Piponi's blog, A Neighborhood of Infinity。他在那里更详细地解释了整个概念。我将复制一点作为答案并添加一些示例。
loeb 实现了一种奇怪的惰性递归
loeb :: Functor a => a (a x -> x) -> a x
loeb x = fmap (\a -> a (loeb x)) x
假设我们有一个类型a,其中Functor a,和一个a-代数(a x -> x 类型的函数)。您可能会认为这是从值结构中计算值的一种方式。例如,这里有一些[]-algebras:
length :: [Int] -> Int
(!! 3) :: [a] -> a
const 3 :: Num a => [a] -> a
\l -> l !! 2 + l !! 3 :: Num a => [a] -> a
我们可以看到,这些a-代数既可以使用Functor中存储的值,也可以使用Functor本身的结构。
将d :: a x -> x 视为x 的值的另一种方式是需要一些上下文——一个完整的Functor 化值a x——才能被计算。也许这种解释写成Reader (a x) x 更清楚,强调这只是x 的值,它是延迟的,等待生成a x 上下文。
type Delay q x = q -> x
使用这些想法,我们可以将loeb 描述如下。我们得到了一个f-结构,其中包含一些Delayed 值,其中f 是Functor
Functor f, f (Delay q x)
当然,如果我们得到q,那么我们可以将其转换为不延迟的形式。事实上,只有一个(非作弊)函数可以多态地做到这一点:
force :: Functor f => f (Delay q x) -> q -> f x
force f q = fmap ($ q) f
loeb 所做的是处理额外的棘手情况,其中q 实际上是force f q,这正是这个函数的结果。如果您熟悉fix,这正是我们产生此结果的方式。
loeb :: Functor a => a (Delay (a x) x) -> a x
loeb f = fix (force f)
所以举个例子,我们只需要构建一个包含Delayed 值的结构。一个自然的例子是使用之前的列表示例
> loeb [ length :: [Int] -> Int
, const 3 :: [Int] -> Int
, const 5 :: [Int] -> Int
, (!! 2) :: [Int] -> Int
, (\l -> l !! 2 + l !! 3) :: [Int] -> Int
]
[5, 3, 5, 5, 10]
在这里我们可以看到列表中充满了延迟等待评估列表结果的值。这个计算可以准确地进行,因为数据依赖没有循环,所以整个事情可以懒惰地确定。例如,const 3 和 const 5 都可以立即作为值使用。 length 要求我们知道列表的长度,但不包含任何值,因此它也立即在我们的固定长度列表上继续。有趣的是值延迟等待我们的结果列表中的其他值,但由于(!! 2) 仅最终取决于结果列表的第三个值,该值由const 5 确定,因此可以立即可用,计算向前推进。 (\l -> l !! 2 + l !! 3) 也有同样的想法。
所以你有它:loeb 完成了这种奇怪的延迟值递归。不过,我们可以在任何类型的 Functor 上使用它。我们需要做的就是想出一些有用的Delayed 值。
Chris Kuklewicz 的评论指出,使用Maybe 作为函子,您可以做的事情并不多。那是因为Maybe 上的所有延迟值都采用这种形式
maybe (default :: a) (f :: a -> a) :: Maybe a -> a
并且Maybe (Delay (Maybe a) a) 的所有有趣值都应该是Just (maybe default f),因为loeb Nothing = Nothing。因此,归根结底,default 的值甚至从未被使用过——我们总是拥有它
loeb (Just (maybe default f)) == fix f
所以我们还是直接写吧。