From https://wiki.haskell.org/99_questions/1_to_10
Problem: Find the last element of a list
So typing this:
myLast [1,2,3,4]
should give you this:
4
If myLast is defined like this:
myLast = head
then we'd get:
1
This:
myLast = reverse
would give me:
[4,3,2,1]
So I just need the head of the reverse:
myLast x = head (reverse x)
But do I really need that 'x' there. And can do I something about the parens.
This works:
myLast = head . reverse
Here's another way:
myLast x = x !! ((length x) - 1)
Another way:
myLast [x] = x
myLast (x:xs) = myLast xs
That's all I could come up with.
When I looked a the solutions, I found some stuff I did not understand at all.
For instance, this definition works:
myLast = foldr1 (const id)
What the heck?
Well, let's take this one at a time. Before we look at foldr1, let's look at a similar function called, scanr1. scanr1 takes two parameters and what it does will show by example. Here's one example of scanr1:
scanr1 (+) [1,2,3,4]
will show:
[10,9,7,4]
It starts from the right side, at the end, it moves the 4 as-is. Then it adds 3 to it, yielding 7, and that becomes the next entry. Then it adds 2 to 7, which yields 9. And finally, adding 1 to 9 yields 10. Similarly scanl1 will do it in the opposite order, from left-to right:
scanl1 (+) [1,2,3,4]
will show:
[1,3,6,10]
i.e, [1, 1+2, 1+2+3, 1+2+3+4]
Here's another example of scanr1:
scanr1 (+) [1,22,333,4444]
will show:
[4800,4799,4777,4444]
Okay, now that we know what scanr1 is, what is foldr1? foldr1 is just like scanr1, but it only returns the final number. So for list [1,22,333,4444] and function (+), it will return 4800. For list [1,2,3,4] with (+) it will return 10
So far, we've only been using the (+) operator with scanr1 and foldr1. We can really use any function that take two parameters of some type and return a value of the same type. Using a minus sign:
foldr1 (-) [1,2,3,4]
will return:
-2
A multiplication sign:
foldr1 (*) [1,2,3,4]
will return:
24
Let's park foldr1 for a second and remind ourselves that the definition we're trying to figure out:
myLast = foldr1 (const id)
We know that foldr1 is supposed to take two parameters, a function and a list, but here, foldr1 is taking only a single parameter, (const id). The list is not specified because it's passed as the parameter to myLast. So we can conclude that (const id) is the function, like (+), that transforms a value, like (+), taking two parameters and returning one. Let's try it:
(const id) 5 6
shows:
6
Or
(const id) 99 104
shows:
104
It looks like (const id) takes two parameters and always returns the second one. Let's try it with scanr1:
scanr1 (const id) [1,2,3,4]
shows:
[4,4,4,4]
Why? Let's walk through what scanr1 does. It takes the last argument, 4, and moves it as it. Then it applies the function on 3 and 4. Passing parameters 3 and 4 to (const id) we know will return 4, so 4 is placed in the 3rd position, and so on.
And we know foldr1 simply returns the final accumulated value, which in this case would be 4.
That's why:
myLast = foldr1 (const id)
works.
Before we're done with this example, let's see if we can understand what the heck (const id) is
First of all, what is id? id is a function that takes one argument and returns that argument. So:
id 5
shows:
5
and
id 98
shows
98
That's all it does. And what is const? const takes one argument and returns a function. The function that it returns also takes an argument, but that argument is always ignored. Instead the original value that was passed into const is return. In this example:
hours = const 40
hours becomes a function which always returns 40, no matter what you pass it:
hours 5
show:
40
and
hours "hello"
show:
40
However, hours does require you to pass in a parameter. It just ignores it.
So, if:
hours = const 40
creates a function that always returns 40, and assigns it to hours, then
zzz = const id
creates a function that always returns id, and assigns it to zzz.
So it follows that:
zzz 7 8
will do this: zzz takes the first parameter and ignores it, it then returns id. id takes the next parameter, 8, and returns it. The end result of zzz is that it takes two parameters, ignores the first one and returns the second one. Recall that zzz is the same as (const id), thus
(const id) 7 8
eats 7 and returns 8.
There's another solution that I didn't think of:
myLast = foldr1 (flip const)
Which looks very similar to the one of the other solutions:
myLast = foldr1 (const id)
so (flip const) seems to have the same behavior as (const id). Let's try it:
(flip const) 33 44
produces:
44
What does flip do? It looks like flip is a way to call a two argument function but with the parameters swapped. For instance, if we were to call the (-) function with 100 2:
(-) 100 2
we'd get:
98
But we were to pass the parameters in reverse order, we'd get -98. Or, we can pass the parameters in the same order as above, and still get -98, as long as we call (-) through flip:
(flip (-)) 100 2
produces:
-98
So it follows that:
myLast = foldr1 (flip const)
would be a solution.
Another solution I didn't think of that looks like the above is:
myLast = foldl1 (curry snd)
I know what snd is; it returns the second value of a pair (two item tuple). That is:
snd (6,3)
returns
3
So what is:
(curry snd)
First of all, not surprisingly:
(curry snd) 99 33
returns:
33
But how? It's hard to understand the curry function since everything in Haskell is already curried. Let's look that the uncurry function first. Let's say we have a two argument function such as (+). A two argument function, in Haskell, is a function that takes one argument that returns another one argument function. All functions in Haskell are one argument functions. Uncurrying (+) produces a function that takes a single argument for the argument, in the form of a tuple. That is, if you call (+) this way:
(+) 34 100
normally, you would call the uncurried version as:
(uncurry (+)) (34, 100)
Assume you have an uncurried version of (+) called uadd:
uadd = uncurry (+)
such that you can call it like so:
uadd (34, 100)
Then you could produced a curried version of uadd using the curry function:
let add = curry uadd
which can be called the normal way:
add 34 100
or
34 `add` 100
So that's what the curry function does. It takes a function that takes two parameters as a single argument as a pair, and converts it into a function that takes two arguments.
Recall that snd is called as:
snd (6,3)
a function that takes an argument as a pair. It's a function that's uncurried. So it stands to reason that currying snd will produce a version that takes the values as two arguments:
let csnd = curry snd
such that:
csnd 6 3
producing:
3
And thus:
(curry snd)
being another form that can be used with foldl1 to define myLast:
myLast = foldl1 (curry snd)
That's it.