Haskell中where子句的执行
我得到了以下代码,我知道它可以工作,但我对 Haskell 完全陌生,并且有 2 个关于 where 子句的问题。
f3 :: [[Int]] -> [Int] -> [Int]
f3 [] status = status --- Base Case
f3 ([p1,p2]:tail) status
| status !! (p1-1) == 0 = f3 tail status --- Case 1
| status !! (p2-1) == 1 = f3 tail newStatus1 --- Case 2
| otherwise = f3 tail newStatus2 --- Case 3
where newStatus1 = set status p1 0 --- Line 7
newStatus2 = set newStatus2Temp p1 1 --- Line 8
newStatus2Temp = set status p2 0 --- Line 9
所以基本上 f3 谓词有 2 个参数:
- 像这样的整数列表列表:[[1,2],[2,3],[3,2]]
- 整数列表
它的输出是最终更新的第二个参数。
正如您所看到的,除了基本情况之外,我还得到了 2 种情况 (2) 和 (3),其中 status/[Int] 参数通过标准 set predicate。
问题 1):
- 说案例2是真的。Haskell 执行第 8 行和第 9 行吗?
- 说案例3是真的。Haskell 执行第 7 行吗?
问题2) :
- 守卫可以拥有自己的位置吗?
- 有没有更好的方法来实际做到这一点?
回答
作为惰性求值的结果,第 7-9 行中的每一行中的代码仅在对匹配情况的代码求值过程中评估/使用相应绑定的值时才运行。所以:
- 如果情况 1 为真,则第 7-9 行都不会运行。
- 如果情况 1 为假但情况 2 为真,则评估
newStatus运行第 7 行,但不运行第 8-9 行。 - 如果情况 1 和 2 为假但情况 3 为真,则
newStatus2运行第 8 行的评估,该评估newStatus2Temp导致第 9 行运行。第 7 行未运行。
该where子句本身只能附着到整个图案绑定(例如,整个f3 ([p1,p2]:tail) status | ... | ... = ...表达),而不是单个防护装置,以便保护不能有其自己的where条款。您可以为每个守卫重复该模式:
f3 :: [[Int]] -> [Int] -> [Int]
f3 [] status = status
f3 ([p1,p2]:tail) status | status !! (p1-1) == 0 = f3 tail status
f3 ([p1,p2]:tail) status | status !! (p2-1) == 1 = f3 tail newStatus1
where newStatus1 = set status p1 0
f3 ([p1,p2]:tail) status | otherwise = f3 tail newStatus2
where newStatus2 = set newStatus2Temp p1 1
newStatus2Temp = set status p2 0
或使用let ... in ...块:
f3 :: [[Int]] -> [Int] -> [Int]
f3 [] status = status
f3 ([p1,p2]:tail) status
| status !! (p1-1) == 0 = f3 tail status
| status !! (p2-1) == 1
= let newStatus1 = set status p1 0
in f3 tail newStatus1
| otherwise
= let newStatus2 = set newStatus2Temp p1 1
newStatus2Temp = set status p2 0
in f3 tail newStatus2
我认为您的where-clause 版本没有任何问题,并且编写 Haskell 代码并在where每种情况下仅使用 -clause 中的绑定子集(甚至有效/有意义)的情况并不少见。有了这么小的助手,这个特定的例子可能在没有任何助手的情况下写得更清楚:
f3 :: [[Int]] -> [Int] -> [Int]
f3 [] status = status
f3 ([p1,p2]:tail) status
| status !! (p1-1) == 0 = f3 tail $ status
| status !! (p2-1) == 1 = f3 tail $ set status p1 0
| otherwise = f3 tail $ set (set status p2 0) p1 1
使用 GHC 和-O2,所有这四个(您的原始代码和这三个变体)都编译为相同的低级代码,因此请使用您认为最清楚的那个。