I'm making a toy language where I want to be able to do arithmetic operations on both Floats and Integers with the same constructors, like so (minimal example provided below).
data Expr a where
NumberConst :: Number a -> Expr (Number a)
Mul :: Expr (Number n) -> Expr (Number n) -> Expr (Number n)
data family Number :: * -> *
data instance Number Integer = NumInt Integer deriving (Eq, Ord, Show)
data instance Number Float = NumFloat Float deriving (Eq, Ord, Show)
class (Eq a, Ord a, Num a) => SquanchyNum a where
toDigit :: Number a -> a
divide :: Number a -> Number a -> a
mul :: Number a -> Number a -> a
sub :: Number a -> Number a -> a
add :: Number a -> Number a -> a
instance SquanchyNum Integer where
toDigit (NumInt x) = x
divide (NumInt x) (NumInt y) = x `div` y
mul (NumInt x) (NumInt y) = x * y
sub (NumInt x) (NumInt y) = x - y
add (NumInt x) (NumInt y) = x + y
instance SquanchyNum Float where
toDigit (NumFloat x) = x
divide (NumFloat x) (NumFloat y) = x / y
mul (NumFloat x) (NumFloat y) = x * y
sub (NumFloat x) (NumFloat y) = x - y
add (NumFloat x) (NumFloat y) = x + y
eval :: Expr a -> a
eval (NumberConst a) = a
eval (Mul a b) = (eval a) `mul` (eval b) <-- this line makes the problem visible
in ghci I get the following error
*Main> :r
[2 of 3] Compiling Lib ( /home/michael/git/brokensquanchy/src/Lib.hs,
interpreted )
/home/michael/git/brokensquanchy/src/Lib.hs:45:18: error:
• Occurs check: cannot construct the infinite type: n ~ Number n
Expected type: a
Actual type: n
• In the expression: (eval a) `mul` (eval b)
In an equation for ‘eval’: eval (Mul a b) = (eval a) `mul` (eval b)
• Relevant bindings include
b :: Expr (Number n)
(bound at /home/michael/git/brokensquanchy/src/Lib.hs:45:13)
a :: Expr (Number n)
(bound at /home/michael/git/brokensquanchy/src/Lib.hs:45:11)
|
45 | eval (Mul a b) = (eval a) `mul` (eval b)
| ^^^^^^^^^^^^^^^^^^^^^^^
Failed, one module loaded.
however when I try the following in ghci by hand it works
(eval (NumberConst (NumInt 6))) `mul` (eval (NumberConst (NumInt 2)))
12
This leads me to believe I am not providing enough detail in my type signature for eval. What's the solution?
Edit - I don't think I'm either approaching this problem correctly or explaining it clearly.
Addressing luqui's question
You are already marking the type with the
a-- you can deduce from the type which constructor it will be -- the constructor does nothing for you. Why not banishNumber afrom this source file, just replacing it withaeverywhere?
Okay, let's do that with NumberConst
data Expr a where
NumberConst :: a -> Expr a
Now I can do this
:t NumberConst ("bad code")
NumberConst ("bad code") :: Expr [Char]
I don't want to be able to do that.
So, I need a way to (1) enforce that my arithmetic constructors can only have an Int or a Float (2) avoid duplicate constructors, one for both Int and Float. I should be able to use arithmetic constructors on either Int or Float. (3) be able to know if it's an Int or a Float so that I can handle division.
A data family plus type class is the way I thought to be able to accomplish that. If that's not necessary, I'm all for a simpler approach. I just don't know what that is.