我认为loeb
的主要来源是丹·皮波尼的博客《无限邻域》(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
的函数)。您可以将其视为从值结构中计算值的方法。例如,这里是一些[]
代数:
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
的方式是将其视为需要某些上下文——整个 Functor
化的值 a x
才能计算得出的 x
值。也许更清晰的解释是 Reader (a x) x
,强调这只是一个被延迟的 x
值,等待产生 a x
上下文。
type Delay q x = q -> x
使用这些思想,我们可以描述loeb
如下。给定一个包含一些Delay
延迟值的f
-结构,其中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)
因此,为了举例说明,我们只需构建一个包含
Delay
值的结构。一个自然的例子是使用之前的列表示例。
> 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
使用它,只需要考虑一些有用的Delay
ed值即可。
Chris Kuklewicz的评论指出,如果将Maybe
作为functor,那么你可以做的事情并不多。这是因为所有关于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
所以我们最好直接写出来。
loeb g = x where x = g <*> pure x
。这让我们想起了fix f = x where x = f $ x
。 - Will Ness