Или, что конкретно, почему мы используем foldr для кодирования списков и итерации для кодирования чисел?
Извините за длинное введение, но я действительно не знаю, как назвать вещи, о которых я хочу спросить, поэтому мне нужно сначала дать некоторые изложения. Это в значительной степени связано с этим сообщением CAMcCann, которое просто не удовлетворяет моему любопытству, и я также буду обсуждать проблемы с рангами n-типами и бесконечными ленивыми вещами.
Одним из способов кодирования типов данных как функций является создание функции "сопоставления шаблонов", которая получает один аргумент для каждого случая, причем каждый аргумент является функцией, которая получает значения, соответствующие этому конструктору, и все аргументы, возвращающие один и тот же тип результата.
Все это работает как ожидалось для нерекурсивных типов
--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 или foldl в терминах foldr, как менее магические)
-
Почему система типа Haskell не позволяет использовать рекурсивные типы, необходимые в кодировке "соответствие шаблону"?. Есть ли причина только разрешать рекурсивные типы в типах данных, определенных через
data
? Является ли соответствие шаблону единственным способом непосредственного использования рекурсивных алгебраических типов данных? Это связано с алгоритмом определения типа?