-- | 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.