Ghc 8 - правила параметризации ограниченного типа с переименованными функциями

Меня озадачивает кажущееся ошибочным поведение GHC с довольно простыми программами на Haskell.

Рассмотрим следующий код:

import System.IO
output :: [String] -> IO()
output stringList = sequence_ $ map putStrLn stringList

main :: IO ()

s = show

main = output [
    s 42,
    s True
  ]

В GHC 8.4.3 выдает следующий вывод:

$ runghc parameterize.hs
.hs:9:7: error:
    • No instance for (Num Bool) arising from the literal ‘42’
    • In the first argument of ‘s’, namely ‘42’
      In the expression: s 42
      In the first argument of ‘output’, namely ‘[s 42, s True]’
  |
9 |     s 42,
  |

GHC 8.0.2 выдает ту же ошибку.

Это также происходит со следующими вариантами:

  • Использование where

    main :: IO ()
    main = output [
        s 42,
        s True
      ]
      where s = show
    
  • Использование let ... in

    main :: IO ()
    main = let s = show in output [
        s 42,
        s True
      ]
    

Но в трех случаях замена s = show на s x = show x решает проблему:

main :: IO ()

s x = show x

main = output [
    s 42,
    s True
  ]

$ runghc u.hs
42
True

Это не относится к show. Вот тот, который не работает с функцией succ, работающей с элементами Enum:

main :: IO ()
main = let s = succ in putStrLn $ showList [
    show $ s 41,
    show $ s False
  ] ""

Замена s = succ на s x = succ x все еще решает проблему.

Я не смог найти объяснения этому неожиданному поведению. Это ошибка? Если это не так, объясните, что происходит.

2
0
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вас укусил ограничение мономорфизма, и это не ошибка. Первые два абзаца этой страницы:

The "monomorphism restriction" is a counter-intuitive rule in Haskell type inference. If you forget to provide a type signature, sometimes this rule will fill the free type variables with specific types using "type defaulting" rules. The resulting type signature is always less polymorphic than you'd expect, so often this results in the compiler throwing type errors at you in situations where you expected it to infer a perfectly sane type for a polymorphic expression.

A simple example is plus = (+). Without an explicit signature for plus, the compiler will not infer the type (+) :: (Num a) => a -> a -> a for plus, but will apply defaulting rules to specify plus :: Integer -> Integer -> Integer. When applied to plus 3.5 2.7, GHCi will then produce the somewhat-misleading-looking error, No instance for (Fractional Integer) arising from the literal ‘3.5’.

Просто замените здесь (+) на show, и это ваш пример.

Например, с помощью этого кода:

import System.IO
output :: [String] -> IO()
output stringList = sequence_ $ map putStrLn stringList

main :: IO ()

s = show

s2 x = show x

main = do
  output [
    s False,
    s True
   ]
  output [
    s2 42,
    s2 True
    ]

Вы получите этот результат в ghci после загрузки файла, содержащего это:

Prelude> :l test.hs
[1 of 1] Compiling Main             ( test.hs, interpreted )
Ok, one module loaded.
*Main> :t s
s :: Bool -> String
*Main> :t s2
s2 :: Show a => a -> String
*Main>

Итак, в вашем первом примере тип, выведенный для s, не позволяет вам использовать его с числом.

Точные правила, когда применяется ограничение мономорфизма, определены здесь Раздел 4.5.5 отчета Haskell 2010, но являются довольно техническими.

Также обратите внимание, что ограничение мономорфизма отключается в приглашении ghci и применяется только к скомпилированным модулям.

Чтобы быть точным, решение состоит в том, чтобы либо предоставить сигнатуру полиморфного типа (s :: (Show a) => a -> String), либо добавить прагму {-# LANGUAGE NoMonomorphismRestriction #-} в начало файла (или, что эквивалентно, -XNoMonomorphismRestriction в командной строке или NoMonomorphismRestriction в поле extensions в файле Cabal или package.yaml).

Jon Purdy 27.10.2018 10:47

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