Я столкнулся с небольшой эстетической проблемой в моем музыкальном проекте, и это беспокоило меня в течение некоторого времени.
У меня есть тип data Key = C | D | ...
и я могу построитьМасштаб
изКлюча
иМод
. «Режим» различает, например,. крупный и второстепенный масштаб.
Я могу определить тип Mode
как функцию от Key
до Scale
. В этом случае режимы будут иметь строчные имена (что хорошо), и я могу получить такую шкалу
aScale = major C
Но музыканты так не говорят. Они называют эту шкалу C major, а не major C.
Что я хочу
В идеале я бы хотел написать
aScale = C major
Возможно ли это вообще?
Что я пробовал
Я могу сделать Key
функцией, которая создает Scale
из Mode
, чтобы я мог писать
aScale = c Major
Но я не могу ограничить Ключи построением Весов. Они нужны и для других вещей (например,. строительство аккордов ). Также «Ключ» должен быть экземпляром «Показать».
Я могу поместить Mode
после Key
, когда использую дополнительную функцию (или конструктор значений):
aScale = масштаб C major
с scale :: Key - > Режим - > Масштаб
Но дополнительное слово scale выглядит шумно и вопреки своему названию, «scale» на самом деле не касается весов. Интеллектуальная часть в «major», «scale» на самом деле просто «flip ($)».
Использование newtype Mode = Major | Minor ...
на самом деле мало что меняет, кроме scale
должен быть более умным:
aScale = scale C Major
Решение 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
Решение 2а:
Это также возможно
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)
Вот одно причудливое решение, которое я не очень рекомендую, но выглядит очень «музыкально»:
infix 8 ♮
(♮) :: Key -> Mode -> Scale
(♮) = (Data.Function.&)
-- ≡ flip ($)
Тогда ты можешь написать
> C♮ major :: Scale
Конечно, это действительно нацелено на то, что у вас также будут «F♯ minor» и «B ₣ major» и т. Д..
Если вы не возражаете против дополнительного оператора, вы можете использовать &
из Data.Function
. Предполагая, что major
является функцией Key - > Масштаб
, вы можете написатьC & майор
. Это производит значение Scale
:
Prelude Data.Function> :t C & major
C & major :: Scale
Уже есть несколько хороших ответов, но вот решение для прохождения стиля продолжения, которое может быть полезным (возможно, не для этого конкретного примера, а в других контекстах, где требуется своего рода синтаксис обратного приложения).
Со стандартными определениями для некоторых типов проблемных доменов:
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 для простых типов в любой форме postfix (т.е., как продолжение, которое должно быть передано в Cont
):
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
есть.
С модификацией типа «Примечание» вы можете легко поддерживать двойные случайные (например,., c острый острый
, отличается от d
) и т. д.
Но я не могу ограничить Ключи построением Весов. Они нужны и для других вещей (например,. построение аккордов). Также Ключ должен быть экземпляром Шоу.
Вы можете использовать классы типов, чтобы ловко обойти это:
{-# 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
Теперь вы можете использовать строчные буквы и для других типов, определяя соответствующие экземпляры.