Tentamen AFP
Swierstra
Monads and QuickCheck (25 points)
Alternative definitions for Monad operations.
Instead of defining a monad by
bind (
>>=)and
return, we can also define it in
terms of
map (hereafter called
mmap to avoid confusion with
map from the
Prelude),
join, and
return.
mmap :: Monad m => (a -> b) -> m a -> m b
join :: Monad m => m (m a) -> m a
- Define
mmap and join in terms of bind (>>=) and return, and
- define
bind in terms of mmap, join, and return.
Snoc -lists as Monads
A
Snoc -list is an alternative representation of a (normal) list: a non-empty
Snoc -list consists of
the last element of the list, and the rest.
data Snoc a = Nil | Snoc a :< a deriving (Show, Eq)
- Define
listToSnoc, such that listToSnoc [1..5] returns ((((Nil :< 1) :< 2) :< 3) :< 4) :< 5. Use higher-order functions if possible.
<++
- Define the operator
(<++) :: Snoc a -> Snoc a -> Snoc a for concatenating two Snoc -lists.
Snoc is a Monad
- Make
Snoc an instance of the Monad type class such that the three monad laws are satisfied by giving definitions for the member functions (>>=) and return.
The function test
- Give the type and the result of the function
test.
test = do x <- listToSnoc [1..5]
return (x*x)
Monad laws
- Prove that the first two monad laws hold for a |Snoc|-list. Use equational reasoning.
return x >>= f is equivalent to f x (left-identity)
m >>= return is equivalent to m (right-identity)
Quickcheck
- Give a
QuickCheck property to check that also the third monad law holds.
(m >>= f) >>= g is equivalent to m >>= (\x -> f x >>= g) (associativity)
- Indicate precisely how this property can be validated by the
QuickCheck system, and make sure that QuickCheck is able to generate random Snoc -lists (with an appropriate distribution of test cases). Do not forget to give the type signature as this is required by the =QuickCheck system.
De-Bruijn numbering (40 points)
Alfa conversion
When we substitute lambda-terms when doing beta-reduction without taking proper precautions we may get name clashes.
- Give as an example how things can go wrong if we do not use alfa-conversion (i.e. systematically rename lambda-bound names).
De Bruijn numbering
The Dutch logician De Bruijn proposed a representation of lambda-terms in which each variable is replaced by a number which tells
how many lambda-hops away its binding occurrence is. So the lambda expression
\ x -> \y -> y is represented by
\_ -> \_ -> 0 and
\ x -> \y -> x is represented by
\_ -> \_ -> 1, etc. Note that the names which were introduced originally have disappeared. The identifiers have been replaced by a number telling how many static levels upwards from the used position the corresponding parameter binding can be found. We give the data types:
data Lam = LVar String | LApp Lam Lam | Lambda String Lam
data DBLam = DBVar Int | DBApp Lam Lam | DBLamda Lam
- Write a function
compile :: Lam -> DBLam which maps the first to the second.
- Show with a few examples that your compiler works as expected.
Evaluation
- Write a function
normDB :: DBLam -> DBLam, which normalises a DBLam to a normal form, if it exists. Make sure that you keep your numbering scheme correct!
runST (10 points)
- Explain, in your own words, why the type of
runST makes sure that no unsafe access is given to the internals of the maintained state. Why is it not a problem if references to this internal state escape.
Idiomatic (25 points)
We define the class
Idiomatic as follows:
data Stop = Stop
stop = Stop
start :: forall a g. (Idiomatic f g) => f -> g
start f = idiomatic (pure f)
class Idiomatic f g | g -> f where
idiomatic :: Parser f -> g
instance Idiomatic x (Stop -> Parser x) where
idiomatic ix Stop = ix
instance Idiomatic f g => Idiomatic (a -> f) (Parser a -> g) where
idiomatic isf is = idiomatic (isf <*> is)
so we can omit the sequencing
<*> operators from our parsers. Note that we have left out the
i parameter, since we have specialised things here for parsers.
An example of a somewhat extended and more specialised approach can be found
here. The underlying ideas were explained in the exercise of the paper
Applicative programming with effects, and the solution in the code above is hinted at (at the bottom of page 4).
How does this work?
- Explain in your own wording the working of this code using e.g. the definition of the function
eval at the bottom op this same page. Which instances are being passed. Why are the functional dependencies important?
Extending the collection of instances
- What instance should be aded to the code on the page Idioms, such that the example there could have been written as:
run (iI (+) '(' one "+" one ')' Ii) "(2+3)" in a context where one :: Int, i.e. by replacing the pNatural by a integer value?
--
DoaitseSwierstra - 14 Mar 2011