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
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 Int
s, Integer
s, Word64
s, Double
s 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