我必须实现haskell map函数来处理教会列表,其定义如下:
type Churchlist t u = (t->u->u)->u->u
在lambda演算中,列表编码如下:
[] := λ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
我不知道这个解决方案是如何工作的,我不知道如何创建这样的功能。我已经体验过lambda演算和教堂数字,但是这个练习对我来说是一个很大的问题,我必须能够在下周的考试中理解并解决这些问题。有人可以给我一个很好的消息来源,在那里我可以学习如何解决这些问题,或者给我一些关于它如何运作的指导?
答案 0 :(得分:33)
所有lambda演算数据结构都是函数,因为lambda演算中就是全部。这意味着布尔,元组,列表,数字或任何东西的表示必须是一些表示该事物的活动行为的函数。
对于列表,它是一个“折叠”。不可变的单链接列表通常定义为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)
。
答案 1 :(得分:7)
因此,让我们从编写两个列表构造函数开始,使用您的示例作为参考:
[] := λ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
必须在所有列表上按预期工作。
答案 2 :(得分:3)
好吧,我们可以用这种方式评论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
。