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
как и хотел.