Haskell newbie here.
I wrote an evaluator for a minimal assembly-like language.
Now, I want to extend that language to support some syntactic sugar which,
This problem was named "the expression problem" by Phil Wadler, in his words:
The goal is to define a data type by cases, where one can add new cases to the data type and new functions over the data type, without recompiling existing code, and while retaining static type safety.
One solution to have extensible data type is to use type classes.
As an example let's assume we have a simple language for arithmetics:
data Expr = Add Expr Expr | Mult Expr Expr | Const Int
run (Const x) = x
run (Add exp1 exp2) = run exp1 + run exp2
run (Mult exp1 exp2) = run exp1 * run exp2
e.g.
ghci> run (Add (Mult (Const 1) (Const 3)) (Const 2))
5
If we wanted to implement it in an extensible way, we should switch to type classes:
class Expr a where
run :: a -> Int
data Const = Const Int
instance Expr Const where
run (Const x) = x
data Add a b = Add a b
instance (Expr a,Expr b) => Expr (Add a b) where
run (Add expr1 expr2) = run expr1 + run expr2
data Mult a b = Mult a b
instance (Expr a, Expr b) => Expr (Mult a b) where
run (Mult expr1 expr2) = run expr1 * run expr2
Now let's extend the language adding subtractions:
data Sub a b = Sub a b
instance (Expr a, Expr b) => Expr (Sub a b) where
run (Sub expr1 expr2) = run expr1 - run expr2
e.g.
ghci> run (Add (Sub (Const 1) (Const 4)) (Const 2))
-1
For more info on this approach, and in general on the expression problem, check Ralf Laemmel's videos 1 and 2 on Channel 9.
However, as noticed in the comments, this solution changes the semantics. For example lists of expressions are no longer legal:
[Add (Const 1) (Const 5), Const 6] -- does not typecheck
A more general solution using coproducts of type signatures is presented in the functional pearl "Data types a la carte". See also Wadler's comment on the paper.