Haskell 在哈斯克尔,有没有类似于副警卫的东西?

Haskell 在哈斯克尔,有没有类似于副警卫的东西?,haskell,pattern-guards,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)

我正在写一个关于音程分类的程序。概念结构相当复杂,我会尽可能清楚地表达出来。前几行代码是一个小摘录,可以正常工作。第二个是伪代码,可以满足我简洁的需求

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
间隔pt1 pt2
|gd==0&&sd<-2=(“unison”,显示(abs sd)++d)
|gd==0&&sd==(-2)=(“统一”、“dd”)
|gd==0&&sd==(-1)=(“unison”、“d”)
|gd==0&&sd==0=(“unison”,“P”)
|gd==0&&sd==1=(“统一”、“A”)
|gd==0&&sd==2=(“unison”、“AA”)
|gd==0&&sd>2=(“unison”,显示sd++“A”)
|gd==1&&sd<-1=(“第二”,显示(abs sd)++d”)
|gd==1&&sd==(-1)=(“第二”、“第二”)
|gd==1&&sd==0=(“第二个”、“d”)
|gd==1&&sd==1=(“第二个”、“m”)
|gd==1&&sd==2=(“第二个”、“M”)
|gd==1&&sd==3=(“第二个”,“A”)
|gd==1&&sd==4=(“第二”,“AA”)
|gd==1&&sd>4=(“第二”,显示(abs sd)+“A”)
哪里
(bn1,acc1,oct1)=解析音高pt1
(bn2,acc2,oct2)=解析音高pt2
方向=符号sd
sd=位移插入长度pt1 pt2
gd=沃巴西债券方向bn1和bn2之间的abs$位移
是否有一种编程结构可以像下面的伪代码那样简化代码?

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
间隔pt1 pt2
|gd==0 | sd<-2=(“unison”,显示(abs sd)+“d”)
|标准差=(-2)=(“协调一致”,“dd”)
|标准差=(-1)=(“统一”、“d”)
|sd==0=(“统一”,“P”)
|sd==1=(“unison”,“A”)
|sd==2=(“unison”,“AA”)
|sd>2=(“unison”,显示sd++“A”)
|gd==1 | sd<-1=(“第二”,显示(abs sd)+“d”)
|sd==(-1)=(“第二”、“第二”)
|sd==0=(“第二个”、“d”)
|sd==1=(“秒”,“m”)
|sd==2=(“第二”,“M”)
|sd==3=(“第二”,“A”)
|sd==4=(“第二”,“AA”)
|sd>4=(“第二”,显示(abs sd)+“A”)
|gd==2 | sd…==。。。
|sd…=。。。
...
|mod gd 7==1 | mod sd 12==。。。
|mod sd 12==。。。
...
|否则=。。。
哪里
(bn1,acc1,oct1)=解析音高pt1
(bn2,acc2,oct2)=解析音高pt2
方向=符号sd
sd=位移插入长度pt1 pt2
gd=沃巴西债券方向bn1和bn2之间的abs$位移

提前感谢您的建议。

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

interval :: _ -> _ -> (String, String)
interval pt1 pt2
    | gd == 0 = doSomethingA pt1 pt2
    | gd == 1 = doSomethingB pt1 pt2
    | gd == 2 = doSomethingC pt1 pt2
    ...
然后,例如:

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  

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

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
我们的目标是将需要
n<12
的两个分支“组合”在一起,将这种情况考虑在内。(这在
原版的
玩具示例中并不是一个巨大的收益,但在更复杂的情况下可能是如此。)

我们可以天真地将代码分为两种嵌套情况:

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
然而,这却带来了惊喜:

> map wrong1 [1..12]
*** Exception: Non-exhaustive patterns in case

> map wrong2 [1..12]
*** Exception: Non-exhaustive guards in multi-way if
问题是当
n
1
时,将采用
n<12
分支,对内部案例进行评估,然后没有分支考虑
1
。原始
代码只是尝试下一个分支,由下一个分支处理它。但是,
error 1、error 2
不是回溯到外壳

请注意,当您知道外壳具有非重叠条件时,这不是问题。在OP发布的代码中,情况似乎是这样的,因此
errow1,errow2
方法将在那里起作用(如Jeffrey所示)

但是,对于可能存在重叠的一般情况如何?幸运的是,Haskell很懒,所以很容易滚动我们自己的控制结构。为此,我们可以利用
Maybe
monad,如下所示:

