I'm trying to implement a dynamically typed programming languages in Haskell which supports three data types, let's call them A
, B
and C
and just for illustration purposes I will let A = Integer
, B = [Integer]
and C = (Integer, Integer)
(but you can ignore the semantics of these types, that's not what I'm concerned about).
In order to use values of any type interchangeably in arithmetic expressions I have implemented an algebraic data type Value
:
data Value = A A
| B B
| C C
And because I want to be able to add and multiply values I have implemented the typeclass OP
:
class Op a where
add :: a -> a -> a
mul :: a -> a -> a
Now, I also want my types to implicitly convertible to each other (when two different types appear in an arithmetic expression), according to the following rules:
- If both types are
A
, no conversion takes place - If one of the types is
A
, the other is converted toA
- Otherwise, both types are converted to
B
To make this possible I have implemented another typeclass, ImplicitlyConvertible
:
class ImplicitlyConvertible a where
toA :: a -> A
toB :: a -> B
A complete example would then look like this:
{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
module Value where
type A = Integer
type B = [Integer]
type C = (Integer,Integer)
data Value = A A
| B B
| C C
class ImplicitlyConvertible a where
toA :: a -> A
toB :: a -> B
instance ImplicitlyConvertible A where
toA = id
toB = error "can't convert A to B"
instance ImplicitlyConvertible B where
toA = sum
toB = id
instance ImplicitlyConvertible C where
toA = sum
toB c = [fst c, snd c]
instance ImplicitlyConvertible Value where
toA v = case v of
A a -> toA a
B b -> toA b
C c -> toA c
toB v = case v of
A a -> toB a
B b -> toB b
C c -> toB c
class Op a where
add :: a -> a -> a
mul :: a -> a -> a
instance Op A where
add = (+)
mul = (*)
instance Op B where
add = zipWith (+)
mul = zipWith (*)
valueOp :: (Value -> Value -> Value) -> (Value -> Value -> Value)
valueOp op (A v) v' = op (A v) (A $ toA v')
valueOp op v (A v') = op (A $ toA v) (A v')
valueOp op v v' = op (B $ toB v) (B $ toB v')
instance Op Value where
add = valueOp add
mul = valueOp mul
I have three problems with this:
The fact that
toB
is not actually implemented forA
seems unclean. Even though it should never be called I would like to avoid having to implement it at all.instance ImplicitlyConvertible Value
is just a bunch of boilerplate code that I would like to get rid of.I'm not sure if my implementation of
instance Op Value
is sensible.
Am I maybe going about this the wrong way in the first place? How can I implement all of this more cleanly?