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
?
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)