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 |
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 Left
s, 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.