function :: (t2 -> t1) -> (t1 -> t) -> t2 -> t
function f1 f2 x = f2 (f1 x )
function1 :: (t -> t) -> t -> t
function1 f1 x = f1 (f1 x)
function11 :: (t1 -> t1) -> t -> t1 -> t1
function11 f1 f2 x = f1 (f1 x)
function3 :: (t1 -> t1) -> t -> t1 -> t1
function3 f1 f2 x = f1 (f1 (f1 x))
function4 :: (t1 -> t1) -> t -> t1 -> t1
function4 f1 f2 x = f1 (f1 (f1 x))
Can anyone explain the type signatures for the the first two and also why the type signature for the last 3 are the same? I'm reading about high order functions and what they are hasn't gotten into my head.
Can anyone explain the type signatures for the the first two?
Let's give some slightly more human-readable names for the type variables.
function :: (in -> tmp) -> (tmp -> out) -> in -> out
function f1 f2 x = f2 (f1 x )
This type says: if you give me a function that munges inputs into temporary values, and a function that munges temporary values into outputs, I will give you back a function which munges inputs into outputs. You could imagine drawing a "data processing pipeline", where we have a bunch of objects of various types, and operations that turn values of one type into values of another type. For example, here's how I might visually draw your function
:
in ----- f1 -----> tmp ----- f2 -----> out
\ ^
\ /
`------------- function f1 f2 --------------'
The intended interpretation here is that you could imagine taking two routes in this pipeline -- either you first use operation f1
, then use operation f2
; or else you can use the operation function f1 f2
to do the whole thing in one go.
function1 :: (t -> t) -> t -> t
function1 f1 x = f1 (f1 x)
Here, function1
takes a function for transforming t
s (into other t
s), and gives back a (potentially different) function for transforming t
s (again, into other t
s). Again, drawing the data-processing pipeline for this, you might get something like this:
t ----- f1 -----> t ----- f1 -----> t
\ ^
\ /
`------------- function1 f1 --------------'
Why is the type signature for the last 3 the same?
Let's quickly refresh our memory on the definitions of the last three functions:
function2 f1 f2 x = f1 (f1 x)
function3 f1 f2 x = f1 (f1 (f1 x))
function4 f1 f2 x = f1 (f1 (f1 x))
Okay, so the first interesting thing about function2
, function3
, and function4
is that they take three arguments, f1
, f2
, and x
, but each of them ignores f2
entirely -- it is never mentioned on the right-hand side of the =
for any of them. Secondly, function3
and function4
are defined in exactly the same way, so it's no surprise they can be given the same type.
Taking these two things into account, we can narrow the question somewhat, so that we are asking why these two functions have the type signature:
function2' f x = f (f x)
function3' f x = f (f (f x))
Specifically, their shared type signature is this:
(t -> t) -> (t -> t)
As we discussed above for function1
, this means that they take a transformation on t
s, and they also happen to produce a transformation on t
s. Thinking again in terms of data processing pipelines, it should now be pretty clear why they have the same type: it doesn't matter whether we apply our function f
two times or three inside the pipe; the type just ain't changing:
.------------------------ function3' f ------------------------.
/ \
/ v
t ----- f -----> t ----- f -----> t ----- f -----> t
\ ^
\ /
`------------ function2' f -------------'
Assuming f
is a function that starts at a node labeled t
and ends at a node labeled t
, then no matter how many times we apply f
before stopping, we're always coming back to a function that starts at a node labeled t
and ends at a node labeled t
.