You are on page 1of 3

Prof. Dr.

Ralf Hinze TU Kaiserslautern


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

Functional Programming: Exercise 4


Sheet published: Friday, May 27th
Exercise session: Thursday, June 2nd, 12:00

Exercise 4.1 (Skeleton: BinaryNumbers.hs). We want to represent natural numbers in


the binary system as lists of bits:

data Bit = O | I

newtype Binary = Bin {fromBinary :: [Bit ]}

The newtype definition has almost1 the same effect as the following definitions:

data Binary = Bin [Bit ]


fromBinary :: Binary → [Bit ]
fromBinary (Bin bits) = bits

You can use the data constructor Bin ::[Bit ] → Binary to convert a list of bits to a number
of type Binary and the function fromBinary :: Binary → [Bit ] for the inverse direction.
Our Binary numbers are represented with the least significant bit first, e.g. the binary
number Bin [O, I , I , O, I ] represents 0 · 20 + 1 · 21 + 1 · 22 + 0 · 23 + 1 · 24 = 22.
Provide an instance of the Eq type class2 for our Binary type such that two binary
numbers are equal if the represented number is equal, regardless of the concrete list.
For example, Bin [ ], Bin [O ] and Bin [O, O ] should be equal (they all represent the
number 0), as well as Bin [I ] and Bin [I , O ] (both represent the number 1).

1
There is just some difference regarding strictness which we do not worry about here.
See https://wiki.haskell.org/Newtype#The_messy_bits
2
That is the reason for using newtype. You cannot define type class instances for simple type synonyms,
the type must be declared with data or newtype.

1
Exercise 4.2 (Skeleton: Data.hs). When computers want to persist data (e.g. on a hard
drive), the data needs to be transformed into binary form. That process is also known
as serialization. We use the following type class for data that can be transformed to and
from lists of bits:

data Bit = O | I

class Data a where


encode :: (a, [Bit ]) → [Bit ]
decode :: [Bit ] → (a, [Bit ])

The methods satisfy the following properties: if c is the encoding of the value v , then

encode (v , x ) = c ++ x
decode (c ++ x ) = (v , x )

The function encode takes an accumulating argument that is appended to the encoding;
decode decodes a prefix of its input, returning the decoding alongside the unused suffix.
End users often use the following functions to avoid specifying the list parameter:

serialize :: (Data a) ⇒ a → [Bit ]


serialize a = encode (a, [ ])
deserialize :: (Data a) ⇒ [Bit ] → a
deserialize xs = let (a, [ ]) = decode xs in a
— crashes if the remaining list is non-empty

The expression deserialize [I , O ] has type (Data a) ⇒ a. Haskell tries to use the context
to find out what type a should be and therefore which decode implementation will be used.
The decision is left open as long as possible (lazy evaluation). But if there is not enough
context and Haskell is asked to evaluate and show the result, then some arbitrary choice
is made. Therefore, when using decode or deserialize in GHCi, always specify the type
you want to have: deserialize [I , O ] :: Maybe Bool .
Provide instances of the Data type class for the following types. Always make sure that
decode ◦ encode = id .

a) Bool
b) Bit
c) The unit type ()
d) Maybe a (assuming that there is a Data instance for a)
e) The list type [a ] (assuming that there is a Data instance for a)
f) The pair type (a, b) (assuming that there are Data instances for a and b)
g) Integer . There are some tricky pitfalls here. At the end, check that
deserialize (serialize [0, 123, −456, 789, 0]) :: [Integer ] is [0, 123, −456, 789, 0].

2
Exercise 4.3 (Skeleton: MapReduce.hs). In the lectures we have implemented reduce in
terms of a higher-order function: reduce = foldr (•) ε. Of course, this is a rather arbitrary
choice: reduce = foldl (•) ε works equally well. Since the operation is associative, it
does not matter how nested applications of ‘•’ are parenthesized. The overall result is
bound to be the same. However, there is possibly a big difference in running time. For
many applications, a balanced “expression tree” is actually preferable, for example in
the mergesort algorithm. In particular, a balanced tree can in principle be evaluated in
parallel!

foldl (•) ε foldm (•) ε foldr (•) ε


• •

• x4 • x1 •

• x3 • • x2 •

x1 x2 x1 x2 x3 x4 x3 x4

The goal is to define a function

foldm :: (a → a → a) → a → [a ] → a

that constructs and evaluates a balanced expression tree.


a) The types of foldl , foldm, and foldr are quite different. Why?
b) Implement foldm using a top-down approach: Split the input list into two halves, eval-
uate each half separately, and finally combine the results using the monoid operation
(divide and conquer ).
c) Implement foldm using a bottom-up approach: Traverse the input list combining two
adjacent elements e.g. [x1 , x2 , x3 , x4 ] becomes [x1 • x2 , x3 • x4 ]. Repeat the transfor-
mation until the list is a singleton list.

You might also like