Haskell中有没有类似于后卫的东西? [英] Is there, in Haskell, something similar to sub-guards?
问题描述
我正在编写一个关于音程的分类的程序。概念结构非常复杂,我会尽可能清楚地表示它。前几行代码是一个小工具,可以正常工作。第二个是符合我简洁需求的伪代码。
interval pt1 pt2
| gd == 0&& sd< (-2)=(unison,show(abs sd)++d)
| gd == 0&& sd ==(-2)=(unison,dd)
| gd == 0&& sd ==(-1)=(unison,d)
| gd == 0&& sd == 0 =(unison,P)
| gd == 0&& sd == 1 =(unison,A)
| gd == 0&& sd == 2 =(unison,AA)
| gd == 0&& sd> 2 =(unison,显示sd ++A)
| gd == 1&& sd< (-1)=(second,show(abs sd)++d)
| gd == 1&& sd ==(-1)=(second,dd)
| gd == 1&& sd == 0 =(second,d)
| gd == 1&& sd == 1 =(second,m)
| gd == 1&& sd == 2 =(second,M)
| gd == 1&& sd == 3 =(second,A)
| gd == 1&& sd == 4 =(second,AA)
| gd == 1&& sd> 4 =(second,show(abs sd)++A)
其中
(bn1,acc1,oct1)= parsePitch pt1
(bn2,acc2, oct2)= parsePitch pt2
direction = signum sd
sd = displacementInSemitonesOfPitches pt1 pt2
gd = abs $ displacementBetweenTwoBaseNotes direction bn1 bn2
是否有一个编程结构可以像下面的伪代码那样简化代码?
间隔pt1 pt2
| gd == 0 | sd< (-2)=(unison,show(abs sd)++d)
| sd ==(-2)=(unison,dd)
| sd ==(-1)=(unison,d)
| sd == 0 =(unison,P)
| sd == 1 =(unison,A)
| sd == 2 =(unison,AA)
| sd> 2 =(unison,显示sd ++A)
| gd == 1 | sd< (-1)=(second,show(abs sd)++d)
| sd ==(-1)=(second,dd)
| sd == 0 =(second,d)
| sd == 1 =(second,m)
| sd == 2 =(second,M)
| sd == 3 =(second,A)
| sd == 4 =(second,AA)
| sd> 4 =(second,show(abs sd)++A)
| gd == 2 | sd ... = ...
| sd ... = ...
...
| mod gd 7 == 1 | mod sd 12 == ...
| mod sd 12 == ...
...
|否则= ...
其中
(bn1,acc1,oct1)= parsePitch pt1
(bn2,acc2,oct2)= parsePitch pt2
direction = signum sd
sd = displacementInSemitonesOfPitches pt1 pt2
gd = abs $ displacementBetweenTwoBaseNotes direction bn1 bn2
提前感谢您的建议。
让我使用比发布的示例更短的示例:
original :: Int - >诠释
原始n
| n< 10&& n> 7 = 1 - 匹配8,9
| n< 12&& n> 5 = 2 - 匹配6,7,10,11
| n< 12&& n> 3 = 3 - 匹配4,5
| n< 13&& n> 0 = 4 - 匹配1,2,3,12
代码在GHCi中运行如下:
> map original [1..12]
[4,4,4,3,3,2,2,1,1,2,2,4]
我们的目标是将需要 n< 12
,把这个条件分解出来。 (这在原来的
玩具例子中并不是很大的收获,但它可能会在更复杂的情况下发生。)
我们可以天真地想到在两个嵌套的情况下分割代码:
wrong1 :: Int - > Int
wrong1 n =
_ |的case() n< 10&& n> 7 - > 1
| n< 12 - >
_ |的情况() n> 5 - > 2
| n> 3 - > 3
| n< 13&& n> 0 - > 4
或者等价地使用 MultiWayIf
扩展名:
wrong2 :: Int - > Int
wrong2 n = if
| n< 10&& n> 7 - > 1
| n< 12 - >如果| n> 5 - > 2
| n> 3 - > 3
| n< 13&& n> 0 - > 4
然而,这会带来惊喜:
> map wrong1 [1..12]
***例外情况:非穷举模式,例如
> map wrong2 [1..12]
***例外:如果
$ b为非穷举式守卫$ b
问题是,当 n
为 1
时, n < 12
分支,评估内部情况,然后没有分支考虑 1
。 原始
代码只是尝试处理它的下一个分支。但是, wrong1,wrong2
不会回溯到外部大小写。
请注意,这不是问题你知道外壳具有不重叠的条件。在OP发布的代码中,这似乎是这种情况,所以 wrong1,wrong2
方法可以在那里工作(如Jefffrey所示)。
然而,一般情况下哪些地方可能会有重叠呢?幸运的是,Haskell很懒,所以很容易推出我们自己的控制结构。为此,我们可以利用 Maybe
monad,如下所示:
正确:: Int - > Int
正确n = fromJust $ msum
[guard(n <10& n> 7)>>返回1
,guard(n <12)>> msum
[guard(n> 5)>>返回2
,guard(n> 3)>>返回3]
,guard(n< 13& n> 0)>>返回4]
它 有点冗长,但不是太多。以这种风格编写代码比看起来容易:一个简单的multiway条件被写为
foo n = fromJust $ msum
[guard boolean1>>返回值1
,guard boolean2>>返回值2
,...
]
如果你想要嵌套的情况下,只需用 msum [...]
替换任何返回值
。
这样做可以确保我们获得想要的回溯。事实上:
> map correct [1..12]
[4,4,4,3,3,2,2,1,1,2,2,4]
这里的技巧是当 guard
失败时,它会生成一个 Nothing
值。库函数 msum
只需选择列表中的第一个非 - Nothing
值。因此,即使内部列表中的每个元素都是 Nothing
,外部 msum
会考虑外部的下一项列表 - 回溯,如通缉。
I'm writing a program on the classification of musical intervals. The conceptual structure is quite complicated and I would represent it as clearly as possible. The first few lines of code are a small extract that works properly. The second are the pseudo-code that would meet my needs of conciseness.
interval pt1 pt2
| gd == 0 && sd < (-2) = ("unison",show (abs sd) ++ "d")
| gd == 0 && sd == (-2) = ("unison","dd")
| gd == 0 && sd == (-1) = ("unison","d")
| gd == 0 && sd == 0 = ("unison","P")
| gd == 0 && sd == 1 = ("unison","A")
| gd == 0 && sd == 2 = ("unison","AA")
| gd == 0 && sd > 2 = ("unison",show sd ++ "A")
| gd == 1 && sd < (-1) = ("second",show (abs sd) ++ "d")
| gd == 1 && sd == (-1) = ("second","dd")
| gd == 1 && sd == 0 = ("second","d")
| gd == 1 && sd == 1 = ("second","m")
| gd == 1 && sd == 2 = ("second","M")
| gd == 1 && sd == 3 = ("second","A")
| gd == 1 && sd == 4 = ("second","AA")
| gd == 1 && sd > 4 = ("second",show (abs sd) ++ "A")
where
(bn1,acc1,oct1) = parsePitch pt1
(bn2,acc2,oct2) = parsePitch pt2
direction = signum sd
sd = displacementInSemitonesOfPitches pt1 pt2
gd = abs $ displacementBetweenTwoBaseNotes direction bn1 bn2
Is there a programming structure that could simplify the code like the following pseudo-code does?
interval pt1 pt2
| gd == 0 | sd < (-2) = ("unison",show (abs sd) ++ "d")
| sd == (-2) = ("unison","dd")
| sd == (-1) = ("unison","d")
| sd == 0 = ("unison","P")
| sd == 1 = ("unison","A")
| sd == 2 = ("unison","AA")
| sd > 2 = ("unison",show sd ++ "A")
| gd == 1 | sd < (-1) = ("second",show (abs sd) ++ "d")
| sd == (-1) = ("second","dd")
| sd == 0 = ("second","d")
| sd == 1 = ("second","m")
| sd == 2 = ("second","M")
| sd == 3 = ("second","A")
| sd == 4 = ("second","AA")
| sd > 4 = ("second",show (abs sd) ++ "A")
| gd == 2 | sd ... = ...
| sd ... = ...
...
| mod gd 7 == 1 | mod sd 12 == ...
| mod sd 12 == ...
...
| otherwise = ...
where
(bn1,acc1,oct1) = parsePitch pt1
(bn2,acc2,oct2) = parsePitch pt2
direction = signum sd
sd = displacementInSemitonesOfPitches pt1 pt2
gd = abs $ displacementBetweenTwoBaseNotes direction bn1 bn2
Thank you in advance for your suggestions.
Let me use a shorter example than the posted one:
original :: Int -> Int
original n
| n < 10 && n > 7 = 1 -- matches 8,9
| n < 12 && n > 5 = 2 -- matches 6,7,10,11
| n < 12 && n > 3 = 3 -- matches 4,5
| n < 13 && n > 0 = 4 -- matches 1,2,3,12
The code runs in GHCi as follows:
> map original [1..12]
[4,4,4,3,3,2,2,1,1,2,2,4]
Our aim is to "group" together the two branches requiring with n < 12
, factoring this condition out. (This is not a huge gain in the original
toy example, but it could be in more complex cases.)
We could naively think of splitting the code in two nested cases:
wrong1 :: Int -> Int
wrong1 n = case () of
_ | n < 10 && n > 7 -> 1
| n < 12 -> case () of
_ | n > 5 -> 2
| n > 3 -> 3
| n < 13 && n > 0 -> 4
Or, equivalently, using the MultiWayIf
extension:
wrong2 :: Int -> Int
wrong2 n = if
| n < 10 && n > 7 -> 1
| n < 12 -> if | n > 5 -> 2
| n > 3 -> 3
| n < 13 && n > 0 -> 4
This however, leads to surprises:
> map wrong1 [1..12]
*** Exception: Non-exhaustive patterns in case
> map wrong2 [1..12]
*** Exception: Non-exhaustive guards in multi-way if
The issue is that when n
is 1
, the n < 12
branch is taken, the inner case is evaluated, and then no branch there considers 1
. The original
code simply tries the next branch, which handles it. However, wrong1,wrong2
are not backtracking to the outer case.
Please note that this is not a problem when you know that the outer case has non-overlapping conditions. In the code posted by the OP, this seems to be the case, so the wrong1,wrong2
approaches would work there (as shown by Jefffrey).
However, what about the general case, where there might be overlaps? Fortunately, Haskell is lazy, so it's easy to roll our own control structures. For this, we can exploit the Maybe
monad as follows:
correct :: Int -> Int
correct n = fromJust $ msum
[ guard (n < 10 && n > 7) >> return 1
, guard (n < 12) >> msum
[ guard (n > 5) >> return 2
, guard (n > 3) >> return 3 ]
, guard (n < 13 && n > 0) >> return 4 ]
It is a bit more verbose, but not by much. Writing code in this style is easier than it might look: a simple multiway conditional is written as
foo n = fromJust $ msum
[ guard boolean1 >> return value1
, guard boolean2 >> return value2
, ...
]
and, if you want a "nested" case, just replace any of the return value
with a msum [ ... ]
.
Doing this ensures that we get the wanted backtracking. Indeed:
> map correct [1..12]
[4,4,4,3,3,2,2,1,1,2,2,4]
The trick here is that when a guard
fails, it generates a Nothing
value. The library function msum
simply selects the first non-Nothing
value in the list. So, even if every element in the inner list is Nothing
, the outer msum
will consider the next item in the outer list -- backtracking, as wanted.
这篇关于Haskell中有没有类似于后卫的东西?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!