Церковные списки в Хаскелле

Мне пришлось реализовать функцию карты haskell для работы со списками церквей, которые определены следующим образом:

type Churchlist t u = (t->u->u)->u->u

В лямбда-исчислении списки кодируются следующим образом:

[] := λc. λn. n
[1,2,3] := λc. λn. c 1 (c 2 (c 3 n))

Пример решения этого упражнения:

mapChurch :: (t->s) -> (Churchlist t u) -> (Churchlist s u)
mapChurch f l = \c n -> l (c.f) n

Я понятия не имею, как работает это решение, и я не знаю, как создать такую ​​функцию. У меня уже есть опыт работы с лямбда-исчислением и церковными числительными, но это упражнение стало для меня большой головной болью, и я должен быть в состоянии понять и решить такие задачи к экзамену на следующей неделе. Может ли кто-нибудь дать мне хороший источник, где я мог бы научиться решать такие проблемы, или дать мне небольшое руководство о том, как это работает?


person jcdmb    schedule 17.03.2012    source источник
comment
Страница Церковное кодирование в Википедии кажется хорошим местом для начала. .   -  person Riccardo T.    schedule 17.03.2012
comment
@jcdmb: Вы изучаете информатику в КИТе?   -  person Martin Thoma    schedule 27.11.2013


Ответы (3)


Все структуры данных лямбда-исчисления — это функции, потому что это все, что есть в лямбда-исчислении. Это означает, что представление для логического значения, кортежа, списка, числа или чего-то еще должно быть некоторой функцией, которая представляет активное поведение этой вещи.

Для списков это «складка». Неизменяемые односвязные списки обычно определяются List a = Cons a (List a) | Nil, что означает, что единственными способами, которыми вы можете создать список, являются либо Nil, либо Cons anElement anotherList. Если написать его в стиле lisp, где c — это Cons, а n — это Nil, то список [1,2,3] будет выглядеть так:

(c 1 (c 2 (c 3 n)))

Когда вы выполняете свертку списка, вы просто указываете свои собственные «Cons» и «Nil», чтобы заменить список. В Haskell для этого используется библиотечная функция foldr.

foldr :: (a -> b -> b) -> b -> [a] -> b

Выглядит знакомо? Уберите [a], и вы получите тот же тип, что и Churchlist a b. Как я уже сказал, церковное кодирование представляет списки как их функцию складывания.


Итак, пример определяет map. Обратите внимание, как l используется как функция: в конце концов, это функция, которая сворачивает некоторый список. \c n -> l (c.f) n в основном говорит: «замените каждый c на c . f и каждый n на n».

