Let’s look at a sample evaluation of evens
on an input list. I’ll use "abcde"
—recall that String
is an alias for a list of characters [Char]
, so this is equivalent to ['a', 'b', 'c', 'd', 'e']
or 'a' : 'b' : 'c' : 'd' : 'e' : []
.
We start with the initial input:
evens "abcde"
Match the first pattern of evens
, adding 'a'
to the beginning of the result and proceeding on the remainder of the list:
evens "abcde"
-------------
-- evens (x : xs) = x : odds xs
-- x = 'a'
-- xs = "bcde"
-- evens "abcde" = 'a' : odds "bcde"
'a' : odds "bcde"
-----------------
Match the first pattern of odds
, ignoring 'b'
and proceeding:
'a' : odds "bcde"
-----------
-- odds (_ : xs) = evens xs
-- xs = "cde"
-- odds "bcde" = evens "cde"
'a' : evens "cde"
-----------
First pattern of evens
, adding 'c'
:
'a' : evens "cde"
-----------
-- evens (x : xs) = x : odds xs
-- x = 'c'
-- xs = "de"
-- evens "cde" = 'c' : odds "de"
'a' : 'c' : odds "de"
---------------
First pattern of odds
, ignoring 'd'
:
'a' : 'c' : odds "de"
---------
-- odds (_ : xs) = evens xs
-- xs = "e"
-- odds "de" = evens "e"
'a' : 'c' : evens "e"
---------
First pattern of evens
, adding 'e'
:
'a' : 'c' : evens "e"
---------
-- evens (x : xs) = x : odds xs
-- x = 'e'
-- xs = "" = []
-- evens "e" = 'e' : odds ""
'a' : 'c' : 'e' : odds ""
-------------
Now, finally, the first pattern of odds
doesn’t match, because an empty list []
doesn’t match the list constructor _ : _
, so we fall through to the second (default) pattern:
'a' : 'c' : 'e' : odds ""
-------
-- odds _ = []
-- odds "" = []
'a' : 'c' : 'e' : []
--
Giving the final result:
"ace"
Basically these functions are treating the input as a “stream” of values and producing a stream as a result: evens
consumes one element and outputs it to the result, proceeding to take the odds
of the remainder; while odds
consumes one element and discards it, taking the evens
of the remainder.
This doesn’t do any calculation on the indices because it doesn’t have to—it’s just following the structure of the list. By definition, the first value in a list is at an even index (when counting from 0), so evens
keeps it and takes the odd indices of the remainder, while odds
discards it and takes the even indices of the remainder. Removing each element shifts all the indices down by one—that is, the element that was at index 1
in the input list is at index 0
in the tail of the input:
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')]
You could also implement these functions explicitly using indices instead of mutual recursion. With list comprehensions:
evens xs = [x | (i, x) <- zip [0..] xs, even i]
odds xs = [x | (i, x) <- zip [0..] xs, odd i]
Or with explicit map
and filter
:
evens = map snd . filter (even . fst) . zip [0..]
odds = map snd . filter (odd . fst) . zip [0..]
Then you could even turn the predicate on the indices into a parameter:
indices f = map snd . filter (f . fst) . zip [0..]
evens = indices even
odds = indices odd