haskell

"Ambiguous type variable" when using scoped variables in Haskell?


I got this OCaml code:

for x = 0 to 12 do
    let i = (1 + (x * 3)) in
    let j = (60 - (x * 5)) in
    Printf.printf "I=%d J=%d\n" (i) (j);
done;;

And I translated it to Haskell, but it wouldn't work:

import Text.Printf

for list action = mapM_ action list

main :: IO ()
main = do
    for [0..12] $ \x -> do
        let i = (1 + (x * 3))
            j = (60 - (x * 5)) in
            printf "I=%d J=%d\n" i j :: IO ()

The first error was:

Ambiguous type variable ‘t0’ arising from the arithmetic sequence ‘0 .. 12’ prevents the constraint ‘(Enum t0)’ from being solved.

So I had to move the code inside the loop into a new function, then it worked:

import Text.Printf

for list action = mapM_ action list

block :: Int -> IO ()
block x = 
    let i = 1 + (x * 3)
        j = 60 - (x * 5) in
        printf "I=%d J=%d\n" i j :: IO ()

main :: IO ()
main = do
    for [0..12] $ \x -> do block x

So my question is: Does Haskell allow variables to be declared inside a inner scope? If yes, what did I miss?

Tested on replit.com


Solution

  • In this snippet:

    main :: IO ()
    main = do
        for [0..12] $ \x -> do
            let i = (1 + (x * 3))
                j = (60 - (x * 5)) in
                printf "I=%d J=%d\n" i j :: IO ()
    

    there's no explicit indication of which numeric type is being used here. This would type check whether 0, 12, etc. were Ints, Integers, Word64s, Doubles or Complex numbers.

    In certain, limited circumstances, GHC will "default" an unknown numeric type to either Integer or Double depending on how it's used, but this relies on the unknown type being constrained to a limited set of "built-in" type classes, and the use of printf introduces an additional type class constraint that interferes with this defaulting process. If you avoided the use of printf, the type defaulting would work as intended, so the following works (with the numeric type defaulting to Integer):

    import Text.Printf
    
    for list action = mapM_ action list
    
    main :: IO ()
    main = do
        for [0..12] $ \x -> do
            let i = (1 + (x * 3))
                j = (60 - (x * 5)) in
                putStrLn $ "I=" ++ show i ++ " J=" ++ show j
    

    Alternatively, if you added a type signature to explicitly indicate which numeric type you wanted to use, it would also work:

    import Text.Printf
    
    for list action = mapM_ action list
    
    main :: IO ()
    main = do
        for [(0::Int)..12] $ \x -> do
        --     ^^^^^
            let i = (1 + (x * 3))
                j = (60 - (x * 5)) in
                printf "I=%d J=%d\n" i j :: IO ()
    

    You can also give type signatures in a let statement, so this would work too:

    import Text.Printf
    
    for list action = mapM_ action list
    
    main :: IO ()
    main = do
        for [0..12] $ \x -> do
            let i, j :: Int
            --  ^^^^^^^^^^^
                i = (1 + (x * 3))
                j = (60 - (x * 5)) in
                printf "I=%d J=%d\n" i j :: IO ()
    

    Basically, anything that lets GHC figure out the type will fix this error. The reason your version that uses block works is that block has a type signature that determines the numeric type as Int. If you'd left out that type signature, you would have had the same error:

    import Text.Printf
    
    for list action = mapM_ action list
    
    -- comment out the type signature, and it fails
    -- block :: Int -> IO ()
    block x = 
        let i = 1 + (x * 3)
            j = 60 - (x * 5) in
            printf "I=%d J=%d\n" i j :: IO ()
    
    main :: IO ()
    main = do
        for [0..12] $ \x -> do block x