I was asked to write the most simple definition of the result of s k k
, where
s = \ f g x -> f x (g x)
s :: (t1 -> t2 -> t3) -> (t1 -> t2) -> t1 -> t3
k = \ x y -> x
k :: p1 -> p2 -> p1
Writing :t s k k
in the terminal gives: t -> t
.
I think s
takes two functions - f
and g
. where f
takes t1
and t2
as inputs and returns t3
. function g
takes t1
as input and returns t2
, and x
is t1
. Why is t3
the return type?
I think I understand function k
, where you take x
and y
as inputs and the type of x
is output. Is there a common approach when it comes to understanding how these two functions will work together forming s k k
.
I also tried going step by step typing :t s k
, which gives (t3 -> t2) -> t3 -> t3
, however, that didn't help me too much :) Can someone explain this to someone new to Haskell?
Ans. 2 The different types of functions are as follows: many to one function, one to one function, onto function, one and onto function, constant function, the identity function, quadratic function, polynomial function, modulus function, rational function, signum function, greatest integer function and so on.
A function is a relation between a set of inputs and a set of permissible outputs with the property that each input is related to exactly one output. Let A & B be any two non-empty sets, mapping from A to B will be a function only when every element in set A has one end only one image in set B.
The types of functions can be broadly classified into four types. Based on Element: One to one Function, many to one function, onto function, one to one and onto function, into function.
A function is a process or a relation that associates each element x of a set X, the domain of the function, to a single element y of another set Y (possibly the same set), the codomain of the function.
Types of Functions In terms of relations, we can define the types of functions as: One to one function or Injective function: A function f: P → Q is said to be one to one if for each element of P there is a distinct element of Q. Many to one function: A function which maps two or more elements of P to the same element of set Q.
These include known derivatives, known integrals, and the ability to use software to manipulate the functions. Although this is indeed labeled as a “G-Function”, it isn’t a “known” one. All that’s needed is to rewrite the expression in the brackets so that the function becomes a “named function.”
Specifying a function. Given a function , by definition, to each element of the domain of the function , there is a unique element associated to it, the value () of at . There are several ways to specify or describe how is related to (), both explicitly and implicitly. Sometimes, a ...
If fun :: a1 -> b
and arg :: a2
, then the application fun arg :: b
and a1 ~ a2
(a1
is equal to a2
).
s k k
is the same as (s k) k
because function application associates to the left. So we have an application s k
:
s :: (t1 -> t2 -> t3) -> (t1 -> t2) -> t1 -> t3
k :: (p1 -> p2 -> p1)
s k :: (t1 -> t2) -> t1 -> t3
p1 ~ t1
p2 ~ t2
p1 ~ t3
Note that p1 ~ t1
and p1 ~ t3
, therefore t1 ~ t3
:
s k :: (t1 -> t2) -> t1 -> t1
In the application (s k) k
, a similar thing happens; I’ll rename the type variables with a prime '
to indicate that this is a different instantiation of k
’s type:
(s k) :: (t1 -> t2 ) -> t1 -> t1
k :: (p1' -> p2' -> p1')
(s k) k :: t1 -> t1
t1 ~ p1'
t2 ~ p2' -> p1'
Notice that when t1 -> t2
is unified with p1' -> p2' -> p1'
, which is equal to p1' -> (p2' -> p1')
because function types are right-associative, t2
is equated with the function type p2' -> p1'
.
As a result, the type of the whole expression is t1 -> t1
, or forall t1. t1 -> t1
with ExplicitForAll
. Even without knowing the term here, there is only one function (which is total) of the type forall a. a -> a
, and that is id
. Indeed s k k
is just a roundabout way of implementing the identity function, because for any X
we give it, it returns X
:
s k k X
(\ f g x -> f x (g x)) k k X
(\ g x -> k x (g x)) k X
(\ x -> k x (k x)) X
k X (k X)
(\ x y -> x) X (k X)
(\ y -> X) (k X)
X
The first k
selects X
, and the second application of k
is ignored. That’s why neither of the type variables from the second use of k
(p1'
and p2'
) appear in the resulting type.
Well we know from their definitions that
s k k x = k x (k x) = x
-- s = \f g x -> f x (g x)
-- s k k x = k x (k x)
-- k = \ x y -> x
-- k x y = x
-- k x (k x) = x
so that
s k k = \ x -> x
and thus its type is
a -> a
GHCi concurs (as you've already noted):
> :t s k k
s k k :: t3 -> t3
As to the types in s
you ask about,
s :: (t1 -> t2 -> t3) -> (t1 -> t2) -> t1 -> t3
s = \ f g x -> f x (g x)
s f g x = f x (g x)
--
-- x :: t1
-- g :: t1 -> t2
-- g x :: t2
-- x :: t1
-- f :: t1 -> t2 -> t3
-- f x (g x) :: t3
just follow from the most basic rule of type inference in application,
x :: A x :: a
f :: A -> B f :: b -> c
------------------- --------------
f x :: B f x :: c , a ~ b
This is analogous to Modus Ponens in logic, "if we have A, and A implies B, then B holds as well".
If you wanted to get this from the types of s
and k
a.o.t. their definitions, a similar kind of diagram works for that as well:
s :: (a -> b -> c) -> (a -> b ) -> a -> c
k :: x -> y -> x
------- a~x, b~y, c~x => c~x~a -------------------
s k :: (a -> b ) -> a -> a
k :: x2 -> y2 -> x2
---------------------- a~x2, b~(y2 -> x2) ---------
s k k :: a -> a
(you can see the type you asked about, s k :: (a->b)->a->a
, there). Here you can see the type unifications done "step by step" but the two unifications could well have been done together,
s :: (a -> b -> c) -> (a -> b ) -> a -> c
k :: x -> y -> x
k :: x2 -> y2 -> x2
------- a~x, b~y, c~x => c~x~a -------------------
---------------------- a~x2, b~(y2 -> x2) ---------
s k k :: a -> a
The end result is of course the same. Whether we read the above diagram at once or in stages, it's the same diagram. Currying is nice, and it works. And since it works, when you've already applied a thing to two other things you needn't concern yourself with the partial application in stages. Except maybe as a mental exercise.
There's nothing much to say about all these interim types really. You just write them one under the other, being careful to rename the variables consistently at different invocations to avoid capture, noting the resulting equivalences and using them to simplify the resulting types.
Last thing to notice is that s k s
and s k (s k)
etc. could also be used for the same purpose. Almost, just like ($)
is almost the same as id
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With