I'm struggling with the semantics of where
within do
blocks, specifically with Test.Hspec
. The following works:
module ExampleSpec where
import Test.Hspec
import Test.QuickCheck
spec :: Spec
spec = do
describe "foo" $ do
let
f = id
in
it "id" $ property $
\x -> f x `shouldBe` (x :: Int)
describe "bar" $ do
it "id" $ property $
\x -> x `shouldBe` (x :: Int)
This does not:
module ExampleSpec where
import Test.Hspec
import Test.QuickCheck
spec :: Spec
spec = do
describe "foo" $ do
it "id" $ property $
\x -> f x `shouldBe` (x :: Int)
where
f = id
describe "bar" $ do
it "id" $ property $
\x -> x `shouldBe` (x :: Int)
It fails with:
/mnt/c/haskell/chapter15/tests/ExampleSpec.hs:13:5: error: parse error on input ‘describe’
|
13 | describe "bar" $ do
| ^^^^^^^^
Am I doing something wrong or is this some kind of inherent limitation with where
?
CodePudding user response:
A where
clause can only be attached to a function or case binding, and must come after the right hand side body.
When the compiler sees where
, it knows that the RHS of your spec = ...
equation is over. Then it uses indentation to figure out how far the block of definitions inside the where
extends (just the single f = id
in this case). Following that the compiler is looking for the start of the next module-scope definition, but an indented describe "bar" $ do
is not valid for the start of a definition, which is the error you get.
You cannot randomly insert a where
clause into the middle of a function definition. It only can be used to add auxiliary bindings in scope over the whole RHS of a binding; it cannot be used to add local bindings in scope for an arbitrary sub-expression.
However there is let ... in ...
for exactly that purpose. And since you're using do
blocks under each describe
, you can also use the let
statement (using the remainder of the do
block to delimit the scope of the local bindings, instead of the in
part of the let ... in ...
expression). So you can do this instead:
spec = do
describe "foo" $ do
let f = id
it "id" $ property $
\x -> f x `shouldBe` (x :: Int)
describe "bar" $ do
it "id" $ property $
\x -> x `shouldBe` (x :: Int)
CodePudding user response:
This is a syntactic restriction in service of the scoping rules of a where block. Within a where
block, values bound in a pattern match are in scope, and values defined in the where
block are in scope for guards within that pattern match. As such, a where
block must be attached to locations where a pattern match and guards at the very least could exist. This ends up being value declarations and branches of case expressions. In your second example you are trying to attach a where
block to an arbitrary expression, which is just not what they're intended to do.