-- | Refactoring data types: after refactoring module RefactorDataPost where -- | Abstract syntax of arithmetic expressions. data Expr = Lit Int | Bin Op Expr Expr deriving (Eq,Show) -- | Arithmetic operations. data Op = Add | Sub | Mul deriving (Eq,Show) -- | Smart constructor for add. add :: Expr -> Expr -> Expr add = Bin Add -- | Smart constructor for sub. sub :: Expr -> Expr -> Expr sub = Bin Sub -- | Smart constructor for mul. mul :: Expr -> Expr -> Expr mul = Bin Mul e1, e2, e3 :: Expr e1 = sub (mul (Lit 3) (Lit 4)) (Lit 5) e2 = mul (add (Lit 4) (Lit 5)) (add (Lit 3) (Lit 2)) e3 = sub e2 (mul (Lit 5) e1) -- | Get the leftmost literal in a expression. leftLit :: Expr -> Int leftLit (Lit i) = i leftLit (Bin _ l _) = leftLit l -- | Get the rightmost literal in a expression. rightLit :: Expr -> Int rightLit (Lit i) = i rightLit (Bin _ _ r) = rightLit r -- | Get a list of all literals in an expression. allLits :: Expr -> [Int] allLits (Lit i) = [i] allLits (Bin _ l r) = allLits l ++ allLits r -- | Evaluate an expression. eval :: Expr -> Int eval (Lit i) = i eval (Bin Add l r) = eval l + eval r eval (Bin Sub l r) = eval l - eval r eval (Bin Mul l r) = eval l * eval r eval' :: Expr -> Int eval' (Lit i) = i eval' (Bin o l r) = op o (eval l) (eval r) where op Add = (+) op Sub = (-) op Mul = (*) -- Another kind of refactoring that is even more beneficial is to refactor a -- data type to make undesirable cases impossible to represent. For example, -- here's a standard List data type: -- -- data List a = Nil | Cons a (List a) -- -- If a certain kind of list should never be empty in your program, you could -- refactor this data type to the following, which enforces by construction -- that all lists contain at least one element. -- -- data NonEmptyList a = End a | Cons a (List a) -- -- This can simplify both the types and implementations of your functions -- considerably since you no longer need to check for and handle the -- undesirably cases (in this case, empty lists). -- -- For example, here are two functions that return the last element in a list -- that should never be empty. -- -- last :: List a -> Maybe a -- last Nil = Nothing -- last (Cons h Nil) = Just h -- last (Cons _ t) = last t -- -- last :: NonEmptyList a -> a -- last (End a) = a -- last (Cons _ t) = last t -- -- Code that uses the 'last' function gets the biggest benefit of this -- refactoring since it no longer needs to pattern match on the result to -- make sure it was not the 'Nothing' case, but can instead just use the -- result of 'last' directly.