Search code examples
haskelleitherpolymorphic-functions

Haskell polymorphic function Using Either Left Right


I'm new to Haskell. I have the types:

type Variable = String
type Value = Float
type EvalError = [Variable]
type EvalResult = Either EvalError Value

And I want to create a function that I'll use a function to use it on 2 EvalResult types, and get a EvalResult accordingly. If I get 2 Value types, I want to use the function on them (sum/sub for example), and If I'll get EvalError I want to return the EvalError.

What I did:

evalResultOp :: (a -> b -> c) -> EvalResult a -> EvalResult b -> EvalResult c
evalResultOp f (Left a) (Left b) = Left (a ++ b)
evalResultOp f (Left a) (Right b) = Left a
evalResultOp f (Right a) (Left b) = Left b
evalResultOp f (Right a) (Right b) = Right (f a b)

The error:

hs3.hs:46:34: error:
    • Expecting one fewer arguments to ‘EvalResult’
      Expected kind ‘* -> *’, but ‘EvalResult’ has kind ‘*’
    • In the type signature:
        evalResultOp :: (a -> b -> c)
                        -> EvalResult a -> EvalResult b -> EvalResult c
   |
46 | evalResultOp :: (a -> b -> c) -> EvalResult a -> EvalResult b -> EvalResult c    |                                  ^^^^^^^^^^^^

    hs3.hs:46:50: error:
        • Expecting one fewer arguments to ‘EvalResult’
          Expected kind ‘* -> *’, but ‘EvalResult’ has kind ‘*’
        • In the type signature:
            evalResultOp :: (a -> b -> c)
                            -> EvalResult a -> EvalResult b -> EvalResult c
       |
    46 | evalResultOp :: (a -> b -> c) -> EvalResult a -> EvalResult b -> EvalResult c    |                                                  ^^^^^^^^^^^^

    hs3.hs:46:66: error:
        • Expecting one fewer arguments to ‘EvalResult’
          Expected kind ‘* -> *’, but ‘EvalResult’ has kind ‘*’
        • In the type signature:
            evalResultOp :: (a -> b -> c)
                            -> EvalResult a -> EvalResult b -> EvalResult c
       |
    46 | evalResultOp :: (a -> b -> c) -> EvalResult a -> EvalResult b -> EvalResult c    | 

Solution

  • The problem is that you define your EvalResult type as:

    type EvalResult = Either EvalError Value
    

    since Either is a type constructor, that takes two types, this means that you have constructed a type now (without any type parameters). If you write a function with type signature (a -> b -> c) -> EvalResult a -> EvalResult b -> EvalResult c, then Haskell thus will eventually have to construct types EvalResult a ~ Either EvalError Value a, and since Either only takes two type parameters, this makes no sense.

    My guess is that you want to define

    type EvalResult a = Either EvalError a

    or shorter:

    type EvalResult = Either EvalError

    Now EvakResult thus acts like a type constructor that can take one type parameter, and then the function indeed works over types.

    We can make the implementation more compact by writing:

    import Control.Monad(liftM2)
    
    evalResultOp :: (a -> b -> c) -> EvalResult a -> EvalResult b -> EvalResult c
    evalResultOp f (Left a) (Left b) = Left (a ++ b)
    evalResultOp f x y = liftM2 f x y
    

    liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c is a function that works over monadic types m. Either a is a monadic type, it is defined as:

    instance Monad (Either a) where
        return = Right
        (>>=) (Right x) f = f x
        (>>=) (Left l) _ = Left l
    

    liftM2 is implemented as:

    liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
    liftM2 f xm ym = do
        x <- mx
        y <- my
        return (f x y)
    

    which is syntactical sugar for:

    liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
    liftM2 f xm ym = mx >>= (\x -> my >>= \y -> return (f x y))
    

    So basically we evaluate mx >>= (...) by checking if mx is a Right, if it is a Left, we return the content of the Left. Since we already handled the case with two Lefts, that case is no longer possible, so we know that the second EvalResult is a Right, in that case we thus return the first Left. In case mx is a Right x, we now inspect my >>= (..) and check the state of my. If my is a Left, we again return that Left, otherwise, we return the return (f x y), since return for an Either a monad, is actually Right, we thus wrap the content of f x y in the Right data constructor.