让我们看一下输入列表中evens 的示例评估。我将使用"abcde"——回想一下String 是字符列表[Char] 的别名,所以这相当于['a', 'b', 'c', 'd', 'e'] 或'a' : 'b' : 'c' : 'd' : 'e' : []。
我们从初始输入开始:
evens "abcde"
匹配evens 的第一个模式,将'a' 添加到结果的开头并继续处理列表的其余部分:
evens "abcde"
-------------
-- evens (x : xs) = x : odds xs
-- x = 'a'
-- xs = "bcde"
-- evens "abcde" = 'a' : odds "bcde"
'a' : odds "bcde"
-----------------
匹配odds的第一个模式,忽略'b'并继续:
'a' : odds "bcde"
-----------
-- odds (_ : xs) = evens xs
-- xs = "cde"
-- odds "bcde" = evens "cde"
'a' : evens "cde"
-----------
evens的第一个模式,添加'c':
'a' : evens "cde"
-----------
-- evens (x : xs) = x : odds xs
-- x = 'c'
-- xs = "de"
-- evens "cde" = 'c' : odds "de"
'a' : 'c' : odds "de"
---------------
odds 的第一个模式,忽略 'd':
'a' : 'c' : odds "de"
---------
-- odds (_ : xs) = evens xs
-- xs = "e"
-- odds "de" = evens "e"
'a' : 'c' : evens "e"
---------
evens的第一个模式,添加'e':
'a' : 'c' : evens "e"
---------
-- evens (x : xs) = x : odds xs
-- x = 'e'
-- xs = "" = []
-- evens "e" = 'e' : odds ""
'a' : 'c' : 'e' : odds ""
-------------
现在,最后,odds 的第一个模式不匹配,因为空列表 [] 与列表构造函数 _ : _ 不匹配,所以我们进入第二个(默认)模式:
'a' : 'c' : 'e' : odds ""
-------
-- odds _ = []
-- odds "" = []
'a' : 'c' : 'e' : []
--
给出最终结果:
"ace"
基本上,这些函数将输入视为值的“流”并生成一个流作为结果:evens 消耗一个元素并将其输出到结果,然后继续获取余数的odds;而odds 消耗了一个元素并将其丢弃,取而代之的是evens。
这不会对索引进行任何计算,因为它没有必要——它只是遵循列表的结构。根据定义,列表中的第一个值位于 even 索引处(从 0 开始计数),因此 evens 保留它并获取余数的 odd 索引,而odds 将其丢弃并采用余数的 even 索引。删除每个元素会将所有索引向下移动 1 — 也就是说,输入列表中位于索引 1 的元素位于输入尾部的索引 0:
zip [0..] "abcde" == [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e')]
'a' 'b' 'c' 'd' 'e'
0 1 2 3 4
| | | | |
x / / / /
/ / / /
/ / / /
/ / / /
| | | |
'b' 'c' 'd' 'e'
0 1 2 3
zip [0..] "bcde" == [(0, 'b'), (1, 'c'), (2, 'd'), (3, 'e')]
您还可以使用索引而不是相互递归来显式实现这些函数。使用列表推导:
evens xs = [x | (i, x) <- zip [0..] xs, even i]
odds xs = [x | (i, x) <- zip [0..] xs, odd i]
或者使用明确的map 和filter:
evens = map snd . filter (even . fst) . zip [0..]
odds = map snd . filter (odd . fst) . zip [0..]
然后你甚至可以将索引上的谓词变成一个参数:
indices f = map snd . filter (f . fst) . zip [0..]
evens = indices even
odds = indices odd