I have to convert a Haskell type signature into a term. The type signature is :
f :: (a -> b -> c) -> (d -> b) -> (d -> a) -> d -> c
The correct resulting term is :
f g h j x = g (j x) (h x)
and here lies my problem as I understand it g
is a function which returns a function which returns c
and c
is function which returns a function d
which returns b
and b
is a function which returns itself which then returns itself again which then returns c
.
Correct me if i am wrong.
What I don't get is why is g
taking (j x)
as first argument and (h x)
as second argument. Shouldn't it be the other way around? Haskell is right associative and h
is the secound parameter given to the function f
and not j
.
g :: a -> b -> c
, h :: d -> b
, j :: d -> a
, and x :: d
are all independent arguments to f
; their order implies nothing about how we might end up using them in the definition of f
.
To start, we know that f
uses its arguments to return a value of type c
. But none of the arguments have a value of type c
; the only way to get a value of type c
is to use g
. But in order to use g
, you need arguments of type a
and type b
, and none of f
's arguments have those types. But we could use h
and j
to get them, if we had an argument of type d
to apply them to, and lo and behold, we do have a value of type d
: the argument x
!.
f g h j x = let aValue = j x
bValue = h x
cValue = g aValue bValue
in cValue
which can be flattened to the original answer of
f g h j x = g (j x) (h x)
If you want to think of the return value of f
as being d -> c
, rather than just c
, you can eliminate x
from the definition with some point-free trickery.
f g h j = g <$> j <*> h -- liftA2 g j h
You can even go a little further to remove h
and j
as arguments, but the result, though simple, is even more incomprehensible:
f = flip . liftA2
Moral of the story: sometimes point-free style abstracts away distracting details, other times it completely obscures the meaning of the function.