Reputation: 2100
I am trying to understand Haskell type variables for functions. I wrote a function:
applyTwice f x = f(f x)
I tried to understand the type variables for this function, so I did a :t applyTwice
. This is how Haskell interprets the types:
applyTwice :: (t -> t) -> t -> t
Then I created another function:
applyOnce f x = f x
This time for :t
Haskell returns
applyOnce :: (t1 -> t) -> t1 -> t
My questions are
How do we read/understand what these functions take and return?
This is for applyTwice
. If they say anything left side of the ->
is what the function takes and right hand side is what it returns then should it not be
applyTwice :: ((t -> t) -> t) -> t
? (t -> t)
for (f x)
and ((t -> t) -> t)
for f (f x)
and the return type being t
.
applyOnce
. Why is the function's type interpreted as applyOnce :: (t1 -> t) -> t1 -> t
? because we only take a function and return it's value. Should it not be applyOnce :: (t1 -> t) -> t1
?As a beginner in Haskell I would like any advice in this.
Upvotes: 2
Views: 426
Reputation: 1578
Let's translate Haskell to English.
applyTwice :: (t -> t) -> t -> t
means that applyTwice accepts a function from a t
to a t
and a value t
and returns a t
. Indeed, if we look at the definition, we see that it applies a function f
to a value x
and feeds the result into f
once more. This implies - as indicated by the type signature - that f
and f x
must have the same type t
(since f
only accepts a t
as an argument).
In contrast, applyOnce :: (t1 -> t) -> t1 -> t
allows for f x
and x
to have different types (t
and t1
respectively), since this function doesn't feed the output of f
(of type t
) back into f
.
I think the confusion stems from the fact that you fail to distinguish between f
(the function) and f x
(the result of the function).
Lastly, the type you suggest for applyOnce
(namely (t1 -> t) -> t
) implies (nay affirms) that you can get the result of a function without providing an argument; remember that (t1 -> t)
is the input type (a function) and t
is the output type. How do you calculate f(x)
without an x
?
Upvotes: 1
Reputation: 21757
We have:
applyTwice f x = f(f x)
This would make sense only if the types of x
and f x
were equivalent (else how would you be able to pass f x
to a function that takes x
?). Therefore, you could say that applyTwice
takes 2 input arguments:
Now this input could be of any type, say t1. But you also have to be able to apply the function to this input, and so the type of the input must also be t. Put together, we get the signature:
applyTwice :: (t -> t) -> t -> t
Remember that only the last term is the return type, and all others are types of the inputs.
Now consider:
applyOnce f x = f x
Simplistically, all it says about the function f is that it should be able to accept an input x of any type. There is nothing about what type the function should return except that applyOnce
should also return that same type. Hence, we end up with the signature you see:
applyOnce :: (t1 -> t) -> t1 -> t
where t1 could be any type and t could be any type (same or different from t1), but requiring that the return type of f
matches the return type of applyOnce
, represented by t here.
TL;DR: Your function signature for a function with n inputs will always have (n+1) terms, and the last term will be the return type of the function.
However, you should note that this is not actually how things work under the hood. All functions in Haskell actually take one argument i.e. they are curried. You can read more about this here.
Upvotes: 4
Reputation: 7350
What arguments a function takes and what a function returns is quite a tricky question in Haskell, because of special stuff like partial application.
The map
function looks like it takes an f :: a -> b
, the transformation function and a list xs :: [a]
and returns a list of ys :: [b]
with each element of xs
transformed per f
. But map
can also be seen as a function which takes a simple function f
and returns another function, which acts upon lists and returns lists.
What I do to help me reason about arguments is first separate all types delimited by top level ->
s. For example map
:
map :: (a -> b) -> [a] -> [b]
-- gives us
-- (a -> b) and [a] and [b]
The rightmost type is what is traditionally called the "return type" when map is called with all its arguments provided. The other types are the arguments I have to use, for example in the function definition.
Take this with a grain of salt, as you will see plenty of examples of definitions of functions which do not use all arguments explicitly to define the function.
Coming to your second question, you apparently mix up the arguments and the function body. Taking applyTwice
:
applyTwice f x = f (f x)
f
and x
are the arguments to applyTwice
and f (f x)
is the function body. Now f
must be a function, since it is applied to an argument x
. x
's type can be anything, let's call it t
. Now f must go from t
, the type of x
to something else, call it s
. But now f x
, which has type s
is an argument to f
again, so the compiler can deduce that s ~ t
. Now f (f x)
is again of type s
, which must be the same as t
, which means you end up with:
applyTwice :: (t -> t) -> t -> t
Analogously you can try this for applyOnce
and see that its type is (t -> s) -> t -> s
, which is just $
from the Prelude
.
Upvotes: 1