C'è qualche possibilità di scrivere "C major" invece di "major C"?

Ho incontrato un piccolo problema estetico nel mio progetto musicale e mi ha dato fastidio per qualche tempo.

Ho un tipo data Key = C | D | ... e posso costruire una Scale da una Key e un Mode. Il Mode distingue tra, per esempio, una scala maggiore e una minore.

Posso definire il tipo Mode come una funzione da Key a Scale. In questo caso i modi avranno nomi minuscoli (il che va bene) e posso ottenere una scala come questa

aScale = major C

Ma i musicisti non parlano così. Si riferiscono a questa scala come la scala C major, non la scala major C.

Cosa voglio

Idealmente vorrei scrivere

aScale = C major

È possibile?

Cosa ho provato

Posso fare di Key una funzione che costruisce una Scala da un Mode, quindi posso scrivere

aScale = c Major

Ma non posso limitare le chiavi alla costruzione di scale. Sono necessarie anche per altre cose (per esempio costruire accordi). Anche Key dovrebbe essere un'istanza di Show.


Posso mettere il Mode dopo il Key quando uso una funzione extra (o un costruttore di valori):

aScale = scala C major con scale :: Key -> Mode -> Scale.

Ma la parola extra scala sembra rumorosa e, contrariamente al suo nome, scale non si occupa veramente di scale. La parte intelligente è in major, scale è davvero solo flip ($).


Usare un newtype Mode = Major | Minor ...non cambia molto, tranne che scala deve essere più intelligente:

aScale = scale C Major
Commenti sulla domanda (5)

Soluzione 1:

Usare questo

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

Ora puoi scrivere (con la C maiuscola e la M maiuscola)

aScale = C Major

Soluzione 2a:

Anche questo è possibile

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

data Scale = Scale Key Mode  

Ora si scrive

aScale = Scale C Major

Soluzione 2b:

Anche questo è possibile

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

type Scale = (Key, Mode)  

Ora si scrive

aScale = (C, Major)
Commentari (1)

Ecco una soluzione stravagante che non consiglio, ma che sembra molto "musicale":

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

Poi si può scrivere

> C♮ major :: Scale

Naturalmente, dove questo è veramente mirato è che si avrebbe anche F♯ minore e B♭ maggiore ecc.

Commentari (2)

Se non ti dispiace un operatore extra, potresti usare & da Data.Function. Assumendo che major sia una funzione Key -> Scale, potresti scrivere C & major. Questo produce un valore Scale:

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

Ci sono già diverse buone risposte, ma ecco una soluzione di stile che può essere utile (forse non per questo particolare esempio, ma in altri contesti in cui si vuole una sorta di sintassi di applicazione inversa).

Con definizioni standard per alcuni tipi di dominio problematici:

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)

è possibile introdurre un tipo di passaggio continuo:

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

e scrivere i primitivi tipi di costruzione di note per costruire i tipi di "Contenuti" in questo modo:

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

Quindi, le funzioni di costruzione di scale, note e accordi possono risolvere i Cont's a tipi semplici in entrambe le forme postfix (cioè, come continuazioni da passare alCont's):

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

note :: Note -> Note
note = id

o forma di prefisso (cioè, prendendo come argomenti `Cont's):

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

Ora, puoi scrivere:

> 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]

Si noti che c in sé non ha un'istanza di Show, ma c note lo fa.

Con una modifica al tipo Note, si potrebbero facilmente supportare doppi incidenti (ad esempio, c sharp sharp, distinta da d), ecc.

Commentari (1)

Ma non posso limitarmi a costruire le Chiavi per costruire le Scale. Sono necessarie anche per altre cose (ad esempio per costruire accordi). Anche la chiave dovrebbe essere un'istanza di Show.

Si possono usare le classi tipografiche per aggirare abilmente questo problema:

{-# 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

Ora, è possibile utilizzare le lettere minuscole anche per altri tipi definendo le istanze appropriate.

Commentari (0)