Как понять сообщения компилятора Haskell - PullRequest
5 голосов
/ 21 апреля 2011

Добрый день.

Здесь приведен простой фрагмент кода "угадай число", содержащий одну ошибку, но компилятору очень сложно понять, что же не так:

import System.Random
import Control.Monad
import Control.Monad.Cont

main = do
  rn <- randomRIO (1,10) :: IO Int
  runContT (callCC $ \k -> forever $ do
    lift $ putStr "Your number:"
    n <- lift (fmap read getLine)
    when (n == rn) $ k
    lift $ putStrLn $ if (n > rn) 
                      then "Too much" 
                      else "Too little") (return)
  putStrLn $ "Good guess! " ++ (show rn)

GHC даетошибка:

> simple.hs:11:21:
>     Couldn't match expected type `()' against inferred type `m b'
>       Expected type: a -> ()
>       Inferred type: a -> m b
>     In the second argument of `($)', namely `k'
>     In a stmt of a 'do' expression: when (n == rn) $ k

И это меня очень смущает, говорит, что что-то ожидалось типа '()', но как определить, кто это "что-то"?Это "к"?кажется, не так.Это выглядит более разумно, если мы обмениваемся ожидаемым и предполагаемым, но как это выглядит сейчас, это очень запутанно.Мой вопрос: как определить причину и исправить эту ошибку?

Ответы [ 5 ]

10 голосов
/ 21 апреля 2011

Чтобы понять эти типы, полезно взглянуть на окружающие функции.

Ошибка упоминает переменную k, которая сначала появляется в выражении callCC $ \k -> forever .... Мы можем получить тип k, посмотрев на тип callCC:

callCC :: MonadCont m => ((a -> m b) -> m a) -> m a

Из этого видно, что k имеет тип a -> m b. Обратите внимание, что поскольку b не используется где-либо еще в этой функции, его тип не имеет значения и будет определяться контекстом, в котором используется функция.

k используется в выражении when после $ (которое на самом деле не нужно). Тип, когда это:

 when :: Monad m => Bool -> m () -> m ()

