So I was taking a test about Haskell and one question said:
let the function be
lolo g x = ys
where ys = [x] ++ filter (curry g x) ys
then determine the type of the function called lolo. The options are:
a) (a,b) -> Bool -> b -> [(a,b)]
b) (b -> b -> b) -> Bool -> [b]
c) ((b,b) -> Bool) -> b -> [b]
d) (a -> b -> Bool) -> b -> [c]
Can somebody please explain which one it is and why? I'm really confused about this one... things I do not understand are:
1) the curry function can only be applied to functions right? not datatypes that may be tuples? then you can infer that g is a function in this context? what if g and x are both functions? is it possible to use curry with nth arguments? I've only seen curry used with 1 argument.
2) the other thing I don't understand very much is the recursion in the definition of ys. so ys is defined by ys, but I don't see the base case in this scenario. Will it ever end? maybe it's the filter function that makes the recursion end.
3) also in curry gx = curry (gx) right? (this is a question about precedence in application of functions)
Thanks a lot
1) The first argument to curry
has to be a function, it is what is known as a higher order function , it takes a function and returns a new one. While its type is printed out in GHCi as
curry :: ((a, b) -> c) -> a -> b -> c
It is more clearly represented (IMO) as
curry :: ((a, b) -> c) -> (a -> b -> c)
Which makes it more obvious that it takes a function and returns a new function. Technically, you could say that curry
takes 3 arguments, one of type (a, b) -> c
, one of type a
, and one of type b
. It just takes a function that normally accepts a tuple of arguments and converts it into a function that takes 2 arguments.
2) The computation for ys
will never end, don't bother trying to call length
on it, you'll just run the computation forever. This isn't a problem, though, you can work with infinite lists and non-terminating lists just fine (non-terminating being a list where it takes forever to compute the next element, not just one that has infinite elements). You can still use functions like take
and drop
on it, though.
3) Does curry gx == curry (gx)
? No! When you see an expression like abcde
, all of b
, c
, d
, and e
are arguments to a
. If you instead saw abc (de)
, then e
is applied to d
, and that result is applied to abc
. Consider filter even [1..10]
, this is certainly not the same as filter (even [1..10])
, since it wouldn't even compile! ( even :: Integral a => a -> Bool
).
When solving this sort of problem, first look at what functions are used in the expression that you already know the types of:
(++) :: [a] -> [a] -> [a]
filter :: (b -> Bool) -> [b] -> [b]
curry :: ((c, d) -> e) -> c -> d -> e
I've used different type variables in each so that there will be less confusion when trying to line up the types. You can get these types by loading up GHCi, then typing
> :type (++)
(++) :: [a] -> [a] -> [a]
> -- Or just use :t
> :t filter
filter :: (a -> Bool) -> [a] -> [a]
> :t curry
curry :: ((a, b) -> c) -> a -> b -> c
As you can see, I've changed filter
to use b
instead of a
, and curry
to use c
, d
, and e
. This doesn't change the meaning any more than fx = x + 1
versus fy = y + 1
, it'll just make it easier to talk about.
Now that we've broken down our function into its subcomponents, we can work from the "top" down. By top, I mean the last function that gets called, namely (++)
. You can picture this function by a tree like
(++)
/ \
[x] filter
/ \
curry ys
/ \
g x
So we can clearly see that (++)
is at the top. Using that, we can infer that [x]
has the type [a]
, which means that x ~ a
(the tilde is the type equality symbol) and consequently ys ~ [a]
, since ys = [x] ++ something
. Now that we know the type of x
, we can start filling out the rest of the expression. Next, we work down to filter (curry gx) ys
. Since it is the second argument to (++)
, we can infer that this subexpression also has the type [a]
. If we look at the type of filter
:
filter :: (b -> Bool) -> [b] -> [b]
The final result is a list of type [b]
. Since it's being applied to [x] ++
, we can infer that filter (curry gx) ys :: [a]
. This means that [b] ~ [a] => b ~ a
. For reference, this makes filter
's type
filter :: (a -> Bool) -> [a] -> [a]
This now places a constraint on curry gx
, it must fit into filter
's first argument which has the type a -> Bool
. Looking at curry
's type again:
curry :: ((c, d) -> e) -> c -> d -> e
This means that e ~ Bool
, and d ~ a
. If we plug those back in
curry :: ((c, a) -> Bool) -> c -> a -> Bool
Ignoring g
for now, we look at the type of x
, which we figured out is a
. Since x
is the second argument to curry
, that means that x
matches with the argument of type c
, implying that c ~ a
. Substituting this into what we just computed we get
curry :: ((a, a) -> Bool) -> a -> a -> Bool
With
curry g x :: a -> Bool
filter (curry g x) :: [a] -> [a]
filter (curry g x) ys :: [a]
[x] ++ filter (curry g x) ys :: [a]
From this we can directly infer that lolo
's type signature ends with [a]
, so
lolo :: ??? -> [a]
I'll leave you to do the remaining few steps to figure out what ???
is.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.