Search code examples
haskellghctype-parameter

GHC 8 - Constrained type parameterization rules with renamed functions


I'm puzzled by a seemingly buggy behavior of GHC happening with rather simple Haskell programs.

Consider the following code:

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

main :: IO ()

s = show

main = output [
    s 42,
    s True
  ]

In GHC 8.4.3 produces the following output:

$ 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 produces the same error.

This also happens with the following variations:

  • Using where

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

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

But in the three cases, replacing s = show by s x = show x solves the problem:

main :: IO ()

s x = show x

main = output [
    s 42,
    s True
  ]

$ runghc u.hs
42
True

This is not specific to show. Here is one which fails with the succ function operating on Enum elements:

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

Replacing s = succ by s x = succ x still fixes the thing.

I haven't been able to find an explanation for this unexpected behavior. Is it a bug ? If it isn't, please explain what is happening.


Solution

  • You are bitten by the monomorphism restriction and its not a bug. The first two paragraphs of that page:

    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’.

    Simply replace (+) here with show and it is your example.

    With this code for example:

    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
        ]
    

    You get this result in ghci after loading the file containing this:

    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>
    

    So in your first example the type deduced for s doesn't allow you to use it with a number.

    The exact rules, when the monomorphism restriction applies are defined here Section 4.5.5 of Haskell Report 2010, but are fairly technical.

    Also pay attention that the monomorphism restriction is deactivated at the ghci prompt and only applies to compiled modules.