[This question is motivated by Chapter 9 in "Real World Haskell"]
Here's a simple function (cut down to essentials):
saferOpenFile path = handle (\_ -> return Nothing) $ do
h <- openFile path ReadMode
return (Just h)
Why do I need that $
?
If the second argument to handle isn't a do block, I don't need it. The following works just fine:
handle (\_ -> putStrLn "Error calculating result") (print x)
When I tried removing the $
compilation failed. I can get it to work if I explicitly add parens, i.e.
saferOpenFile path = handle (\_ -> return Nothing) (do
h <- openFile path ReadMode
return (Just h))
I can understand that, but I guess I'm expecting that when Haskell hits the do
it should think "I'm starting a block", and we shouldn't have to explicitly put the $
there to break things up.
I also thought about pushing the do block to the next line, like this:
saferOpenFile path = handle (\_ -> return Nothing)
do
h <- openFile path ReadMode
return (Just h)
but that doesn't work without parens either. That confuses me, because the following works:
add a b = a + b
addSeven b = add 7
b
I'm sure I'll just reach the point where I accept it as "that's just how you write idiomatic Haskell", but does anyone have any perspective to give? Thanks in advance.
This is due to Haskell's order of operations. Function application binds tightest, which can be a source of confusion. For example:
add a b = a + b
x = add 1 add 2 3
Haskell interprets this as: the function add applied to 1, and the function add. This is probably not what the programmer intended. Haskell will complain about expecting a Num for the second argument, but getting a function instead.
There are two solutions:
1) The expression can be parenthesized:
x = add 1 (add 2 3)
Which Haskell will interpret as: the function add applied to 1, then the value of add 2 3.
But if the nesting gets too deep, this can get confusing, hence the second solution.
2) The $ operator:
x = add 1 $ add 2 3
$ is an operator that applies a function to its arguments. Haskell reads this as: the function (add 1) applied to the value of add 2 3. Remember that in Haskell, functions can be partially applied, so (add 1) is a perfectly valid function of one argument.
The $ operator can be used multiple times:
x = add 1 $ add 2 $ add 3 4
Which solution you pick will be determined by which you think is more readable in a particular context.
This actually isn't quite specific to do
-notation. You also can't write things like print if x then "Yes" else "No"
or print let x = 1+1 in x*x
.
You can verify this from the grammar definition at the beginning of chapter 3 of the Haskell Report: a do
expression or a conditional or a let
expression is an lexp, but the argument of function application is an aexp, and an lexp is generally not valid as an aexp.
This doesn't tell you why that choice was made in the Report, of course. If I had to guess, I might surmise that it's to extend the useful rule "function application binds tighter than anything else" to the binding between, say, do
and its block. But I don't know whether that was the original motivation. (I find the rejected forms like print if x then ...
hard to read, but that may just be because I'm used to reading code that Haskell accepts.)
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