有没有可能写成"C大调"而不是"C大调"?

我在我的音乐项目中遇到了一个小的审美问题,它已经困扰了我一段时间了。

我有一个类型data Key = C | D | ...,我可以从KeyMode构建一个Scale。模式 "可以区分大调和小调音阶。

我可以将 "模式 "类型定义为一个从 "键 "到 "音阶 "的函数。在这种情况下,模式将有小写的名字(这很好),我可以得到一个像这样的音阶

aScale = major C

但音乐家们不会这样说话。他们把这个音阶称为_C大调_音阶,而不是_C大调_音阶。

我想的是

理想情况下,我想写的是

aScale = C major

这到底有没有可能?

我所尝试的

我可以让Key成为一个函数,从Mode构建一个Scale,所以我可以写出

aScale = c Major

但我不能把钥匙局限于构造音阶。其他事情也需要它们(例如构造和弦)。另外,"键 "应该是 "显示 "的一个实例。


当我使用一个额外的函数(或值构造器)时,我可以把Mode放在Key后面:

aScale = scale C majorscale ::Key -> Mode -> Scale

但额外的词scale看起来很吵,与它的名字相反,scale并不真正关心音阶。聪明的部分是在大调中,音阶实际上只是翻转($)


使用newtype Mode = Major | Minor ...除了scale'需要更智能外,并没有什么变化:

aScale = scale C Major
对该问题的评论 (5)

解决方案1:

使用这个

data Mode  = Major | Minor
data Scale = C Mode | D Mode | E Mode | F Mode | G Mode | A Mode | B Mode 

现在你可以写(大写C和大写M)了

aScale = C Major

解决方案2a:

这也是可能的

data Mode  = Major | Minor
data Key   = C | D | E | F | G | A | B 

data Scale = Scale Key Mode  

现在你写

aScale = Scale C Major

解决方案2b:

这也是可能的

data Mode  = Major | Minor
data Key   = C | D | E | F | G | A | B 

type Scale = (Key, Mode)  

现在你写

aScale = (C, Major)
评论(1)

这里有一个异想天开的解决方案,我不太推荐,但看起来很有 "音乐感":

infix 8 ♮
(♮) :: Key -> Mode -> Scale
(♮) = (Data.Function.&)
 -- ≡ flip ($)

然后你可以写

> C♮ major :: Scale

当然,这真正的目的是,你也会有F♯小调降B大调等等。

评论(2)

如果你不介意多一个运算符,你可以使用Data.Function中的&。假设major'是一个Key -> Scale'的函数,你可以写C & major'。这将产生一个音阶'值:

Prelude Data.Function> :t C & major
C & major :: Scale
评论(0)

已经有好几个好的答案了,但这里有一个延续传递风格的解决方案,可能会有帮助(也许不适合这个特殊的例子,但在其他想要某种反向应用语法的情况下)。

用一些问题域类型的标准定义。

data Mode = Major | Minor                 deriving (Show)
data Key = C | D | E | F | G | A | B      deriving (Show)
data Semitone = Flat | Natural | Sharp    deriving (Show)

data Note = Note Key Semitone             deriving (Show)
data Scale = Scale Note Mode              deriving (Show)
data Chord = Chord [Note]                 deriving (Show)

你可以引入一个延续传递类型。

type Cont a r = (a -> r) -> r

并写出原始的笔记构建类型来构建Cont类型,就像这样。

a, b, c :: Cont Note r
a = mkNote A
b = mkNote B
c = mkNote C
-- etc.
mkNote a f = f $ Note a Natural

flat, natural, sharp :: Note -> Cont Note r
flat    = mkSemi Flat
natural = mkSemi Natural
sharp   = mkSemi Sharp
mkSemi semi (Note k _) f = f $ Note k semi

然后,音阶、音符和和弦构建函数可以将Conts解析为后缀形式的纯类型(即作为连续型传递给Cont)。

major, minor :: Note -> Scale
major n = Scale n Major
minor n = Scale n Minor

note :: Note -> Note
note = id

或前缀形式(即以Conts作为参数)。

chord :: [Cont Note [Note]] -> Chord
chord = Chord . foldr step []
  where step f acc = f (:acc)

现在,你可以写了。

> c sharp note
Note C Sharp
> c note
Note C Natural
> c major
Scale (Note C Natural) Major
> b flat note
Note B Flat
> c sharp major
Scale (Note C Sharp) Major
> chord [a sharp, c]
Chord [Note A Sharp,Note C Natural]

请注意,"c "本身没有 "显示 "实例,但 "c note "有。

通过修改Note类型,你可以很容易地支持双意外符(例如,c sharp sharp,与d不同),等等。

评论(1)

但我不能把音键局限于音阶的构造。它们也需要用于其他的事情(例如构造和弦)。同时Key应该是Show的一个实例。

你可以使用类型类来巧妙地解决这个问题。

{-# LANGUAGE FlexibleInstances #-}

data Key = C | D | E | F | G | A | B deriving(Show)

data Mode = Major | Minor

data Scale = Scale Key Mode

class UsesKey t where
  c, d, e, f, g, a, b :: t

instance UsesKey Key where
  c = C
  d = D
  e = E
  f = F
  g = G
  a = A
  b = B

instance UsesKey (Mode -> Scale) where
  c = Scale C
  d = Scale D
  e = Scale E
  f = Scale F
  g = Scale G
  a = Scale A
  b = Scale B

aScale :: Scale
aScale = c Major

现在,你也可以通过定义适当的实例将小写字母用于其他类型。

评论(0)