¿Hay alguna posibilidad de escribir "C mayor" en lugar de "mayor C"?

39

Encontré un pequeño problema estético en mi proyecto de música y me ha estado molestando durante algún tiempo.

Tengo un tipo data Key = C | D | ...y puedo construir a Scalede a Keyy a Mode. El Modedistingue entre, por ejemplo, una escala mayor y una menor.

Puedo definir el Modetipo como una función de Keya Scale. En ese caso, los modos tendrán nombres en minúscula (lo cual está bien) y puedo obtener una escala como esta

aScale = major C

Pero los músicos no hablan así. Se refieren a esta escala como la escala C mayor , no la escala C mayor .

Lo que quiero

Idealmente me gustaría escribir

aScale = C major

¿Es esto posible en absoluto?

Lo que probé

Puedo hacer Keyuna función que construya a Scaledesde a Mode, así puedo escribir

aScale = c Major

Pero no puedo limitar Keys a construir escalas. También son necesarios para otras cosas (por ejemplo, construir acordes ). tambiénKey debería ser una instancia de Show.


Puedo poner el Mode después de Keycuando uso una función adicional (o constructor de valores):

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

Pero la escala de palabras adicional parece ruidosa y, al contrario de su nombre, scaleno se preocupa realmente por las escalas. La parte inteligente está adentro major, scalees realmente justa flip ($).


El uso de a newtype Mode = Major | Minor ...no cambia mucho, excepto que scalenecesita ser más inteligente:

aScale = scale C Major
Martin Drautzburg
fuente
3
Yo mismo me encontré con una sintaxis extremadamente similar en el pasado, pero TBH no vale la pena. Solo ve con major C.
Leftaroundabout
44
Al igual que una objeción musical: "Clave" es un nombre engañoso para ese tipo de datos, ya que, por ejemplo, C mayor y C menor son claves diferentes en la terminología estándar. "PitchClass" sería un nombre más preciso para el tipo.
PLL
2
@PLL De hecho, estoy teniendo problemas para encontrar un buen nombre para C, C #, D ... Sé que Euterpea usa PitchClass. Es más correcto que Key, pero no es "musical" en absoluto. En este momento estoy jugando con la idea de llamarlo Root o Tonic, aunque eso solo sugiere Acordes y Escalas. ¿Qué demonios llaman los músicos a eso: una nota sin octava?
Martin Drautzburg
44
@MartinDrautzburg: No diría que la clase de tono no es musical, no es solo hablar de programador de ninguna manera, se ha establecido en la teoría de la música como "una nota sin octava" desde al menos alrededor de mediados del siglo XX. No es muy común fuera de los contextos técnicos de la teoría de la música, pero eso es solo porque la distinción precisa entre "un tono" y "un tono sin octava" no es realmente necesaria a menudo en el uso diario, y cuando se necesita, generalmente es clara del contexto Pero "Root" o "Tonic" suenan bien como términos un poco más familiares, aunque menos precisos.
PLL
1
No, porque no funciona viceversa, un programador entrando en música
Emobe

Respuestas:

29

Solución 1:

Utilizar este

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

Ahora puedes escribir (con C mayúscula y M mayúscula)

aScale = C Major

Solución 2a:

Esto también es posible

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

data Scale = Scale Key Mode  

Ahora escribes

aScale = Scale C Major

Solución 2b:

Esto también es posible

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

type Scale = (Key, Mode)  

Ahora escribes

aScale = (C, Major)
Elmex80s
fuente
OMI con la solución 2 te servirá bien. Ríndete a la sintaxis de Haskell y crea un modelo limpio de tu dominio. Las cosas pueden ser agradables si lo haces
luqui
16

Aquí hay una solución caprichosa que realmente no recomiendo, pero se ve muy "musical":

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

Entonces puedes escribir

> C major :: Scale

Por supuesto, donde esto realmente está dirigido es que tú también tendrías F♯ minory B♭ majoretc.

a la izquierda
fuente
1
Me pregunto si hay algo como el espacio sin interrupción que está permitido como operador :)
chepner
26
@chepner en realidad sí: U + 2800 BRAILLE PATTERN BLANK se puede usar como infijo. No hace falta decir que esta es una idea horrible ... Todos los personajes espaciales reales están prohibidos como infijos, pero, como era de esperar, Unicode contiene algo que puede ser pirateado con el propósito de abuso.
Leftaroundabout
11

Si no le importa un operador adicional, puede usarlo &desde Data.Function. Suponiendo que majorsea ​​una función Key -> Scale, podrías escribir C & major. Eso produce un Scalevalor:

Prelude Data.Function> :t C & major
C & major :: Scale
Mark Seemann
fuente
4

Ya hay varias buenas respuestas, pero aquí hay una solución de estilo de paso continuo que puede ser útil (tal vez no para este ejemplo en particular, pero en otros contextos donde se desea una especie de sintaxis de aplicación inversa).

Con definiciones estándar para algunos tipos de dominio problemáticos:

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)

puede introducir un tipo de paso continuo:

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

y escriba los tipos primitivos de construcción de notas para construir Conttipos de esta manera:

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

Luego, las funciones de construcción de escala, nota y acorde pueden resolver los Conts en tipos simples en cualquier forma de postfijo (es decir, como continuaciones que se pasarán a Cont):

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

note :: Note -> Note
note = id

o forma de prefijo (es decir, tomando Conts como argumentos):

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

Ahora puedes escribir:

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

Tenga en cuenta que c sí no tiene una Showinstancia, pero c notesí.

Con una modificación al Notetipo, podría admitir fácilmente dobles accidentes (por ejemplo c sharp sharp, distintos de d), etc.

KA Buhr
fuente
Agradable. De hecho, intenté resolver mi problema Cont, sin embargo, intenté pegarlo a los constructores en A | B | C ...lugar de usar funciones. No pude hacer que esto funcionara y todavía no entiendo por qué, dado que los constructores de valores son solo funciones. Si puedo pegar una función delante de mis teclas, muchas cosas se vuelven posibles. Si la función es flip ($)entonces obtengo tu patrón flip ($) B :: Cont Key r. Mi original aScale = scale C Majorno es muy diferente.
Martin Drautzburg
3

Pero no puedo limitar Keys a construir escalas. También son necesarios para otras cosas (por ejemplo, construir acordes). También Key debería ser una instancia de Show.

Puede usar las clases de tipos para solucionar eso inteligentemente:

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

Ahora, puede usar las letras minúsculas para otros tipos también definiendo instancias apropiadas.

Joseph Sible-Reinstate a Monica
fuente