Ex 8 Solutions

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 5

Prof. Dr.

Ralf Hinze TU Kaiserslautern


M.Sc. Sebastian Schloßer
Fachbereich Informatik
AG Programmiersprachen

Functional Programming: Exercise 8


Sheet published: Monday, June 27th
Exercise session: Thursday, June 30th, 12:00

Exercise 8.1. Please take part in the lecture evaluation. If you have not received
an email from the SCI, go to https://vlu.cs.uni-kl.de/teilnahme/ and enter your
*@*.uni-kl.de email address.

1
Exercise 8.2. Using the free theorem for that type, show that any function

comp :: (b → c) → ((a → b) → (a → c))

must actually be the composition function ◦.

We replace each type variable by a (not yet specified) relation: R for a, S for b and T
for c. The free theorem is:

(comp, comp) ∈ (S → T ) → ((R → S ) → (R → T ))

We now repeatedly plug in the first definition from slide 404:

⇐⇒∀f1 f2 . (f1 , f2 ) ∈ S → T ⇒ (comp f1 , comp f2 ) ∈ (R → S ) → (R → T )


⇐⇒∀f1 f2 . (∀x y . (x , y) ∈ S ⇒ (f1 x , f2 y) ∈ T )
⇒ (∀g1 g2 . (g1 , g2 ) ∈ R → S ⇒ (comp f1 g1 , comp f2 g2 ) ∈ R → T )
⇐⇒∀f1 f2 . (∀x y . (x , y) ∈ S ⇒ (f1 x , f2 y) ∈ T )
⇒ (∀g1 g2 . (∀x y . (x , y) ∈ R ⇒ (g1 x , g2 y) ∈ S )
⇒ (∀a b . (a, b) ∈ R ⇒ (comp f1 g1 a, comp f2 g2 b) ∈ T ))
Up to now, the transformation was automatic, this is the actual free part of our theorem.
We now use a logical equivalence: A → (B → C) ⇐⇒ (A ∧ B) → C

⇐⇒∀f1 f2 g1 g2 a b . (∀x y . (x , y) ∈ S ⇒ (f1 x , f2 y) ∈ T )


∧ (∀x y . (x , y) ∈ R ⇒ (g1 x , g2 y) ∈ S )
∧ (a, b) ∈ R
⇒ ((comp f1 g1 a, comp f2 g2 b) ∈ T )
Since the free theorem holds for all relations R, S , T , we can define them as we want.
We chose R := {(p, q)}, S := {(g1 p, g2 q)} and T := {(f1 (g1 p), f2 (g2 q))}. This is a
non-automatic concious decision contributing to our proof.

=⇒∀f1 f2 g1 g2 a b . (∀x y . x = g1 p ∧ y = g2 q ⇒ f1 x = f1 (g1 p) ∧ f2 y = f2 (g2 q))


∧ (∀x y . x = p ∧ y = q ⇒ g1 x = g1 p ∧ g2 y = g2 q)
∧a =p∧b =q
⇒ comp f1 g1 a = f1 (g1 p) ∧ comp f2 g2 b = f2 (g2 q)
By equational reasoning, we can derive:

=⇒∀f1 f2 g1 g2 . comp f1 g1 p = f1 (g1 p) ∧ comp f2 g2 q = f2 (g2 q)


Therefore, comp f g x = f (g x ) must hold which is exactly the definition of the
composition function ◦.

2
Exercise 8.3 (Skeleton: Evaluator.hs). In the lecture1 , we have developped an evaluator
for the following expression data type:

data Expr
= Lit Integer — a literal
| Expr :+: Expr — addition
| Expr :∗: Expr — multiplication
| Today — some global constant
| Expr :?: Expr — choice

The base implementation

evaluateA :: (Applicative f ) ⇒ Expr → f Integer

supports only expressions without effects (literals, addition and multiplication).


The implementation

evaluateE :: Expr → (Integer → Integer )

is an exact copy of evaluateA, but additionally supports the global today constant.
The implementation

evaluateN :: Expr → [Integer ]

is an exact copy of evaluateA as well, and comes with additional support for choice.
None of those implementations supports expressions that contain the global today con-
stant and choice at the same time, e.g. Today :?: Lit 42.
We want to implement a function

evaluateEN :: Expr → (Integer → [Integer ])

that supports both the global today constant and choice. The implementation should be
in applicative style such that we can simply copy the first three cases from the evaluateA
implementation. evaluateE uses the Applicative instance for (→) r and evaluateN the one
for [ ], both are predefined in Haskell’s Prelude. We will combine both container types to
a combined one:

