module DSL where
import Control.Monad
import Control.Applicative (pure, (<$>), (<*>))
import Data.Monoid
import Data.List(sortBy, sort)
import Test.QuickCheck -- not needed for the exam question
-- Exam question code:
empty :: Doc
char :: Char -> Doc
text :: String -> Doc
line :: Doc -- newline
(<>) :: Doc -> Doc -> Doc -- append
union :: Doc -> Doc -> Doc -- a choice of two variants only differing in layout
prettys :: Doc -> [String]
d1 = union (text "x<-m") (text "x <- m")
d2 = union (text "do {" <> d1 <> char ';' <> text "f x}")
(text "do " <> d1 <> line <> text " f x" )
-- Task (a): Implementation
data Doc = Empty | Char Char | Text String | Line
| Concat Doc Doc | Union Doc Doc
deriving (Show, Eq)
empty = Empty
char = Char
text = Text
line = Line
(<>) = Concat
union = Union
-- Note that lists form a Monad
prettys (Empty) = return ""
prettys (Char c) = return (c:[])
prettys (Text s) = return s
prettys (Line) = return "\n"
prettys (Concat x y) = sortBy cmp $ liftM2 (++) (prettys x) (prettys y)
prettys (Union x y) = mergeOn width (prettys x) (prettys y)
cmp :: String -> String -> Ordering
cmp xs ys = compare (width xs) (width ys)
type Width = Int
width :: String -> Int
width "" = 0
width s = maximum $ map length $ lines s
mergeOn :: Ord a => (t -> a) -> [t] -> [t] -> [t]
mergeOn f [] ys = ys
mergeOn f xs [] = xs
mergeOn f (x:xs) (y:ys) | f x <= f y = x : mergeOn f xs (y:ys)
| otherwise = y : mergeOn f (x:xs) ys
-- Task (b): Discussion
{-
Is your implementation deep or shallow?
This implementation is deep. The constructors are trivial and the run
function |prettys| is doing all of the work.
Are you using any monads (explain)?
The run function is using the list monad as a way of simulating a
non-deterministic choice of several alternatives.
Would some of the API operations fit the |Monoid| type class
(explain)?
It seems natural to have (Doc, empty, (<>)) as a Monoid. We just have
to be careful with what equality means - for the above implementation
with "derived" Eq the laws don't hold.
One way around it is to use "smart" constructors instead - the
smartapp below solves the first two monoid laws, but not the third.
Another way is to compare the semantics. Then we don't need any smart
constructors.
Note that (Doc, empty, union) is not a Monoid although union is
associative. This is because empty is an empty document, not an empty
list of documents. Also, union is not really mathematical union, but
cartesian product.
-}
---------
-- The rest is support code - not part of the expected exam answer.
instance Monoid Doc where
mempty = empty
mappend = smartapp
smartapp Empty m = m
smartapp m Empty = m
smartapp m n = m <> n
law1' (==) z (+) m = (z + m) == m
law2' (==) z (+) m = (m + z) == m
law3' (==) (+) m1 m2 m3 = ((m1 + m2) + m3) == (m1 + (m2 + m3))
law1 e = law1' e mempty mappend
law2 e = law2' e mempty mappend
law3 :: Monoid t2 => (t2 -> t2 -> Bool) -> t2 -> t2 -> t2 -> Bool
law3 e = law3' e mappend
synEq :: Doc -> Doc -> Bool
synEq = (==) -- derived, syntactic equality
semEq :: Doc -> Doc -> Bool
semEq x y = prettys x `bagEq` prettys y
bagEq x y = sort x == sort y
-- The third monoid law does not hold for list equality.
-- This is because the sortBy only sorts down to length, not in more detail.
instance Arbitrary Doc where
arbitrary = sized arbitraryDoc
shrink = shrinkDoc
arbitraryDoc :: Int -> Gen Doc
arbitraryDoc n = oneof $ basecase ++ if n == 0 then [] else recurse
where basecase = [pure empty, char <$> arbitrary, text <$> arbitrary, pure line]
recurse = [ (<>) <$> arbitrary <*> arbitrary
, union <$> arbitrary <*> arbitrary
]
shrinkDoc Empty = []
shrinkDoc (Char 'x') = [Empty]
shrinkDoc (Char c) = [Empty, Char 'x']
shrinkDoc (Text s) = Empty : map Char s -- ++ map Text (shrink s)
shrinkDoc Line = [Empty]
shrinkDoc (Concat x y) = x : y : [Concat x' y | x' <- shrink x]
++ [Concat x y'| y' <- shrink y]
shrinkDoc (Union x y) = x : y : [Union x' y | x' <- shrink x]
++ [Union x y'| y' <- shrink y]
main = do quickCheck (law1 semEq)
quickCheck (law2 semEq)
quickCheck (law3 semEq)
quickCheck (law1 synEq)
quickCheck (law2 synEq)
quickCheck (expectFailure $ law3 synEq)
quickCheck (expectFailure $ law1' semEq empty union)
quickCheck (expectFailure $ law2' semEq empty union)
quickCheck (law3' semEq union)
{-
With list equality instead of bad equality, test3 is a simple test
case that fails.
*** Failed! Falsifiable (after 4 tests and 9 shrinks):
-}
test3 = let d1 = Union Empty Empty
d2 = Union (Char 'x') Empty
d3 = Union (Char 'y') Empty
lhs = prettys $ (d1<>d2)<>d3
rhs = prettys $ d1<>(d2<>d3)
in (law3 semEq d1 d2 d3, lhs, rhs)