Exercise 1: A Functor instance

Implement a Functor instance for both data types. You may have to add a Functor constraint on some of ComplicatedB’s type parameters.

Exercise 2: Rewriting monadic code

The following functions are written using the Monad combinators and/or do-notation, but not all of them use the full power of Monads. Some can be rewritten to use only Applicative or even only Functor laws.

Copy the following functions into your file and rewrite them to have only a Functor or Applicative constraint, if possible. Append a ' to the name of the rewritten function, so that you can test both in GHCi. As an example, I have done it for the first function.

If it is not possible, add a small note in a comment why you think it is not possible.

The rewritten functions should behave identical for any lawful instance of Monad.

Exercise 3: A parser monad

A great example for the convenience and usefulness of monads is parser combinators. In this exercise, you will build a small parser combinator library yourself. In the end, the following code will, for example, parse CSV files:

Try to understand the above code! The operator <* comes with the Applicative class. Read the documentation to see what it does.

Some of the code you will write will bear resemblence with the Supply monad code from last week.

Define the type Parser of kind * -> *:

dataParser a =P (…)

A value of type Parser a is a function that takes a string (the remaining input at this point) and returns either Nothing, if parsing failed, or otherwise returns a value of type a and the remaining input.

Also define

runParser ::Parser a -> …
runParser (P p) = p

to remove the P constructor. Use this rather than explicit pattern matches in the functions below, as otherwise some recursive definitions will loop, because pattern matching (and hence evaluation) happens too early.

Define the function

parse ::Parser a ->String->Maybe a

which is the main entry point to run a parser. It shall return successfully only if the parser consumed the whole input, i.e. if the function inside the Parser a returns a value of type a along with the empty string.

Implement a function

noParser ::Parser a

which represents the always failing parser.

You should have

parse noParser input ==Nothing

for all inputs input, even the empty string.

Implement a function

pureParser :: a ->Parser a

which represents the parser that consumes no input and returns its argument.

which applies the left parser to the input first to get the function. If it succeeds, it applies the right parser to the remaining input to get the argument, and returns the function applied to the argument, and the leftover input by the right argument.

Declare

instanceMonadParserwhere
return = pureParser
fa >>= k = …

which works quite similar to <*>.

Define the primitive parser

anyChar ::ParserChar

that fails if the input is empty, and takes one character off the input otherwise:

without breaking the abstraction introduced by the Parser data type, i.e. using only the combinators introduced above. You can use do-notation if you want.

Define the combinator

orElse ::Parser a ->Parser a ->Parser a

which tries the left parser. If it succeeds, it uses that, otherwise it runs the second parser on its input. This implements backtracking in a very naive way (so don’t expect this parser to have the best performance characteristics – there are highly optimized parser libraries out there).

Fun facts about many that you should ponder (not part of the homework):

The parser many p never fails.

The expression many (pure x) is not very useful. Do you see why?

What happens if you apply many to many?

Define the combinator

sepBy ::Parser a ->Parser () ->Parser [a]

so that p1 sepBy p2 applies the p1, then p2, then p1 and so on. Succeeds if the very first invocation of p1 fails, returning the empty string. Also succeeds if any invocation of p2 fails, returning the results of all p1 invocations as a list. Implement this again without breaking the abstraction, using the combinators above.

You should have

-- if xs is non-empty and does not end with '\n', then
parse (many (anyCharBut '\n') `sepBy` char '\n') xs =Just (lines xs)

Exercise 4: Parsing an INI file

The file consists of one or more sections. Each section starts with a section header, which is a line consisting of just the section title enclosed in square brackets. Section titles are identifiers.

The body of each section is a (possibly empty) list of declarations, which is an identifier, followed by any number of space character, followed by the = sign, followed by more spaces, followed by the value. The value is the remaining content of the line, and may contain any characters except for the newline character ‘’, which terminates the line.

The body of each section may contain empty lines and comment lines (lines starting with #). These are to be ignored.

An identifier consists of letters or digits (see isAlphaNum in Data.Char) and must be non-empty.

You might want to add further general combinators to your parser library, e.g. a variant many1 that works similar to many but fails if it cannot parse at least once, and maybe letterOrDigit :: Parser Char might be useful.