This is a follow up to my previous question.
I am trying to understand the list splitting example in Haskell from here:
foldr (\a ~(x,y) -> (a:y,x)) ([],[])
I can read Haskell and know what foldr is but don't understand this code. Could you walk me through this code and explain it in more details ?
Let’s try running this function on a sample input list, say [1,2,3,4,5]:
foldr (\a ~(x,y) -> (a:y,x)) ([],[]) [1,2,3,4,5]. Here a is the first element of the list, and (x,y) start out as ([],[]), so (a:y,x) returns ([1],[]).a = 2, and (x,y) = ([1],[]), so (a:y,x) = ([2],[1]). Note that the order of the lists has swapped. Each iteration will swap the lists again; however, the next element of the input list will always be added to the first list, which is how the splitting works.a = 3, and (x,y) = ([2],[1]), so (a:y,x) = ([3,1],[2]).a = 4, and (x,y) = ([3,1],[2]), so (a:y,x) = ([4,2],[3,1]).a = 4, and (x,y) = ([4,2],[3,1]), so (a:y,x) = ([5,3,1],[4,2]).([5,3,1],[4,2]).As the walkthrough shows, the split function works by maintaining two lists, swapping them on each iteration, and appending each element of the input to a different list.
We can take a look at an example. For example if we have a list [1, 4, 2, 5]. If we thus process the list, then we see that foldr will be calculated as:
foldr (\a ~(x,y) -> (a:y,x)) ([],[]) [1,4,2,5]
So here a is first the first item of the list, and then it will tus return something like:
(1:y, x)
where (x, y) = foldr (\a ~(x,y) -> (a:y,x)) ([],[]) [4,2,5]
Notice that here the (x, y) tuple is swapped when we prepend a to the first item of the 2-tuple.
(1:y, x)
where (x, y) = (4:y', x')
(x', y') = foldr (\a ~(x,y) -> (a:y,x)) ([],[]) [2,5]
and if we keep doing that, we thus obtain:
(1:y, x)
where (x, y) = (4:y', x')
(x', y') = (2:y'', x'')
(x'', y'') = (5:y''', x''')
(x''', y''') = foldr (\a ~(x,y) -> (a:y,x)) ([],[]) []
Since we reached the end of the list, we thus obtain for the foldr … ([], []) [], the 2-tuple ([], []):
(1:y, x)
where (x, y) = (4:y', x')
(x', y') = (2:y'', x'')
(x'', y'') = (5:y''', x''')
(x''', y''') = ([],[])
So x''' = [] and y''' = [], so thus this is resolved to:
(1:y, x)
where (x, y) = (4:y', x')
(x', y') = (2:y'', x'')
(x'', y'') = (5:[], [])
(x''', y''') = ([],[])
so x'' = [5] and y'' = []:
(1:y, x)
where (x, y) = (4:y', x')
(x', y') = (2:[], [5])
(x'', y'') = (5:[], [])
(x''', y''') = ([],[])
so x' = [5] and y' = [2]:
(1:y, x)
where (x, y) = (4:[5], [2])
(x', y') = (2:[], [5])
(x'', y'') = (5:[], [])
(x''', y''') = ([],[])
so x = [4, 5] and y = [2] so eventually we obtain:
(1:[2], [4,5])
where (x, y) = (4:[5], [2])
(x', y') = (2:[], [5])
(x'', y'') = (5:[], [])
(x''', y''') = ([],[])
so the result is the expected ([1,2], [4,5]).
Let's translate the fold away.
splatter :: [a] -> ([a], [a])
splatter = foldr (\a ~(x,y) -> (a:y,x)) ([],[])
What's this mean? foldr for lists is defined
foldr :: (a -> r -> r) -> r -> [a] -> r
foldr k z = go
where
go [] = z
go (p : ps) = p `k` go ps
Let's inline it and simplify:
splatter = go
where
go [] = ([], [])
go (p : ps) =
(\a ~(x,y) -> (a:y,x)) p (go ps)
splatter = go
where
go [] = ([], [])
go (p : ps) =
(\ ~(x,y) -> (p:y,x)) (go ps)
splatter = go
where
go [] = ([], [])
go (p : ps) =
let (x, y) = go ps
in (p : y, x)
The lazy-by-default pattern match in the let means that we don't actually actually make the recursive call until someone forces x or y.
The key thing to notice is that x and y swap places on each recursive call. This leads to the alternating pattern.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With