Есть ли шанс написать "C Major" вместо "Major C"? - PullRequest
39 голосов
/ 31 января 2020

Я столкнулся с небольшой проблемой эстетики c в моем проекте musi c, и он некоторое время меня беспокоил.

У меня есть тип data Key = C | D | ..., и я могу создать Scale от Key и Mode. Mode различает, например, мажорную и минорную шкалу.

Я могу определить тип Mode как функцию от Key до Scale. В этом случае режимы будут иметь имена в нижнем регистре (это нормально), и я могу получить такой масштаб как 1011

aScale = major C
1015 * Но музыканты так не говорят. Они называют эту шкалу C мажорной шкалой, а не мажорной C шкалой.

Что я хочу

В идеале я бы хотел написать

aScale = C major

Возможно ли это вообще?

Что я пробовал

Я могу сделать Key функция, которая создает Scale из Mode, так что я могу написать

aScale = c Major

Но я не могу ограничить Keys для построения Scales. Они нужны и для других вещей (например, создание аккордов ). Также Key должен быть экземпляром Show.


Я могу поставить Mode после Key, когда я использую дополнительную функцию (или конструктор значений):

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

Но дополнительное слово scale выглядит шумно и вопреки своему названию, scale на самом деле не касается масштабов. Интеллектуальная часть в major, scale на самом деле просто flip ($).


Использование newtype Mode = Major | Minor ... на самом деле мало что меняет, за исключением того, что scale должен быть более интеллектуальным:

aScale = scale C Major

Ответы [ 5 ]

29 голосов
/ 31 января 2020

Решение 1:

Используйте это

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

Теперь вы можете написать (с заглавной C и заглавной М)

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)
16 голосов
/ 31 января 2020

Вот одно причудливое решение, которое я не очень рекомендую, но выглядит очень «музыкально»:

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

Тогда вы можете написать

> C♮ major :: Scale

Конечно, где это действительно нацелено на то, чтобы у вас также были F♯ minor и B♭ major et c ..

11 голосов
/ 31 января 2020

Если вы не возражаете против дополнительного оператора, вы можете использовать & из Data.Function. Предполагая, что major является функцией Key -> Scale, вы можете написать C & major. Это дает Scale значение:

Prelude Data.Function> :t C & major
C & major :: Scale
4 голосов
/ 01 февраля 2020

Уже есть несколько хороших ответов, но вот решение в стиле передачи продолжения, которое может быть полезным (возможно, не для этого конкретного примера, но в других контекстах, где требуется своего рода синтаксис обратного приложения).

Со стандартными определениями для некоторых типов проблемных областей:

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

Затем функции построения гаммы, ноты и аккордов могут преобразовывать Cont s в обычные типы в любой форме постфикса (т. е. как продолжения, передаваемые в * 1014). *):

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

note :: Note -> Note
note = id

или префиксная форма (т. Е. Принимая Cont s в качестве аргументов):

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 сам по себе не имеет экземпляра Show, но c note имеет.

С модификацией типа Note вы можете легко поддерживать двойные случайные действия (например, * 1031). * отличается от d) и др. c.

3 голосов
/ 02 февраля 2020

Но я не могу ограничить Ключи конструированием Весов. Они нужны и для других вещей (например, для создания аккордов). Кроме того, 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

Теперь вы можете использовать строчные буквы и для других типов, определение соответствующих экземпляров.

...