Shane Xu's Home

Life is too short for so much sorrow.

In fact, we can even write foldl using foldr!

Understanding foldl in terms of foldr

If you want to set yourself a solid challenge, try to follow the above definition of foldl using foldr. Be warned: this is not trivial! You might want to have the following tools at hand: some headache pills and a glass of water, ghci (so that you can find out what the id function does), and a pencil and paper.

前几天群里有人发了篇文章:

5 Emerging Programming Languages With a Bright Future

这五门语言分别是:Elm, Kotlin, Rust, elixir, Crystal。

Elm,Hmm,不禁让我想起了两年前在小黑屋里的日子。这几年Elm有什么长足的进步吗?然后我竟然去YouTube上搜了下。然后看了下面的视频。

From Rails to Elm and Haskell - Richard Feldman

诚然,Elm的函数式编程的特性很吸引我。

上面的视频有一段关于

如何学习 haskell :

67538539-e78d3e00-f6ce-11e9-8ea3-8087ce6511f0.png

呵呵。

然而,

I’ve tried learn Haskell...several times!

于是重新打开了翻开了很多次的,《Real World Haskell》。

这次从第四章开始吧。

下面是关于第四章中,foldl,foldr的内容。

先来看看foldl的定义

1: import Prelude(id)
2: 
3: foldl :: (a -> b -> a) -> a -> [b] -> a
4: foldl step zero (x:xs) = foldl step (step zero x) xs
5: foldl _ zero []        = zero

例如计算[Integer]的和:

1: import Prelude((+))
2: 
3: foldl (\a b -> a+b) 0 [1,2,3,4]
4: 
5: foldl (+) 0 [1,2,3,4]

而foldr的定义是这样的

1: foldr :: (a -> b -> b) -> b -> [a] -> b
2: foldr step zero (x:xs) = step x (foldr step zero xs)
3: foldr _ zero []        = zero

同样用foldr也可以计算[Integer]的和:

1: import Prelude((+))
2: 
3: foldr (\a b -> a+b) 0 [1,2,3,4]
4: 
5: foldr (+) 0 [1,2,3,4]

看似作用相同,但是foldl和foldr的计算步骤(顺序)是不一样的,foldl从左往右计算,而foldr则是从右往左。

比如把用foldl计算[Integer]的过程展开:

((((0) + 1) + 2) + 3) + 4

比如把用foldr计算[Integer]的过程展开:

(1 + (2 + (3 + (4 + (0)))))

重点来了,其实我们常用的 map :: (a -> b) -> [a] -> [b] 函数也可以用foldr来表达。

1: myMap f xs = foldr step [] xs
2:   where
3:     step x ys = f x : ys

filter :: (a -> Bool) -> [a] -> [a] 函数也可以用foldr来表达。

1: myFilter p xs = foldr step [] xs
2:   where
3:     step x ys
4:       | p x = x : ys
5:       | otherwise = ys

我们来说标题。事实上,foldl函数也能够用foldr来表达。

1: myFoldl :: (a -> b -> a) -> a -> [b] -> a
2: myFoldl f z xs = foldr step id xs z
3:   where
4:     step x g a = g (f a x)

然后《Real World Haskell》第四章的作者在写完上面这段代码之后,就写了本文开头引用的那段话。

foldr函数有三个参数,我们可以加上括号,来帮助思考

1: myFoldl :: (a -> b -> a) -> a -> [b] -> a
2: myFoldl f z xs = (foldr step id xs) z
3:   where
4:     step x g a = g (f a x)

整个函数的返回值是a,而z的类型也是a,那么括号中的表达式的类型为 a -> a,是一个从类型a到类型a的映射(入参类型为a,出参类型为a的函数)。而id这个函数,则是Prelude模块中的函数,也就是Identity function(恒等函数),恒等函数是数学中对于传回和其输入值相同的函数的称呼。也就是括号中foldr的初始值是identity function,其类型在此场景下为 a -> a。那么step函数的类型为:

step :: b -> (a -> a) -> (a -> a)

也可以写成

step :: b -> (a -> a) -> a -> a

那么上面代码中where后面step的参数声明中x、g、a的类型分别为:

1: x :: b
2: g :: a -> a
3: a :: a

同时 step x g 的类型为:

1: step x g :: a -> a
2: step x g = \a -> g (f a x)

当 xs = [] 时

(\a -> id a) z

当 xs = [x1] 时

(\a' -> ((\a -> id a) (f a' x1))) z

当 xs = [x2, x1] 时

(\a'' -> (\a' -> ((\a -> id a) (f a' x1))) (f a'' x2)) z

当 xs = [x3, x2, x1] 时

(\a''' -> (\a'' -> (\a' -> ((\a -> id a) (f a' x1))) (f a'' x2)) (f a''' x3)) z

以此类推。

大概就这样吧。

Comments

comments powered by Disqus