具体而言,为什么我们使用foldr来编码列表,使用迭代来编码数字?
抱歉我的介绍有些冗长,但我不知道如何命名我想要询问的东西,所以我需要先进行一些阐述。这主要参考了C.A.McCann的这篇文章,但它并没有完全满足我的好奇心,同时我也会略显地讲解rank-n-types和无限懒加载的问题。
将数据类型编码为函数的一种方法是创建一个“模式匹配”函数,该函数接收每个情况的一个参数,每个参数都是一个接收与该构造函数相应的值的函数,并且所有参数都返回相同的结果类型。
对于非递归类型,这一切都按预期进行。
--encoding data Bool = true | False
type Bool r = r -> r -> r
true :: Bool r
true = \ct cf -> ct
false :: Bool r
false = \ct cf -> cf
--encoding data Either a b = Left a | Right b
type Either a b r = (a -> r) -> (b -> r) -> r
left :: a -> Either a b r
left x = \cl cr -> cl x
right :: b -> Either a b r
right y = \cl cr -> cr y
然而,当涉及到递归类型时,与模式匹配的好比并不适用。我们可能会想要做一些类似于以下的事情:
然而,当涉及到递归类型时,与模式匹配的美好类比就不再适用了。我们可能会尝试像下面这样做:
--encoding data Nat = Z | S Nat
type RecNat r = r -> (RecNat -> r) -> r
zero = \cz cs -> cz
succ n = \cz cs -> cs n
-- encoding data List a = Nil | Cons a (List a)
type RecListType a r = r -> (a -> RecListType -> r) -> r
nil = \cnil ccons -> cnil
cons x xs = \cnil ccons -> ccons x xs
但是在Haskell中,我们无法编写这些递归类型定义!通常的解决方案是强制将cons/succ情况的回调应用于所有层次的递归而不仅仅是第一层(即编写一个fold/iterator)。 在此版本中,我们使用返回类型r
来代替递归类型:
--encoding data Nat = Z | S Nat
type Nat r = r -> (r -> r) -> r
zero = \cz cf -> cz
succ n = \cz cf -> cf (n cz cf)
-- encoding data List a = Nil | Cons a (List a)
type recListType a r = r -> (a -> r -> r) -> r
nil = \z f -> z
cons x xs = \z f -> f x (xs z f)
尽管这个版本可以工作,但是它使得一些函数的定义变得更加困难。例如,如果你使用了模式匹配,那么编写列表的"tail"函数或数字的"predecessor"函数就很容易,但如果您需要使用folds,则会变得棘手。
接下来是我的真正问题:
我们如何确保使用folds进行编码与假设的“模式匹配编码”一样强大?是否有一种方法可以通过模式匹配来机械地将任意函数定义转换为仅使用folds的函数定义?(如果有的话,这也将有助于使像tail或foldl这样的棘手定义不再那么神奇)
为什么Haskell类型系统不允许“模式匹配”编码中所需的递归类型?为什么只允许通过"data"定义递归类型的数据类型?模式匹配是否是直接消耗递归代数数据类型的唯一方式?这是否与类型推断算法有关?
O(1)
(我认为这甚至比计算机通常使用的二进制版本更有效率)。 - PyRulez