类型安全的差异列表

时间:2012-11-12 15:10:55

标签: haskell types

Haskell中的一个常见习语是差异列表,它将列表xs表示为值(xs ++)。然后(.)变为“(++)”,id变为“[]”(实际上这适用于任何幺半群或类别)。由于我们可以在恒定时间内组合函数,因此这为我们提供了一种通过追加来有效构建列表的好方法。

不幸的是,[a] -> [a]类型比(xs ++)形式的函数类型更大 - 列表上的大多数函数都会在它们的参数之前做一些事情。

围绕此方法(在dlist中使用)的一种方法是使用智能构造函数创建一个特殊的DList类型。另一种方法(在ShowS中使用)是不在任何地方强制执行约束并希望最好。但是,在使用大小合适的类型时,是否有一种很好的方法可以保留差异列表的所有所需属性?

1 个答案:

答案 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}}函数不是任何列表的追加操作。