type Box r a = r → [a ]

Our Box type synonym combines both effects: The result relies on a global state r and
is nondeterministic ([a ]). Since it is not possible to provide type class instances for type
synonyms, we need to use newtype (or data) to create an actual new type for which we
can instantiate the type class:

newtype Box r a = Box {apply :: r → [a ]}

1
See in Olat Material/lecture 2021-06-16/solution/InterpreterA.lhs

3
a) Implement instance Functor (Box r ) and instance Applicative (Box r ). We need
Functor since it is a requirement for Applicative.

First have a look at the type signatures we need to implement:

fmap :: (a → b) → Box r a → Box r b


pure :: a → Box r a
h∗i :: Box r (a → b) → Box r a → Box r b

For a moment, let us assume Box was a type synonym (instead of newtype):

fmap :: (a → b) → (r → [a ]) → (r → [b ])
pure :: a → (r → [a ])
h∗i :: (r → [a → b ]) → (r → [a ]) → (r → [b ])

But this is not the actual type, since now we are missing the Box constructors.
A working implementation relying on the definition of Box is:

instance Functor (Box r ) where


fmap f (Box g) = Box (\x → map f (g x ))
instance Applicative (Box r ) where
pure x = Box (\ → [x ])
Box f h∗i Box g = Box (\x → f x h∗i g x )

But we can also generalize the definition making use of the fact that there are Functor
and Applicative instances for (→) r and [ ]. For (→) r , Haskell predefines fmap = (◦)
and for [ ] the predefined Functor instance is fmap = map.

instance Functor (Box r ) where


fmap f (Box g) = Box (fmap (fmap f ) g)
instance Applicative (Box r ) where
pure x = Box (pure (pure x ))
Box f h∗i Box g = Box (fmap (h∗i) f h∗i g)

When considering this general definition, you might wonder why this is not prede-
fined. The answer is: It is actually predefined, you just need to import it:

import Data.Functor .Compose

provides

newtype Compose f g a = Compose {getCompose :: f (g a)}

together with

instance (Functor f , Functor g) ⇒ Functor (Compose f g)


instance (Applicative f , Applicative g) ⇒ Applicative (Compose f g)

The instances are implemented exactly as shown above. Instead of our custom type
Box r a we could also use the type Compose ((→) r ) [ ] a and do not need to
implement the Functor and Applicative instances.

4
b) Implement evaluateEN :: Expr → (Integer → [Integer ]). Note that the return type is
Integer → [Integer ], which is almost Box Integer Integer , but not actually the same
since it is a separate type instead of a type synonym. We do not want that the user of
evaluateEN has to deal with the Box layer.
Implement an internal helper function of type Expr → Box Integer Integer that relies
on the Applicative instance for Box r . In evaluateEN you then can call your helper
function and afterwards just get rid of the Box using apply :: Box r a → (r → [a ]).

evaluateEN :: Expr → (Integer → [Integer ])


evaluateEN e i = apply (evaluateEN 0 e) i where
evaluateEN 0 :: Expr → Box Integer Integer
evaluateEN 0 (Lit i ) = pure i
evaluateEN (e1 :+: e2 ) = pure (+) h∗i evaluateEN 0 e1 h∗i evaluateEN 0 e2
0

evaluateEN 0 (e1 :∗: e2 ) = pure (∗) h∗i evaluateEN 0 e1 h∗i evaluateEN 0 e2


evaluateEN 0 (Today) = Box (\i → [i ])
0
evaluateEN (e1 :?: e2 ) =
Box (\i → apply (evaluateEN 0 e1 ) i ++ apply (evaluateEN 0 e2 ) i )

Alternative solution using the predefined type Compose ((→) r ) [ ] a instead of our
custom type Box r a:

evaluateEN2 :: Expr → (Integer → [Integer ])


evaluateEN2 e = getCompose (evaluateEN 0 e) where
evaluateEN 0 :: Expr → Compose ((→) Integer ) [ ] Integer
evaluateEN 0 (Lit i ) = pure i
evaluateEN 0 (e1 :+: e2 ) = pure (+) h∗i evaluateEN 0 e1 h∗i evaluateEN 0 e2
evaluateEN 0 (e1 :∗: e2 ) = pure (∗) h∗i evaluateEN 0 e1 h∗i evaluateEN 0 e2
evaluateEN 0 (Today) = Compose (\i → [i ])
0
evaluateEN (e1 :?: e2 ) =
Compose (\i → getCompose (evaluateEN 0 e1 ) i ++ getCompose (evaluateEN 0 e2 ) i )

You might also like