是的!
我们可以将[a]
其视为一个免费的 monad 实例Free ((,) a) ()
。
因此,我们可以应用 Edward Kmett 在Free Monads for Less中描述的方案。
我们会得到的类型是
newtype F a = F { runF :: forall r. (() -> r) -> ((a, r) -> r) -> r }
或者干脆
newtype F a = F { runF :: forall r. r -> (a -> r -> r) -> r }
我们列表runF
的函数就是这样!foldr
这被称为Boehm-Berarducci 编码,它与原始数据类型(列表)同构——所以它尽可能小。
威尔内斯 说:
所以这种类型仍然太“宽”,它允许的不仅仅是前缀 - 不限制 g 函数参数。
如果我正确理解了他的论点,他指出您可以将foldr
(or runF
) 函数应用于与 and 不同的[]
东西(:)
。
但我从未声称您只能将foldr
-encoding 用于连接。事实上,正如这个名字所暗示的,你可以用它来计算任何折叠——这就是 Will Ness 所展示的。
如果您暂时忘记了有一个真正的列表类型,可能会变得更清楚,[a]
. 可能有很多列表类型——例如我可以定义一个
data List a = Nil | Cons a (List a)
它与 不同,但同构[a]
。
上面的foldr
-encoding 只是列表的另一种编码,例如List a
or [a]
。它也与 同构[a]
,正如函数所证明的那样\l -> F (\a f -> foldr a f l)
,\x -> runF [] (:)
并且它们的组合(以任一顺序)是同一性的事实。但是您没有义务转换为[a]
- 您可以List
直接转换为,使用\x -> runF x Nil Cons
.
重要的一点是它F a
不包含不是foldr
某个列表的函数的元素 - 也不包含一个元素是foldr
多个列表的函数(显然)。
因此,它不会包含太少或太多的元素——它包含的元素数量恰好与准确表示所有列表所需的一样多。
差异列表编码并非如此——例如,该reverse
函数不是任何列表的追加操作。