Home > OS >  My toy language's evaluator won't type check
My toy language's evaluator won't type check

Time:01-15

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?

CodePudding user response:

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.

CodePudding user response:

Those are not the same a. The a inside the Equals constructor is different from the a that is generic parameter of eval.

Imagine this:

eval (Equals (SquanchyVar "foo") (SquanchyVar "bar"))

Here, the a inside the Equals constructor is Text (because that's the type of SquanchyVar), but the a that is generic parameter of eval is Bool (because that is the type of Equals). So they're different.

But a deeper problem is that your eval can only ever return one type. Because its result type a is also mentioned in the monad type ExceptT Text (State (Store a)) under which eval works, it means that in order to be in the same monad, any nested calls to eval must always return the same type as the outer eval that calls them.

But wait! Does your monad actually need to mention a? Let's see where it's actually used. Looks like the only use site is in extractValue. And look: it doesn't actually extract value of any type, it's only ever expected to work with Text.

So that's the solution: just make your monad ExceptT Text (State (Store Text)) instead of ExceptT Text (State (Store a)).

eval :: Expr a -> ExceptT Text (State (Store Text)) a

...

equals :: (Eq n) => Expr n -> Expr n -> ExceptT Text (State (Store Text)) Bool

...

extractValue :: Text -> ExceptT Text (State (Store Text)) Text
  •  Tags:  
  • Related