> map original [1..12]
[4,4,4,3,3,2,2,1,1,2,2,4]
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 ]
而且,如果需要“嵌套”大小写,只需将任何
返回值
替换为
msum[…]

这样做可以确保我们得到想要的回溯。事实上:

> map correct [1..12]
[4,4,4,3,3,2,2,1,1,2,2,4]
这里的技巧是,当
保护
失败时,它会生成一个
值。库函数
msum
仅选择列表中的第一个非
Nothing
值。因此,即使内部列表中的每一个元素都是<代码>没有< /代码>,外部<代码> MsAs>代码>将考虑外部列表中的下一个项目——回溯,如所需。

< P>这不是真正的标题问题的答案,而是您的特定应用程序。类似的方法将适用于许多其他问题,您可能希望使用这样的子保护

首先,我建议你开始时不要“严格打字”:

interval' :: PitchSpec -> PitchSpec -> Interval

data Interval = Unison PureQuality
              | Second IntvQuality
              | Third IntvQuality
              | Fourth PureQuality
              | ...

data IntvQuality = Major | Minor | OtherQual IntvDistortion
type PureQuality = Maybe IntvDistortion
data IntvDistortion = Augm Int | Dimin Int   -- should actually be Nat rather than Int
不管怎样,你的特殊任务可以通过“计算”这些值来完成,而不是与一堆数据进行比较 硬编码案例。基本上,您需要的是:

type RDegDiatonic = Int
type RDeg12edo = Rational  -- we need quarter-tones for neutral thirds etc., which aren't in 12-edo tuning

courseInterval :: RDegDiatonic -> (Interval, RDeg12edo)
courseInterval 0 = ( Unison undefined, 0   )
courseInterval 1 = ( Second undefined, 1.5 )
courseInterval 2 = ( Third undefined,  3.5 )
courseInterval 3 = ( Fourth undefined, 5   )
...
然后,您可以使用1将12edo大小与您给定的大小进行比较,从而“填充”这些未定义的间隔质量



1这里不需要类型类,我还可以为纯间隔和其他间隔定义两个不同名称的函数。

如果您看到第二段代码,OP也希望这样做:
mod gd 7==1
在第一次检查中。正如Sibi所观察到的,我需要测试多个条件,甚至是复杂的和“case”不会允许的。@AlbertoCapitani,
case
会允许的<代码>c1->r1的case()等@Jeffrey对您推荐的解决方案有一个小小的异议。以这种方式,我必须为每个可能的情况“发明”一个名称,而不是只指定逻辑条件。通过与函数的比较,我想要类似于lambda演算的东西(因此我更喜欢MultiWayIf解决方案)。请记住,此解决方案并不总是与原始代码等价:如果内部情况不匹配
> map correct [1..12]
[4,4,4,3,3,2,2,1,1,2,2,4]
interval' :: PitchSpec -> PitchSpec -> Interval

data Interval = Unison PureQuality
              | Second IntvQuality
              | Third IntvQuality
              | Fourth PureQuality
              | ...

data IntvQuality = Major | Minor | OtherQual IntvDistortion
type PureQuality = Maybe IntvDistortion
data IntvDistortion = Augm Int | Dimin Int   -- should actually be Nat rather than Int
type RDegDiatonic = Int
type RDeg12edo = Rational  -- we need quarter-tones for neutral thirds etc., which aren't in 12-edo tuning

courseInterval :: RDegDiatonic -> (Interval, RDeg12edo)
courseInterval 0 = ( Unison undefined, 0   )
courseInterval 1 = ( Second undefined, 1.5 )
courseInterval 2 = ( Third undefined,  3.5 )
courseInterval 3 = ( Fourth undefined, 5   )
...
class IntervalQuality q where
  qualityFrom12edoDiff :: RDeg12edo -> q

instance IntervalQuality PureQuality where
  qualityFrom12edoDiff n = case round n of
         0 -> Nothing
         n' | n'>0       -> Augm n
            | otherwise  -> Dimin n'
instance IntervalQuality IntvQuality where
  qualityFrom12edoDiff n | n > 1      = OtherQual . Augm $ floor n
                         | n < -1     = OtherQual . Dimin $ ceil n
                         | n > 0      = Major
                         | otherwise  = Minor
interval pt1 pt2 = case gd of
       0 -> Unison . qualityFrom12edoDiff $ sd - 0
       1 -> Second . qualityFrom12edoDiff $ sd - 1.5
       2 -> Third  . qualityFrom12edoDiff $ sd - 3.5
       3 -> Fourth . qualityFrom12edoDiff $ sd - 5
       ...