Haskell中的一个常见习语是差异列表,它将列表xs
表示为值(xs ++)
。然后(.)
变为“(++)
”,id
变为“[]
”(实际上这适用于任何幺半群或类别)。由于我们可以在恒定时间内组合函数,因此这为我们提供了一种通过追加来有效构建列表的好方法。
不幸的是,[a] -> [a]
类型比(xs ++)
形式的函数类型更大 - 列表上的大多数函数都会在它们的参数之前做一些事情。
围绕此方法(在dlist
中使用)的一种方法是使用智能构造函数创建一个特殊的DList
类型。另一种方法(在ShowS
中使用)是不在任何地方强制执行约束并希望最好。但是,在使用大小合适的类型时,是否有一种很好的方法可以保留差异列表的所有所需属性?
答案 0 :(得分:25)
是!
我们可以将[a]
视为免费的monad实例Free ((,) a) ()
。
因此,我们可以在Free Monads for Less中应用Edward Kmett描述的方案。
我们将获得的类型是
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 encoding,它与原始数据类型(列表)同构 - 所以这个就像你可能得到的一样小。
Ness会说:
所以这种类型仍然太“宽”,它不仅仅允许前缀 - 不会限制g函数参数。
如果我理解他的论点,他指出您可以将foldr
(或runF
)函数应用于与[]
和(:)
不同的内容。
但我从未声称您只能使用foldr
- 编码进行连接。事实上,正如这个名字所暗示的那样,你可以用它来计算任何折叠 - 这就是Will Ness所展示的。
如果您忘记了一个真正的列表类型[a]
,可能会更清楚。可能有许多列表类型 - 例如我可以通过
data List a = Nil | Cons a (List a)
它与[a]
不同,但与foldr
同构。
上面的List a
- 编码只是列表的另一种编码,例如[a]
或[a]
。它也与\l -> F (\a f -> foldr a f l)
同构,正如函数\x -> runF [] (:)
和[a]
所证明的那样,以及它们的成分(以任何顺序)都是同一性的事实。但您没有义务转换为List
- 您可以使用\x -> runF x Nil Cons
直接转换为F a
。
重要的一点是foldr
不包含某个列表不是foldr
函数的元素 - 它也不包含reverse
函数的元素比一个清单(显然)。
因此,它不包含太少或太多的元素 - 它包含精确代表所有列表所需的元素。
差异列表编码不是这样 - 例如,{{1}}函数不是任何列表的追加操作。