Я думаю, что ваша интерпретация в основном правильная! Позвольте мне прояснить несколько вещей.
Почему не работает ваше определение len
?
Сначала, когда вы объявляете функцию с переменными типа (такими как a
и * 1007) * в Pair a b
), ваша функция должна работать для любого выбора a
или b
. Вот почему в сообщении об ошибке, которое вы видите, компилятор говорит:
...
the type signature for:
len :: forall a b. Pair a b -> Integer
...
forall
- это нечто, неявное в Haskell, когда мы пишем Pair a b
.
Итак, компилятор злится на вас, потому что вы пытаетесь использовать определенный c вид b
(а именно Pair a0 b0
), но ваша функция не будет работать, если b
будет, скажем, Int
.
Почему cdr (cdr l3)
работает?
Это потому, что компилятор знает тип l3
. Когда вы применяете cdr
к нему, вы получаете что-то вроде Pair a b
, так что второе приложение работает.
Вы можете попросить компилятор определить, какими будут типы этих функций. Обратите внимание, что им требуется более конкретный тип c, чем просто Pair a b
.
Prelude> cddr x = cdr (cdr x)
Prelude> :t cddr
cddr :: Pair a1 (Pair a2 b) -> b
Prelude> caddr x = car (cdr (cdr x))
Prelude> :t caddr
caddr :: Pair a1 (Pair a2 (Pair a3 b)) -> a3
. Ситуация несколько усложняется тем, что компилятор делает вывод, что NullPair
имеет очень общий тип forall a b. Pair a b
. Когда он передается как аргумент , a
и b
могут быть выбраны так, чтобы проверялся тип выражения. Таким образом, любое произвольное использование car
и cdr
s на NullPair
, как car (car (cdr NullPair))
, будет проверять тип. Между этими forall
s существует двойственность, когда они передаются функциям и когда их ожидают функции. Но если это объяснение сбивает с толку, вы можете игнорировать его сейчас.
Как обойти это?
Я бы порекомендовал сделать ваш тип данных явно рекурсивным. Это теряет некоторую общность в том, как вы можете использовать тип данных Pair
, но в противном случае было бы сложно написать len
.
data Pair a = NullPair | Pair{ thisCar :: a, thisCdr :: Pair a }
Теперь любая функция, которую вы пишете, будет знать, что thisCdr
имеет форма Pair a
.
(Вы можете заметить, что это просто определение списков с разными именами).
Если вы действительно хотите оставить определение Pair
одинаковым
Я бы не рекомендовал это, но если вы действительно хотите сохранить свое определение Pair
таким же, вот как вы можете это исправить.
Определите тип данных
data Fix f = Fix (f (Fix f))
Имя Fix
является обычным (насколько я могу судить) для такого типа данных; Я не называю это, потому что это решение вашей проблемы. Вы можете рассматривать его как тип данных «рекурсия» (если вы знаете о функции fix
, это ее аналог для типов).
Теперь мы можем использовать ее для рекурсия в Pair
.
len :: Fix (Pair a) -> Integer
len (Fix NullPair) = 0
len (Fix p) = 1 + (len $ thisCdr p)
Если бы мы исследовали тип p
, мы бы увидели, что это p :: Pair a (Fix (Pair a))
. В общем, что-то типа Fix (Pair a)
выглядит как
Fix (Pair a) = Fix (Pair a (Fix (Pair a)))
= Fix (Pair a (Fix (Pair a (Fix (Pair a)))))
= ...
Это дает нам «бесконечный тип», на который компилятор жаловался в вашем первом определении len
. Хотя я использую кавычки, поскольку тип может быть записан конечным образом.
Обратите внимание, что Fix (Pair a)
эквивалентно явно рекурсивному определению Pair
, которое я предложил в разделе выше. Таким образом, в некотором смысле это то же самое решение, только с рекурсивным типом данных, сделанным более явным (или, может быть, запутанным).