Полиморфная функция Haskell, использующая либо лево-право - PullRequest
0 голосов
/ 28 апреля 2018

Я новичок в Хаскеле. У меня есть типы:

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

И я хочу создать функцию, в которой я буду использовать эту функцию для типов 2 EvalResult и получу EvalResult соответственно. Если я получу 2 типа значений, я хочу использовать для них функцию (например, sum / sub), а если я получу EvalError, я хочу вернуть EvalError.

Что я сделал:

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)

Ошибка:

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    | 

1 Ответ

0 голосов
/ 28 апреля 2018

Проблема в том, что вы определяете тип 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.

...