或者具体来说,为什么我们使用 foldr 来编码列表和迭代来编码数字?
很抱歉介绍冗长,但我真的不知道如何命名我想问的事情,所以我需要先做一些说明。这很大程度上来自这篇 CAMcCann 帖子,它不能完全满足我的好奇心,而且我还将讨论 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 案例的回调应用于所有级别的递归,而不仅仅是第一个级别(即,编写折叠/迭代器)。在这个版本中,我们使用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”函数是微不足道的,但如果您需要使用折叠来代替,则变得很棘手。
所以我的真正问题是:
我们如何确定使用折叠的编码与假设的“模式匹配编码”一样强大?有没有办法通过模式匹配来获取任意函数定义,然后机械地将其转换为仅使用折叠的函数?(如果是这样,这也将有助于在 foldr 方面使诸如 tail 或 foldl 之类的棘手定义变得不那么神奇)
为什么 Haskell 类型系统不允许“模式匹配”编码中需要的递归类型?. 是否有理由只允许在通过定义的数据类型中使用递归类型
data
?模式匹配是直接使用递归代数数据类型的唯一方法吗?它与类型推断算法有关吗?