Алгебраический тип данных (предполагаемый) конфликт имен. Как? - PullRequest
0 голосов
/ 26 апреля 2019

У меня была идея реализовать свой собственный маленький модуль теории музыки в Haskell.

Начиная с примечаний (Note) имеет смысл, и тут же я столкнулся с этой неприятной синтаксической проблемой, я понятия не имею, как с ней справляются настоящие Хаскелеры.

data Note = Sharp NoteS | Flat NoteF deriving (Show)

data NoteS = 
    C | SC | D | SD | E | F | SF | G | SG | B
    deriving (Ord,Show,Eq)

data NoteF = 
    C | FD | D | FE | E | F | FG | G | FB | B
    deriving (Ord,Show,Eq)

instance Eq Note where
    (==) (NoteS n1) (NoteS n2) = n1 == n2
    (==) (NoteF n1) (NoteF n2) = n1 == n2
    (==) (NoteS n1) (NoteF n2) = ???
    (==) (NoteF n1) (NoteS n2) = ???
    ...

flatToSharp :: Note -> NoteS
sharpToFlat :: Note -> NoteF

Как многие могут знать, острый C и плоский D являются синонимами в целом, но иногда предпочитают использовать один или другой в зависимости от контекста. Поэтому я надеялся использовать тот факт, что и NoteS, и NoteF являются экземплярами Ord (например, для вычисления интервала). Но в обоих представлениях простые заметки (C, D, E, F ...) имеют одинаковые имена в обоих типах.

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

Итак, вот мой вопрос к профессионалам Haskell ... Как бы я сделал это в духе моей идеи, не слишком много уступок этой проблеме "пространства имен" Haskell?

Я пытался {-# LANGUAGE DuplicateRecordFields #-}, но это не помогает с профсоюзами, очевидно.

Ответы [ 2 ]

7 голосов
/ 26 апреля 2019

Если вам необходимо сохранить представление данных в том виде, в котором оно у вас есть, стандартный механизм пространств имен в Haskell - это модули.Таким образом, вы могли бы написать

module Sharps where data NoteS = ...

module Flats where data NoteF = ...

module Main where
import Sharps as S
import Flats as F

(Конечно, не забывайте, что для GHC каждый модуль должен идти в своем собственном файле с соответствующим именем файла.) Затем, в Main, вы можете обратиться кSharps.C или S.C для получения конструктора NoteS и Flats.C или F.C для получения конструктора NoteF.

Но могу ли я предложить другое решение?Как насчет именования натуральных нот и простого типа, который записывает, насколько вы резкие или плоские?В конце концов, в конце концов, вам захочется обрабатывать двойные острые и двойные квартиры, я уверен.Итак:

data Natural = A | B | C | D | E | F | G
data Note = Note
    { natural :: Natural
    , offset :: Int -- positive for sharp, negative for flat, say
    }

(Есть много других вариантов представления данных, которые вы также можете сделать.)

5 голосов
/ 26 апреля 2019

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

https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#pattern-synonyms

{-# LANGUAGE PatternSynonym #-}

data Note = C | SC | D | SD | E | F | SF | G | SG | B

pattern FD = SC
pattern FE = SD
pattern FG = SF
pattern FB = SG
...