Alb*_*ani 12 haskell pattern-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",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
Run Code Online (Sandbox Code Playgroud)
是否有一个编程结构可以像下面的伪代码那样简化代码?
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
Run Code Online (Sandbox Code Playgroud)
提前感谢您的建议.
让我使用一个比发布的更短的例子:
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
Run Code Online (Sandbox Code Playgroud)
代码在GHCi中运行如下:
> map original [1..12]
[4,4,4,3,3,2,2,1,1,2,2,4]
Run Code Online (Sandbox Code Playgroud)
我们的目标是将两个分支机构"组合"在一起,将n < 12这种情况考虑在内.(这在original玩具示例中并不是一个巨大的收获,但它可能在更复杂的情况下.)
我们可以天真地想到在两个嵌套的情况下拆分代码:
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
Run Code Online (Sandbox Code Playgroud)
或者,等效地,使用MultiWayIf扩展名:
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
Run Code Online (Sandbox Code Playgroud)
然而,这会带来惊喜:
> map wrong1 [1..12]
*** Exception: Non-exhaustive patterns in case
> map wrong2 [1..12]
*** Exception: Non-exhaustive guards in multi-way if
Run Code Online (Sandbox Code Playgroud)
问题是,当n是1时,n < 12分支,内部情况评价,再没有分支有考虑1.该original代码只是尝试下一个分支,它处理它.但是,wrong1,wrong2不会回溯到外壳.
请注意,当您知道外壳具有非重叠条件时,这不是问题.在OP发布的代码中,情况似乎如此,因此这些wrong1,wrong2方法可以在那里工作(如Jefffrey所示).
但是,可能存在重叠的一般情况呢?幸运的是,Haskell是懒惰的,因此很容易推出自己的控制结构.为此,我们可以Maybe如下利用monad:
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 ]
Run Code Online (Sandbox Code Playgroud)
这是更详细一点,但不是很大.用这种风格编写代码比看起来容易:简单的多路条件写成
foo n = fromJust $ msum
[ guard boolean1 >> return value1
, guard boolean2 >> return value2
, ...
]
Run Code Online (Sandbox Code Playgroud)
并且,如果你想要一个"嵌套"的情况,只需return value用a 替换任何一个msum [ ... ].
这样做可以确保我们获得所需的回溯.确实:
> map correct [1..12]
[4,4,4,3,3,2,2,1,1,2,2,4]
Run Code Online (Sandbox Code Playgroud)
这里的诀窍是当guard失败时,它会生成一个Nothing值.库函数msum只是选择Nothing列表中的第一个非值.因此,即使内部列表中的每个元素都是Nothing,外部msum也将考虑外部列表中的下一个项目 - 回溯,如所需.
我建议将每个嵌套条件分组到一个函数中:
interval :: _ -> _ -> (String, String)
interval pt1 pt2
| gd == 0 = doSomethingA pt1 pt2
| gd == 1 = doSomethingB pt1 pt2
| gd == 2 = doSomethingC pt1 pt2
...
Run Code Online (Sandbox Code Playgroud)
然后,例如:
doSomethingA :: _ -> _ -> (String, String)
doSomethingA pt1 pt2
| 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")
where sd = displacementInSemitonesOfPitches pt1 pt2
Run Code Online (Sandbox Code Playgroud)
或者,您可以使用MultiWayIf语言扩展名:
interval pt1 pt2 =
if | gd == 0 -> if | sd < (-2) -> ("unison",show (abs sd) ++ "d")
| sd == (-2) -> ("unison","dd")
| sd == (-1) -> ("unison","d")
...
| gd == 1 -> if | sd < (-1) -> ("second",show (abs sd) ++ "d")
| sd == (-1) -> ("second","dd")
| sd == 0 -> ("second","d")
...
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
617 次 |
| 最近记录: |