问题
After completing the first problem ("Find the last element of a list") in the 99 questions exercises, I wanted to see how my solution compared with others and I found this solution.
myLast' = foldr1 (const id)
This documentation seems to show that foldr1
takes two arguments, the first being a function and the second being a list. But this definition only appears to take a function as an argument. Is there an implicit definition of the arguments passed like this?
myLast' xs = foldr1 (const id) xs
I have looked up the definitions of foldr1
, const
, and id
, but I'm having a hard time understanding how those three work together to return the last item in the list.
回答1:
You're exactly right. In Haskell, a function that takes two arguments can actually be treated as a function that takes one argument and returns another function that takes an argument; this is known as currying. Note that the function signature for foldr1
is:
(a -> a -> a) -> [a] -> a
While we often think of it as "a function that takes a function and a list as arguments and returns a value", it's really "a function that takes a function as an argument and returns a function that takes a list and returns a value".
回答2:
As mipadi explains, the function is being curried. That explains how the list argument gets there, but perhaps doesn't explain how the actual fold works.
The const id
bit is a little trixy. foldr1
is expecting to get something that has the type a -> a -> a
. The definitions of these functions are
const :: x -> y -> x
const x y = x
id :: x -> x
id x = x
So, mangling that all together, we have
const id =
\ y -> id =
\ y -> \ x -> x =
\ y x -> x
In order words, const id
does the same thing as flip const
; it's a 2-argument function that throws the first argument away, and returns the second one. It's not terribly obvious that this is so; IMHO, flip const
would be clearer.
foldr1
will call this function with the old value as the first argument, and the next list element as the second argument. This function always returns the next list element. The final output from foldr
is the last output from the function, which will be the last element of the entire list.
回答3:
Yes, your intuition about functions not needing all their arguments is spot on. And when one function takes another function as a parameter to return another function as a result, it is called "currying." See: http://en.wikipedia.org/wiki/Currying.
(As a side note, this was actually discovered (or rediscovered) by Haskell Curry, which is how our Haskell got its name.)
If the idea of currying is still taking time to sink in, this may help:
There are actually two functions defined in Prelude
called curry
and uncurry
. They carry the following types:
Prelude> :t curry
curry :: ((a, b) -> c) -> a -> b -> c
Prelude> :t uncurry
uncurry :: (a -> b -> c) -> (a, b) -> c
uncurry
takes a 2 argument curried function (or a function that takes a function of one argument that returns a function of one argument,) and produces an uncurrried function, or a function that takes all of the arguments all at once (as a tuple.)
curry
, as you may imply by the name and by its type, goes the other way, so that it takes a curried function (a function that takes all the arguments at once) and produces a function that takes one argument and returns a function that takes the other argument.
Most programming languages work by default in a uncurried fashion, you provide all the arguments and get a result, while Haskell is curried by default.
Now for an example of uncurry
, we can take a simple function, (+)
:
Prelude> :t (+)
(+) :: Num a => a -> a -> a
Prelude> (+) 1 2
3
Prelude> :t (+)
(+) :: Num a => a -> a -> a
Prelude> :t uncurry (+)
uncurry (+) :: Num c => (c, c) -> c
Prelude> uncurry (+) (1,2)
3
And, we could also do this with const
if we want to:
Prelude> :t const
const :: a -> b -> a
Prelude> const 1 2
1
Prelude> :t uncurry const
uncurry const :: (c, b) -> c
Prelude> uncurry const (1,2)
1
But having an uncurried version of const
is just not very useful, because there is no point in having a function that takes two arguments and always returns the first one if you have to specify all the arguments up front.
const
is useful precisely because it is curried and can be given in a place where a function is needed that takes two arguments and simply returns the first one.
Like in foldr1
, for example:
Prelude> :t foldr1
foldr1 :: (a -> a -> a) -> [a] -> a
Where the first argument is a curried function of two arguments.
And since the return value of a function can be a function, it can also be so with const
:
Prelude> :t const id
const id :: b -> a -> a
const id
simply takes an argument of any type b
and returns the id
function.
So if we can apply const id
step by step:
Prelude> :t const id 1
const id 1 :: a -> a
const id 1
or const id anyOtherValueHere
just returns the id function.
Which can be used liked so:
Prelude> :t const id "Giraffe" 100
const id "Giraffe" 100 :: Num a => a
Prelude> const id "Giraffe" 100
100
Prelude> :t const id (\a b -> undefined) 100
const id (\a b -> undefined) 100 :: Num a => a
Prelude> const id (\a b -> undefined) 100
100
So, const
really ignores its second argument. Directly above, is just like applying id
to 100.
So, foldr1 (const id)
simply takes a list and keeps applying id
to each set of two elements and keeping the second one (because const id
returns the value of id
on the second argument passed in) until we have the last element.
回答4:
You are correct that your two examples can be replaced by one another. This can be thought of as algebraic cancelation just like in algebra class.
f x y = g x y
I can cancel the y
on each side:
f x = g x
now I can cancel the x on each side:
f = g
Take a look a the wiki page for foldr vs foldl and foldl' to learn a little more about foldr.
To get an idea of how myLast' works we can do some algebraic suptitution.
myLast' [1, 2, 3]
== foldr1 (const id) [1, 2, 3]
Now we should use the definition of foldr1 in-particular: foldr1 f (x:xs) = f x (foldr1 f xs)
== (const id) 1 (foldr1 (const id) [2, 3])
const id
turns out to have the type sig b -> a -> a
which is the same as flip const
and it turns out it acts the same as well which is a function that ignores its first argument and returns the second. example: (const id) 1 3 == 3
* see below for a little more *
== foldr1 (const id) [2, 3]
== foldr1 (const id) [3]
== 3
That last step may not be what you expected but if you check the definition of foldr1
it contains:
foldr1 _ [x] = x
Which pattern matches when their is only one element in the list and returns it.
* How does const id
work?
const
returns its first argument and ignores it second so
const id 3 == id
const id 3 4 == id 4 == 4
来源:https://stackoverflow.com/questions/18598076/how-is-this-function-equivalent-to-getting-the-last-item-in-a-list