Professional Documents
Culture Documents
data Bit = O | I
The newtype definition has almost1 the same effect as the following definitions:
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
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:
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!
• x4 • x1 •
• x3 • • x2 •
x1 x2 x1 x2 x3 x4 x3 x4
foldm :: (a → a → a) → a → [a ] → a