I recently wrote some Haskell code and it never terminates. After I carefully examined my code, the problem boiled down to the following code piece
main :: IO ()
main = print $ let a = 10 in
let a = a in
a :: Int
I guess this must have something to do with the laziness of Haskell since the same code terminates in OCaml. However, if I wrote the following code instead
main :: IO ()
main = print $ let a = 10 in
let b = a in
b :: Int
the code would have no problem terminating at all. I can't get the reason since in the original code, the two a's should be considered as two different variables. I don't know why the naming of them has anything to do with the semantic of the program.
The issue is that, unlike OCaml, let
bindings in Haskell are recursive by default. So let x = x in ...
is equivalent to OCaml's let rec x = x in ...
and is a circular definition.
This is why shadowing variable names in Haskell (ie defining a
multiple times) is considered bad style and even has a compiler warning, which you can turn on with the -Wall
flag or more specifically -fwarn-name-shadowing
.
This default makes more sense in Haskell than OCaml because, thanks to laziness, circular values (rather than just recursive functions) are actually useful. let x = 1:x
gives us an infinite list of 1
, which we can use just like a normal list.
At the same time, some people don't like this for basically exactly the reason you ran into here: it's possible to introduce unintuitive infinite loops in your code, which makes some errors and typos harder to track down. This is also confusing because, by necessity, <-
bindings in do-notation are not recursive by default, which is a bit inconsistent.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With