Haskell: ошибка переменной жесткого типа при передаче функции в качестве аргумента

GHC говорит, что моя функция слишком общая, чтобы быть переданной в качестве аргумента.

Вот упрощенная версия, которая воспроизводит ошибку:

data Action m a = SomeAction (m a)


runAction :: Action m a -> m a
runAction (SomeAction ma) =  ma

-- Errors in here
actionFile :: (Action IO a -> IO a) -> String -> IO ()
actionFile actionFunc fileName = do
    actionFunc $ SomeAction $ readFile fileName
    actionFunc $ SomeAction $ putStrLn fileName


main :: IO ()
main =
    actionFile runAction "Some Name.txt"

Вот что говорит ошибка:

 • Couldn't match type ‘a’ with ‘()’
      ‘a’ is a rigid type variable bound by
        the type signature for:
          actionFile :: forall a. (Action IO a -> IO a) -> String -> IO ()
        at src/Lib.hs:11:15
      Expected type: Action IO a
        Actual type: Action IO ()

Компилятор хочет, чтобы я был более конкретным в моей сигнатуре типа, но я не могу, потому что мне нужно будет использовать функцию параметров с различными типами аргументов. Как и в моем примере, я передаю Action IO () и Action IO String,

Если я заменю (Action IO a -> IO a) -> String -> IO () за (Action IO () -> IO ()) -> String -> IO (), как спросил компилятор, вызов с readFile ошибки, потому что это выводит IO String,

Почему это происходит и что я должен сделать, чтобы передать эту функцию в качестве аргумента?

Я знаю, что если я просто использую runAction внутри моего actionFile функция все будет работать, но в моем реальном коде runAction является частично примененной функцией, которая создается на основе результатов вычислений ввода-вывода, поэтому она не доступна во время компиляции.

1 ответ

Решение

Это проблема количественного определения. Тип

actionFile :: (Action IO a -> IO a) -> String -> IO ()

означает, как сообщается в ошибке GHC,

actionFile :: forall a. (Action IO a -> IO a) -> String -> IO ()

в котором говорится следующее:

  • звонящий должен выбрать тип a
  • вызывающая сторона должна предоставить функцию g :: Action IO a -> IO a
  • звонящий должен предоставить String
  • в конце концов, actionFile должен ответить с IO ()

Обратите внимание, что a выбирается абонентом, а не actionFile, С точки зрения actionFileпеременная такого типа связана с фиксированным неизвестным типом, выбранным кем-то другим: это переменная "жесткого" типа, которую GHC упоминает в ошибке.

Тем не мение, actionFile звонит g прохождение Action IO () аргумент (из-за putStrLn). Это означает, что actionFile хочет выбрать a = (), Так как звонящий может выбрать другой aвозникает ошибка типа.

В дальнейшем, actionFile также хочет позвонить g прохождение Action IO String аргумент (из-за readFile), поэтому мы также хотим выбрать a = String, Это подразумевает, что g должен принять выбор любого a мы хотим.

Как упомянул Алексис Кинг, решение может состоять в том, чтобы переместить квантификатор и использовать тип ранга 2:

actionFile :: (forall a. Action IO a -> IO a) -> String -> IO ()

Этот новый тип означает, что:

  • вызывающая сторона должна предоставить функцию g :: forall a. Action IO a -> IO a
    • вызывающий g (То есть, actionFile) должен выбрать a
    • вызывающий g (То есть, actionFile) должен предоставить Action IO a
    • в конце концов, g должен предоставить IO a
  • звонящий должен предоставить String
  • в конце концов, actionFile должен ответить с IO ()

Это позволяет actionFile выбирать a как и хотел.

Другие вопросы по тегам