首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在哈斯克尔,有类似于分护人员的东西吗?

在哈斯克尔,有类似于分护人员的东西吗?
EN

Stack Overflow用户
提问于 2015-02-15 13:52:08
回答 2查看 1.2K关注 0票数 13

我正在写一个关于音乐音程分类的程序。概念结构相当复杂,我会尽可能清楚地说明这一点。前几行代码是一个工作正常的小摘录。第二种是伪代码,它能满足我对简洁性的需求。

代码语言:javascript
运行
复制
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

是否有一个可以像下面的伪代码那样简化代码的编程结构?

代码语言:javascript
运行
复制
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

提前感谢您的建议。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-02-15 18:25:01

让我用一个比张贴的例子更短的例子:

代码语言:javascript
运行
复制
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

代码在GHCi中运行如下:

代码语言:javascript
运行
复制
> map original [1..12]
[4,4,4,3,3,2,2,1,1,2,2,4]

我们的目标是将需要使用n < 12的两个分支“分组”,并将此条件考虑在内。(在original玩具示例中,这不是一个巨大的收获,但在更复杂的情况下可能是如此。)

我们可以天真地考虑将代码分成两种嵌套的情况:

代码语言:javascript
运行
复制
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

或者,等效地,使用MultiWayIf扩展:

代码语言:javascript
运行
复制
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

然而,这会带来令人惊讶的结果:

代码语言:javascript
运行
复制
> map wrong1 [1..12]
*** Exception: Non-exhaustive patterns in case

> map wrong2 [1..12]
*** Exception: Non-exhaustive guards in multi-way if

问题是,当n1时,取n < 12分支,计算内部情况,然后那里的分支不考虑1original代码只是尝试下一个分支,该分支处理它。然而,wrong1,wrong2并没有回溯到外部情况。

请注意,当您知道外部情况有不重叠的条件时,这不是一个问题。在OP发布的代码中,情况似乎是这样的,因此wrong1,wrong2方法将在那里工作(如Jefffrey所示)。

然而,在一般情况下,可能会有重叠,这又如何呢?幸运的是,Haskell很懒,所以很容易使用我们自己的控制结构。为此,我们可以如下所示利用Maybe monad:

代码语言:javascript
运行
复制
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 ]

它有点冗长,但并没有太多。用这种风格编写代码要比看起来容易得多:一个简单的多向条件写成

代码语言:javascript
运行
复制
foo n = fromJust $ msum 
   [ guard boolean1 >> return value1
   , guard boolean2 >> return value2
   , ...
   ]

而且,如果您想要一个“嵌套”情况,只需将任何return value替换为msum [ ... ]即可。

这样做可以确保我们得到通缉的回溯。的确:

代码语言:javascript
运行
复制
> map correct [1..12]
[4,4,4,3,3,2,2,1,1,2,2,4]

这里的诀窍是,当一个guard失败时,它会生成一个Nothing值。库函数msum只是选择列表中的第一个非Nothing值。因此,即使内部列表中的每个元素都是Nothing,外部msum也会根据需要考虑外部列表中的下一个项目--回溯。

票数 8
EN

Stack Overflow用户

发布于 2015-02-15 14:00:43

我建议将每个嵌套条件分组到一个函数中:

代码语言:javascript
运行
复制
interval :: _ -> _ -> (String, String)
interval pt1 pt2
    | gd == 0 = doSomethingA pt1 pt2
    | gd == 1 = doSomethingB pt1 pt2
    | gd == 2 = doSomethingC pt1 pt2
    ...

然后,例如:

代码语言:javascript
运行
复制
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  

或者,您可以使用MultiWayIf语言扩展:

代码语言:javascript
运行
复制
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")
                       ...
票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/28526768

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档