Home > Mobile >  Why cannot I get `where` to work in Hspec
Why cannot I get `where` to work in Hspec

Time:12-01

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.

  • Related