Haskell: Can I use a where clause after a block with bind operators (>>=)?
Question
I have a very simple question. I'd like to use a where clause after a bloc of code that uses bind operators but I get a compilation error.
Here is a simple example:
main =
putStrLn "where clause test:" >>
return [1..10] >>= \list ->
print list'
where list' = reverse list -- test1.hs:5:28: Not in scope: `list'
I can use a let clause for list' as in
main =
putStrLn "where clause test:" >>
return [1..10] >>= \list ->
let list' = reverse list -- works of course
in print list'
but I'd really like it if I could use a where clause...
I also tried with do notation
main = do
putStrLn "where clause test:"
list <- return [1..10]
print list'
where list' = reverse list --test3.hs:5:30: Not in scope: `list'
Same problem. Can I use a where clause in these circumstances?
Solution
As ephemient explains, you can't use where
clauses the way you do.
The error happens because in this code:
main =
return [1..10] >>= \list ->
print list'
where
list' = reverse list
The where
-clause is attached to the main function.
Here's that same function with more parentheses:
main = return [1..10] >>= (\list -> print list')
where
list' = reverse list
I think its fairly obvious why you get the "out of scope
" error: The binding for list
is deep inside the main
expression, not something the where
clause can reach.
What I usually do in this situation (and I've been bitten by the same thing a bunch of times). I simply introduce a function and pass the list
as an argument.
main = do
list <- return [1..10]
let list' = f list
print list'
where
f list = reverse list -- Consider renaming list,
-- or writing in point-free style
Of course, I imagine your actual code in the f
function is a lot more that just reverse
and that's why you want it inside a where
clause, instead of an inline let
binding. If the code inside the f
function is very small, I'd just write it inside the let
binding, and wouldn't go through the overhead of introducing a new function.
OTHER TIPS
The problem is that let
-in
is an expression, which can be used inside other expressions, while where
can only be used on a (module|class|instance|GADT|...) declaration or a (function|pattern) binding.
From the Haskell 98 report on declarations and bindings,
p | g1
=
e1
| g2=
e2
…
| gm=
em
where {
decls}
is sugar for
p
= let
declsin
if
g1then
e1else
if
g2then
e2else
…
if
gmthen
emelse error "Unmatched pattern"
or, simplifying things by removing guards,
p
=
ewhere {
decls}
is sugar for
p
= let
declsin
e
in both function and pattern bindings. This is true even when your e is a do {
…}
construct.
If you want to have a binding local to a particular subexpression within a larger expression, you need to use let
-in
(or simply let
inside a do
, but that's just sugar for let
-in
).
You can't even write
main = do
putStrLn "where clause test: "
list <- return [1..10]
(print list' where list' = reverse list)
because "e where {
decls }
" is not a legal expression – where
can only be used in declarations and bindings.
main = do
putStrLn "where clause test: "
list <- return [1..10]
let list' = list'' where list'' = reverse list
print list'
This is legal (if somewhat contrived).
As far as I can tell, the where clause is only used in local bindings. The inner part of a >>(=) binding statement is not a local binding (two different kinds of bindings in that sentence).
Compare with this:
main = f [1..10]
f list =
putStrLn "where clause test:" >> print list'
where list' = reverse list
You might want to refer to the Haskell 98 syntax report - not sure how much help it would be.
If I'm wrong, someone will certainly correct me, but I'm pretty sure you can't use a where clause at all in the style you've shown above. list
will never be in scope to a where clause unless it's a parameter to the function.