(c 1 (c 2 (c 3 n)))
-- replace `c` with `(c . f)`, and `n` with `n`
((c . f) 1 ((c . f) 2) ((c . f) 3 n)))
-- simplify `(foo . bar) baz` to `foo (bar baz)`
(c (f 1) (c (f 2) (c (f 3) n))

Теперь должно быть очевидно, что это действительно функция сопоставления, потому что она выглядит точно так же, как оригинал, за исключением того, что 1 превратилось в (f 1), 2 в (f 2) и 3 в (f 3).

person Dan Burton    schedule 17.03.2012
comment
Это объяснение просто БОЖЕСТВЕННО! Большое спасибо. Вы спасли мой день XD - person jcdmb; 17.03.2012

Итак, давайте начнем с кодирования двух конструкторов списка, используя ваш пример в качестве ссылки:

[] := λc. λn. n
[1,2,3] := λc. λn. c 1 (c 2 (c 3 n))

[] — это конец конструктора списка, и мы можем взять его прямо из примера. [] уже имеет значение в Haskell, так что давайте назовем наш nil:

nil = \c n -> n

Другой конструктор, который нам нужен, берет элемент и существующий список и создает новый список. Канонически это называется cons с определением:

cons x xs = \c n -> c x (xs c n)

Мы можем проверить, что это согласуется с приведенным выше примером, поскольку

cons 1 (cons 2 (cons 3 nil))) =
cons 1 (cons 2 (cons 3 (\c n -> n)) = 
cons 1 (cons 2 (\c n -> c 3 ((\c' n' -> n') c n))) =
cons 1 (cons 2 (\c n -> c 3 n)) =
cons 1 (\c n -> c 2 ((\c' n' -> c' 3 n') c n) ) =
cons 1 (\c n -> c 2 (c 3 n)) =
\c n -> c 1 ((\c' n' -> c' 2 (c' 3 n')) c n) =
\c n -> c 1 (c 2 (c 3 n)) =

Теперь рассмотрим назначение функции map — применить заданную функцию к каждому элементу списка. Итак, давайте посмотрим, как это работает для каждого из конструкторов.

nil не имеет элементов, поэтому mapChurch f nil должно быть просто nil:

mapChurch f nil
= \c n -> nil (c.f) n
= \c n -> (\c' n' -> n') (c.f) n
= \c n -> n
= nil

cons имеет элемент и остальную часть списка, поэтому для того, чтобы mapChurch f работало должным образом, он должен применить f к элементу и mapChurch f к остальной части списка. То есть mapChurch f (cons x xs) должно быть таким же, как cons (f x) (mapChurch f xs).

mapChurch f (cons x xs)
= \c n -> (cons x xs) (c.f) n
= \c n -> (\c' n' -> c' x (xs c' n')) (c.f) n
= \c n -> (c.f) x (xs (c.f) n)
-- (c.f) x = c (f x) by definition of (.)
= \c n -> c (f x) (xs (c.f) n)
= \c n -> c (f x) ((\c' n' -> xs (c'.f) n') c n)
= \c n -> c (f x) ((mapChurch f xs) c n)
= cons (f x) (mapChurch f xs)

Итак, поскольку все списки создаются из этих двух конструкторов, и mapChurch работает с ними обоими, как и ожидалось, mapChurch должен работать так, как ожидается, со всеми списками.

person rampion    schedule 17.03.2012

Что ж, мы можем прокомментировать тип Churchlist таким образом, чтобы прояснить его:

-- Tell me...
type Churchlist t u = (t -> u -> u) -- ...how to handle a pair
                    -> u            -- ...and how to handle an empty list
                    -> u            -- ...and then I'll transform a list into 
                                    -- the type you want

Обратите внимание, что это тесно связано с функцией foldr:

foldr :: (t -> u -> u) -> u -> [t] -> u
foldr k z [] = z
foldr k z (x:xs) = k x (foldr k z xs)

foldr — это очень общая функция, которая может реализовывать все виды других функций списка. Тривиальный пример, который поможет вам, реализует копию списка с foldr:

copyList :: [t] -> [t]
copyList xs = foldr (:) [] xs

Используя закомментированный выше тип, foldr (:) [] означает следующее: «если вы видите пустой список, верните пустой список, а если вы видите пару, верните head:tailResult».

Используя Churchlist, вы можете легко написать аналог следующим образом:

-- Note that the definitions of nil and cons mirror the two foldr equations!
nil :: Churchlist t u
nil = \k z -> z

cons :: t -> Churchlist t u -> Churchlist t u
cons x xs = \k z -> k x (xs k z)  

copyChurchlist :: ChurchList t u -> Churchlist t u
copyChurchlist xs = xs cons nil

Теперь, чтобы реализовать map, вам просто нужно заменить cons подходящей функцией, например:

map :: (a -> b) -> [a] -> [b]
map f xs = foldr (\x xs' -> f x:xs') [] xs

Отображение похоже на копирование списка, за исключением того, что вместо того, чтобы просто копировать элементы дословно, вы применяете f к каждому из них.

Внимательно изучите все это, и вы сможете написать mapChurchlist :: (t -> t') -> Churchlist t u -> Churchlist t' u самостоятельно.

Дополнительное упражнение (простое): запишите эти функции списка в терминах foldr и напишите аналоги для Churchlist:

filter :: (a -> Bool) -> [a] -> [a]
append :: [a] -> [a] -> [a]

-- Return first element of list that satisfies predicate, or Nothing
find :: (a -> Bool) -> [a] -> Maybe a

Если вам хочется заняться чем-нибудь посложнее, попробуйте написать tail вместо Churchlist. (Начните с написания tail вместо [a], используя foldr.)

person Luis Casillas    schedule 17.09.2012