Here's the code
{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts #-}
{-# LANGUAGE GADTs, TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables, OverloadedStrings #-}
module Lib where
import Control.Applicative
import Control.Monad.Except
import Control.Monad.State
import Data.Text
type Var = Text
type Store a = [(Var, Expr a)]
-- type EvalMonad a = ExceptT Text (State (Store a))
class (Num a, Eq a, Ord a) => Divisible a where
divide :: a -> a -> a
instance Divisible Int where divide = div
instance Divisible Float where divide = (/)
data Expr a where
BoolConst :: Bool -> Expr Bool
NumberConst :: (Divisible n) => n -> Expr n
SquanchyString :: Text -> Expr Text
SquanchyVar :: Text -> Expr Text
Not :: Expr Bool -> Expr Bool
Xor :: Expr Bool -> Expr Bool -> Expr Bool
Equals :: (Eq a) => Expr a -> Expr a -> Expr Bool
GreaterThan :: (Eq a) => Expr a -> Expr a -> Expr Bool
Add :: (Divisible n) => Expr n -> Expr n -> Expr n
eval :: Expr a -> ExceptT Text (State (Store a)) a
eval (BoolConst a) = return a
eval (NumberConst a) = return a
eval (SquanchyString s) = return s
eval (SquanchyVar v) = extractValue v
eval (Not b) = not <$> eval b
eval (Xor a b) = do
orRes :: Bool <- (||) <$> eval a <*> eval b
andRes :: Bool <- (&&) <$> eval a <*> eval b
let notRes = not andRes
return $ orRes && notRes
eval (Equals a b) = do -- This stanza is where the problem gets revealed
res :: Bool <- equals a b
return res
-- eval (GreaterThan a b) = (>) <$> eval a <*> eval b
eval (Add a b) = (+) <$> eval a <*> eval b
eval _ = undefined
equals :: (Eq n) => Expr n -> Expr n -> ExceptT Text (State (Store n)) Bool
equals a b = do
eOne <- eval a
eTwo <- eval b
return $ eOne == eTwo
extractValue :: Text -> ExceptT Text (State (Store a)) a
extractValue v = do
store :: Store a <- lift get
case (lookup v store) of
Just i -> eval i
Nothing -> throwError "doh"
Here's the error
• Could not deduce: a1 ~ Bool
from the context: a ~ Bool
bound by a pattern with constructor:
Equals :: forall a. Expr a -> Expr a -> Expr Bool,
in an equation for ‘eval’
at /home/michael/git/brokensquanchy/src/Lib.hs:65:7-16
‘a1’ is a rigid type variable bound by
a pattern with constructor:
Equals :: forall a. Expr a -> Expr a -> Expr Bool,
in an equation for ‘eval’
at /home/michael/git/brokensquanchy/src/Lib.hs:65:7-16
Expected type: ExceptT Text (State (Store a)) Bool
Actual type: ExceptT Text (State (Store a1)) Bool
• In a stmt of a 'do' block: res :: Bool <- equals a b
In the expression:
do res :: Bool <- equals a b
return res
In an equation for ‘eval’:
eval (Equals a b)
= do res :: Bool <- equals a b
return res
• Relevant bindings include
b :: Expr a1
(bound at /home/michael/git/brokensquanchy/src/Lib.hs:65:16)
a :: Expr a1
(bound at /home/michael/git/brokensquanchy/src/Lib.hs:65:14)
|
66 | res :: Bool <- equals a b
I'm hoping the answer is to include missing type information, but I'm not really sure. Any ideas?
Update: I improved my code, prompted by chepner's comment. But I still get the same error.
Why does ghc think a
and a1
are different? How can I convince it otherwise?
The type of your store is broken. In particular, the return type of eval
is:
ExceptT Text (State (Store a)) a
while the return type of equals
is:
ExceptT Text (State (Store n)) Bool
These types can only unify (as they must, for the case eval (Equals a b) = ...
) if a
and n
are both Bool
, i.e., only if you are comparing the equality of two booleans, and that's clearly not what you want.
More generally, the type of your store Store a
indirectly forces all expressions in your language to have the same type, even in programs that don't use any variables (because the a
is part of the monad's type, and that type needs to stay the same throughout your program), so you've accidentally written an evaluator that can either evaluate Bool
expressions or evaluate Text
expressions or evaluate numeric expressions, but can never handle expressions of different types in a single program.
In your current language, you only support Text
variables (via SquanchyVar
), so a quick way to fix this is to change every occurrence of Store a
or Store n
to Store Text
, and modify extractValue
to always return a Text
value. So, the following typechecks:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
module Lib where
import Control.Applicative
import Control.Monad.Except
import Control.Monad.State
import Data.Text
type Var = Text
type Store a = [(Var, Expr a)]
-- type EvalMonad a = ExceptT Text (State (Store a))
class (Num a, Eq a, Ord a) => Divisible a where
divide :: a -> a -> a
instance Divisible Int where divide = div
instance Divisible Float where divide = (/)
data Expr a where
BoolConst :: Bool -> Expr Bool
NumberConst :: (Divisible n) => n -> Expr n
SquanchyString :: Text -> Expr Text
SquanchyVar :: Text -> Expr Text
Not :: Expr Bool -> Expr Bool
And :: Expr Bool -> Expr Bool -> Expr Bool
Or :: Expr Bool -> Expr Bool -> Expr Bool
Xor :: Expr Bool -> Expr Bool -> Expr Bool
Equals :: (Eq a) => Expr a -> Expr a -> Expr Bool
GreaterThan :: (Eq a) => Expr a -> Expr a -> Expr Bool
LessThan :: (Eq a) => Expr a -> Expr a -> Expr Bool
Div :: (Divisible n) => Expr n -> Expr n -> Expr n
Mul :: (Divisible n) => Expr n -> Expr n -> Expr n
Sub :: (Divisible n) => Expr n -> Expr n -> Expr n
Add :: (Divisible n) => Expr n -> Expr n -> Expr n
eval :: Expr a -> ExceptT Text (State (Store Text)) a
eval (BoolConst a) = return a
eval (NumberConst a) = return a
eval (SquanchyString s) = return s
eval (SquanchyVar v) = extractValue v
eval (Not b) = not <$> eval b
eval (And a b) = (&&) <$> eval a <*> eval b
eval (Or a b) = (||) <$> eval a <*> eval b
eval (Xor a b) = do
orRes :: Bool <- (||) <$> eval a <*> eval b
andRes :: Bool <- (&&) <$> eval a <*> eval b
let notRes = not andRes
return $ orRes && notRes
eval (Equals a b) = do -- This stanza is where the problem gets revealed
res :: Bool <- equals a b
return res
-- eval (GreaterThan a b) = (>) <$> eval a <*> eval b
-- eval (LessThan a b) = (<) <$> eval a <*> eval b
eval (Div a b) = divide <$> eval a <*> eval b
eval (Mul a b) = (*) <$> eval a <*> eval b
eval (Add a b) = (+) <$> eval a <*> eval b
eval (Sub a b) = (-) <$> eval a <*> eval b
eval _ = undefined
equals :: (Eq n) => Expr n -> Expr n -> ExceptT Text (State (Store Text)) Bool
equals a b = do
eOne <- eval a
eTwo <- eval b
return $ eOne == eTwo
extractValue :: Text -> ExceptT Text (State (Store Text)) Text
extractValue v = do
store :: Store a <- lift get
case (lookup v store) of
Just i -> eval i
Nothing -> throwError "doh"
Fixing your language to support variables with multiple types is significantly more complicated.