我很难理解do
块中where
的语义,尤其是Test.Hspec
。以下工作:
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)
这不是:
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)
它失败了:
/mnt/c/haskell/chapter15/tests/ExampleSpec.hs:13:5: error: parse error on input ‘describe’
|
13 | describe "bar" $ do
| ^^^^^^^^
我是做错了什么,还是where
有某种固有的局限性?
where
子句只能附加到函数或大小写绑定,并且必须位于右侧正文之后。
当编译器看到where
时,它知道spec = ...
方程的RHS已经结束。然后,它使用缩进来计算where
内部的定义块延伸了多远(在这种情况下,只有单个f = id
(。接下来,编译器将寻找下一个模块范围定义的开始,但缩进的describe "bar" $ do
对于定义的开始无效,这就是您得到的错误。
不能在函数定义的中间随机插入where
子句。只有可以用于在绑定的整个RHS范围内添加辅助绑定;它不能用于在任意子表达式的作用域中添加本地绑定。
然而,let ... in ...
正是为了这个目的。由于在每个describe
下使用do
块,因此也可以使用let
语句(使用do
块的剩余部分来界定本地绑定的范围,而不是let ... in ...
表达式的in
部分(。所以你可以这样做:
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)
这是为where块的作用域规则服务的语法限制。在where
块中,模式匹配中绑定的值在范围内,而在where
块中定义的值在该模式匹配中的保护范围内。因此,where
块必须连接到模式匹配的位置,并且至少可以存在保护。这最终成为值声明和大小写表达式的分支。在第二个例子中,您试图将where
块附加到任意表达式上,这不是它们想要做的