module serialize2start /* Definition for assignment 2 in AFP 2017 Pieter Koopman pieter@cs.ru.nl September 2017 Laurens Kuiper (s4467299) Camil Staps (s4498062) */ import StdEnv, StdMaybe /** * Review questions * * 1. A type is a set of values. In the case of UNIT, UNIT = {UNIT}. The set of * possible arguments to == on a type T is {(x,y) | x <- T, y <- T}, so in the * case of UNIT we have {(UNIT,UNIT)}. Hence, this alternative matches always. * The other suggestions are equivalent, as the pattern match is because of * this meaningless and therefore all alternatives in the question are * equivalent. * * 2. The name of the constructor is redundant information, since the same * information can also be derived (compile-time only, hence it has to be * stored) from the LEFT/RIGHT-path through the ADT tree. Hence, checking on * the constructor name is not needed. * * 3. * - [] = LEFT (CONS "_Nil" UNIT) * - Leaf = LEFT (CONS "Leaf" UNIT) * == has type a a -> Bool, so we cannot apply it to Leaf and [], which are not * of the same type. So no, this does not yield True (it does not yield * anything, as you won't be able to compile it). */ /** * The number of elements in an expression that will be placed on the outer * level w.r.t. parentheses when pretty-printing. * * For instance: * - "37" -> 1 * - "_Nil" -> 1 * - "_Cons 1 _Nil" -> 3 * - "(_Cons 1 _Nil)" -> 1 */ class outerElems a where outerElems :: a -> Int /** * An expression needs parentheses when it has more than one element on the * outer level. */ needsParens x :== outerElems x > 1 /** * In the default case, parentheses are placed around the whole expression, so * the number of elements on the outer level is 1. */ instance outerElems a where outerElems _ = 1 //* UNITs are not printed. instance outerElems UNIT where outerElems _ = 0 //* The arguments and the constructor instance outerElems (CONS a) | outerElems a where outerElems (CONS _ x) = 1 + outerElems x //* Both elements appear on the outer level instance outerElems (PAIR a b) | outerElems a & outerElems b where outerElems (PAIR x y) = outerElems x + outerElems y instance outerElems (EITHER a b) | outerElems a & outerElems b where outerElems (LEFT x) = outerElems x outerElems (RIGHT x) = outerElems x /** * We extended the definition with a restriction on outerElems. This extra * restriction is OK, since there is an instance of outerElems for every type * (and hence, this restriction does not exclude any type). */ class serialize a | outerElems a where write :: a [String] -> [String] read :: [String] -> Maybe (a,[String]) instance serialize Bool where write b c = [toString b:c] read ["True":r] = Just (True,r) read ["False":r] = Just (False,r) read _ = Nothing instance serialize Int where write i c = [toString i:c] read [s:r] # i = toInt s | s == toString i = Just (i,r) = Nothing read _ = Nothing // --- :: UNIT = UNIT :: EITHER a b = LEFT a | RIGHT b :: PAIR a b = PAIR a b :: CONS a = CONS String a // --- writeP :: a [String] -> [String] | serialize a writeP x s = if (needsParens x) ["(":write x [")":s]] (write x s) readP :: [String] -> Maybe (a, [String]) | serialize a readP ["(":r] = case read r of Just (x,[")":r]) -> Just (x,r) _ -> read ["(":r] readP r = case read r of Just (x,r) -> if (needsParens x) Nothing (Just (x,r)) _ -> Nothing instance serialize UNIT where write _ c = c read r = Just (UNIT,r) instance serialize (EITHER a b) | serialize a & serialize b where write (LEFT x) c = write x c write (RIGHT x) c = write x c read r = case read r of Just (x,r) -> Just (LEFT x, r) Nothing -> case read r of Just (x,r) -> Just (RIGHT x,r) Nothing -> Nothing // This goes wrong if the two type variables of EITHER are equal (and // hence read for LEFT is the same as read for RIGHT: we will always // return a LEFT. Therefore, given a type // // :: T = C1 Int | C2 Int // // it is not possible to write and read C2 (C1 will be read, since LEFT // has precedence). Like the example given as the answer to the // reflection questin below, we don't think this to be fixable in the // current setup. instance serialize (PAIR a b) | serialize a & serialize b where write (PAIR x y) c = writeP x [" ":writeP y c] read r = case readP r of Just (x,[" ":r]) -> case readP r of Nothing -> Nothing Just (y,r) -> Just (PAIR x y, r) _ -> Nothing instance serialize (CONS a) | serialize a where write e=:(CONS s x) c | needsParens e = ["(":s:" ":write x [")":c]] | otherwise = [s:c] read ["(":s:" ":r] = case read r of Just (x,[")":r]) -> Just (CONS s x,r) _ -> Nothing read [s:r] = case read r of Just (x,r) -> let e = CONS s x in if (needsParens e) Nothing (Just (e,r)) Nothing -> Nothing read _ = Nothing :: ListG a :== EITHER (CONS UNIT) (CONS (PAIR a [a])) fromList :: [a] -> ListG a fromList [] = LEFT (CONS "_Nil" UNIT) fromList [x:xs] = RIGHT (CONS "_Cons" (PAIR x xs)) toList :: (ListG a) -> [a] toList (LEFT _) = [] toList (RIGHT (CONS _ (PAIR x xs))) = [x:xs] instance serialize [a] | serialize a where write l c = write (fromList l) c read l = case read l of Just (g,r) -> Just (toList g,r) Nothing -> Nothing :: Bin a = Leaf | Bin (Bin a) a (Bin a) :: BinG a :== EITHER (CONS UNIT) (CONS (PAIR (Bin a) (PAIR a (Bin a)))) fromBin :: (Bin a) -> BinG a fromBin Leaf = LEFT (CONS "Leaf" UNIT) fromBin (Bin l x r) = RIGHT (CONS "Bin" (PAIR l (PAIR x r))) toBin :: (BinG a) -> Bin a toBin (LEFT _) = Leaf toBin (RIGHT (CONS _ (PAIR l (PAIR x r)))) = Bin l x r instance serialize (Bin a) | serialize a where write a c = write (fromBin a) c read l = case read l of Just (g,r) -> Just (toBin g,r) Nothing -> Nothing instance == (Bin a) | == a where // better use the generic approach (==) Leaf Leaf = True (==) (Bin l a r) (Bin k b s) = l == k && a == b && r == s (==) _ _ = False // --- /** * Reflection: * The following is parsed as Just ([], []), which shows that the fact that * there is no check on the name of the constructor can be exploited. * * In the actual Clean implementation, there is the construct `CONS of d` where * d :: GenericConsDescriptor. This way, the name, arity, etc. of the * constructor are available as part of the CONS type (even though this type * does not explicitly contain the information). It is essentially a very * restricted form of dependent typing, which makes it possible to distinguish * the _Nil-CONS and the _Cons-CONS at compile-time. * * This requires the `CONS of d` construct, so I don't think this exploit is * fixable in the current setup. It would of course be possible to redefine * toList and toBin to result in a Maybe and check on the constructor there. */ Start :: Maybe ([Int], [String]) Start = read ["_Cons"] Start = [ test True , test False , test 0 , test 123 , test -36 , test [42] , test [0..4] , test [[True],[]] , test (Bin Leaf True Leaf) , test [Bin (Bin Leaf [1] Leaf) [2] (Bin Leaf [3] (Bin Leaf [4,5] Leaf))] , test [Bin (Bin Leaf [1] Leaf) [2] (Bin Leaf [3] (Bin (Bin Leaf [4,5] Leaf) [6,7] (Bin Leaf [8,9] Leaf)))] ] test :: a -> ([String],[String]) | serialize, == a test a = (if (isJust r) (if (fst jr == a) (if (isEmpty (tl (snd jr))) ["Oke "] ["Fail: not all input is consumed! ":snd jr]) ["Fail: Wrong result ":write (fst jr) []]) ["Fail: read result is Nothing "] , ["write produces ": s] ) where s = write a ["\n"] r = read s jr = fromJust r