Обратите внимание, что второй аргумент ожидает m (), но вы передаете k, которое имеет тип a -> m b (так как b не имеет значения, оно может соответствовать (). Поэтому, очевидно, необходимо указать какой-то аргумент на k. Чтобы выяснить, что это, мы оглянемся на определение callCC. Этот аргумент является значением forever $ do ... в вашей программе.

Глядя на тип вечности:

forever :: Monad m => m a -> m b

Требуется одно монадическое вычисление m a, и в результате возвращается другое монадическое вычисление m b. Обратите внимание, что b не появляется в аргументах forever. Это означает, что тип определяется контекстом, в котором он вызывается (например, read "3" может иметь тип Double или Int в зависимости от выражения, в котором он находится). Это определяется runContT:

runContT :: ContT r m a -> (a -> m r) -> m r

Если вы сопоставите переменные типа из runContT, callCC и forever, вы заметите, что b в forever соответствует a в runContT. a используется во втором аргументе runContT, который в вашей программе равен return. return имеет тип a -> m a, поэтому тип a совпадает с r в вашей программе. r появляется на выходе m r.

Выражение runContT находится в контексте do, без каких-либо привязок (<-). Ваш код эквивалентен этому:

main = do
  rn <- randomRIO (1,10) :: IO Int
  runContT (callCC ....) (return) >> (putStrLn $ "Good guess! " ++ (show rn))

Тайна, наконец, будет раскрыта, если взглянуть на тип >>:

(>>) :: Monad m => m a -> m b -> m b

>> отбрасывает значение первого передаваемого ему монадического вычисления (которое было выражением runContT). Таким образом, значение, которое возвращает вычисление, на самом деле не имеет значения (обратите внимание, что a не появляется в результате функции >>). Если вы проследите за последствиями этого объяснения, вы поймете, что переменная, переданная в k, на самом деле не имеет значения! Если вы передадите ему что-нибудь, функция будет работать правильно:

import System.Random
import Control.Monad
import Control.Monad.Cont

main = do
  rn <- randomRIO (1,10) :: IO Int
  runContT (callCC $ \k -> forever $ do
    lift $ putStr "Your number:"
    n <- lift (fmap read getLine)
    when (n == rn) $ k (Just ("Seriously anything works here", 42, [42..]))
    lift $ putStrLn $ if (n > rn) 
                      then "Too much" 
                      else "Too little") (return)
  putStrLn $ "Good guess! " ++ (show rn)

Так что это был действительно сложный пример, и я понимаю, почему вы не следовали ему. Вы становитесь лучше с опытом, хотя. Кроме того, монада продолжения довольно продвинута и сложна для haskell.

9 голосов
/ 21 апреля 2011

Ключевые сообщения в сообщениях с предупреждениями GHC:

Тип у вас есть; типа это должно быть

Couldn't match expected type `()' against inferred type `m b'

так что у вас есть что-то типа (), в монадической настройке m b.

Какое выражение в ошибке

In the second argument of `($)', namely `k'

Значит, k имеет неправильный тип.

Номера строк

simple.hs:11:21

в строке 11.

5 голосов
/ 21 апреля 2011

Не зацикливайтесь на «ожидаемом» и «предполагаемом». Что не всегда очевидно и реже актуально; важно то, что средство проверки типов содержит противоречивую информацию о типе какого-либо термина. Наиболее распространенным случаем является то, что один тип ожидается контекстом (например, применяя функцию, которая принимает аргумент определенного типа), в то время как другой термин выводится для термина.

Теперь, за ошибку, которую вы получили:

Couldn't match expected type `()' against inferred type `m b'

Это означает, что конфликтующие типы - () и m b. Обратите внимание, что это не обязательно полные типы любого фактического выражения; только часть, которая противоречит.

Expected type: a -> ()
Inferred type: a -> m b

Здесь у нас есть два фактических типа. a -> части не конфликтуют, поэтому не упоминались выше.

In the second argument of `($)', namely `k'

Это сообщает нам контекст, в котором был обнаружен конфликт, и дает выражение, тип которого имеет значение, а именно k.

Предполагаемый тип здесь - это тип, который он «уже знает» для k. Откуда это? k привязывается как аргумент к лямбде, переданному в callCC, который имеет тип ((a -> m b) -> m a) -> m a, так что это выводимый тип.

Ожидаемый тип здесь - это второй аргумент when, который имеет тип Bool -> m () -> m (), что дает нам m (). Откуда взялся a -> ()? Мы получаем это, потому что a -> _ эквивалентно (->) a, который объединяется с переменной типа m в сигнатуре when.

Очевидно, что вы не хотите, чтобы типы были, но вы спросили, как интерпретировать ошибку, поэтому я оставлю это в покое.

3 голосов
/ 21 апреля 2011

Ожидаемый тип означает что вы должны иметь в этом месте.В вашем случае у нас есть when :: (Monad m) => Bool -> m () -> m ().Таким образом, компилятор делает вывод, что k в when (n == rn) $ k должно быть типа a -> ().

Предполагаемый тип означает фактический тип, выведенный компиляторомдля переменной.В вашем случае у нас есть callCC :: (MonadCont m) => ((a -> m b) -> m a) -> m a.Это означает, что полученная анонимная функция имеет тип (a -> m b) -> m a.Поскольку k является первым аргументом этой функции, компилятор заключает, что k представляется типа a -> m b.

Поскольку эти два типа не совпадают, выполучить ошибку.

2 голосов
/ 21 апреля 2011

Ошибка в этом случае заключается в том, что вы не даете продолжению k значение, возвращаемое из callCC.

Изменение

when (n == rn) $ k

на

when (n == rn) $ k ()

делает свое дело.

...