J'ai rencontré un petit problème d'ordre esthétique dans mon projet musical et cela me tracasse depuis un certain temps.
J'ai un type data Key = C | D | ...
et je peux construire une Scale
à partir d'une Key
et d'un Mode
. Le Mode
fait la distinction entre, par exemple, une gamme majeure et une gamme mineure.
Je peux définir le type Mode
comme une fonction de Key
à Scale
. Dans ce cas, les modes auront des noms en minuscules (ce qui est bien) et je peux obtenir une échelle comme ceci
aScale = major C
Mais les musiciens ne parlent pas comme ça. Ils font référence à cette gamme comme étant la gamme de C majeur, et non la gamme de C majeur.
Ce que je veux
Idéalement, j'aimerais écrire
aScale = C major
Est-ce possible ?
What I tried
Je peux faire de Key
une fonction qui construit un Scale
à partir d'un Mode
, donc je peux écrire
aScale = c Major
Mais je ne peux pas limiter les clés à la construction de gammes. Elles sont nécessaires pour d'autres choses aussi (par exemple pour construire des accords). De plus, Key
devrait être une instance de Show
.
Je peux mettre le Mode
après le Key
quand j'utilise une fonction supplémentaire (ou un constructeur de valeur) :
aScale = gamme C majeur
avec scale : : Clé -> Mode -> Gamme
.
Mais le mot supplémentaire scale fait du bruit et contrairement à son nom, scale
n'est pas vraiment concerné par les gammes. La partie intelligente est dans major
, scale
est vraiment juste flip ($)
.
Utiliser un newtype Mode = Major | Minor ...
ne change pas grand-chose, sauf que scale
doit être plus intelligent :
aScale = scale C Major
Solution 1:
Utilisez cette
data Mode = Major | Minor
data Scale = C Mode | D Mode | E Mode | F Mode | G Mode | A Mode | B Mode
Vous pouvez maintenant écrire (avec un C majuscule et un M majuscule)
aScale = C Major
Solution 2a:
Ceci est également possible
data Mode = Major | Minor
data Key = C | D | E | F | G | A | B
data Scale = Scale Key Mode
Maintenant, vous écrivez
aScale = Scale C Major
Solution 2b:
Ceci est également possible
data Mode = Major | Minor
data Key = C | D | E | F | G | A | B
type Scale = (Key, Mode)
Maintenant, vous écrivez
aScale = (C, Major)
Voici une solution fantaisiste que je ne recommande pas vraiment, mais qui a l'air très "musicale" :
infix 8 ♮
(♮) :: Key -> Mode -> Scale
(♮) = (Data.Function.&)
-- ≡ flip ($)
Vous pouvez alors écrire
> C♮ major :: Scale
Bien sûr, là où cela est vraiment visé, c'est que vous auriez aussi F♯ mineur
et B♭ majeur
etc.
Si un opérateur supplémentaire ne vous dérange pas, vous pouvez utiliser "&" de "Data.Function". En supposant que "major" est une fonction "Key -> Scale", vous pourriez écrire "C & major". Cela produit une valeur d'"Echelle" :
Prelude Data.Function> :t C & major
C & major :: Scale
Il y a déjà plusieurs bonnes réponses, mais voici une solution de style "continuation passing" qui peut être utile (peut-être pas pour cet exemple particulier, mais dans d'autres contextes où une sorte de syntaxe d'application inversée est souhaitée).
Avec des définitions standard pour certains types de domaines problématiques :
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)
vous pouvez introduire un type de passage en continuation :
type Cont a r = (a -> r) -> r
et écrire les types primitifs de construction de notes pour construire des types "Cont" comme ça :
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
Ensuite, les fonctions de construction de gammes, de notes et d'accords peuvent résoudre les "Cont" en types simples sous la forme de postfix (c'est-à-dire comme des suites à passer au "Cont") :
major, minor :: Note -> Scale
major n = Scale n Major
minor n = Scale n Minor
note :: Note -> Note
note = id
ou la forme du préfixe (c'est-à-dire en prenant les "Cont" comme arguments) :
chord :: [Cont Note [Note]] -> Chord
chord = Chord . foldr step []
where step f acc = f (:acc)
Maintenant, vous pouvez écrire :
> 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]
Notez que "c" lui-même n'a pas d'instance "Show", mais "c note" en a une.
Avec une modification du type Note
, vous pourriez facilement supporter les doubles altérations (par exemple, c dièse dièse
, distinct de d
), etc.
Mais je ne peux pas limiter les clés à la construction d'échelles. Elles sont nécessaires pour d'autres choses aussi (par exemple pour construire des accords). La Tonalité doit également être une instance de Show.
Vous pouvez utiliser des classes de type pour contourner ce problème :
{-# 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
Vous pouvez maintenant utiliser les minuscules pour d'autres types de documents en définissant des instances appropriées.