Уже есть несколько хороших ответов, но вот решение в стиле передачи продолжения, которое может быть полезным (возможно, не для этого конкретного примера, но в других контекстах, где требуется своего рода синтаксис обратного приложения).
Со стандартными определениями для некоторых типов проблемных областей:
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.