Проблема в том, что вы определяете тип EvalResult
как:
type EvalResult = Either EvalError Value
, поскольку Either
является конструктором типа , который принимает два типа, это означает, что вы уже создали тип (без каких-либо параметров типа). Если вы пишете функцию с сигнатурой типа (a -> b -> c) -> EvalResult a -> EvalResult b -> EvalResult c
, то Haskell, таким образом, в конечном итоге придется создавать типы EvalResult a ~ Either EvalError Value a
, а поскольку Either
принимает только два параметра типа, это не имеет смысла.
Я предполагаю, что вы хотите определить
type EvalResult <b>a</b> = Either EvalError <b>a</b>
или короче:
type EvalResult = Either EvalError
Теперь EvakResult
, таким образом, действует как конструктор типов, который может принимать один параметр типа, и тогда функция действительно работает над типами.
Мы можем сделать реализацию более компактной, написав:
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
- это функция, которая работает с монадическими типами m
. Either a
является монадическим типом, он определяется как:
instance Monad (Either a) where
return = Right
(>>=) (Right x) f = f x
(>>=) (Left l) _ = Left l
liftM2
реализовано как:
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)
который является синтаксическим сахаром для:
liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
liftM2 f xm ym = mx >>= (\x -> my >>= \y -> return (f x y))
Таким образом, в основном мы оцениваем mx >>= (...)
, проверяя, является ли mx
значением Right
, если оно является Left
, мы возвращаем содержимое Left
. Поскольку мы уже обрабатывали случай с двумя Left
с, этот случай больше невозможен, поэтому мы знаем, что второй EvalResult
является Right
, в этом случае мы, таким образом, возвращаем первый Left
. В случае, если mx
является Right x
, мы теперь проверяем my >>= (..)
и проверяем состояние my
. Если my
является Left
, мы снова возвращаем Left
, в противном случае мы возвращаем return (f x y)
, поскольку return
для монады Either a
фактически Right
, поэтому мы переносим содержимое f x y
в конструкторе данных Right
.