Как обрабатывать множество констант в Haskell?
Я работаю над библиотекой, позволяющей разработчику управлять Minitel (французский терминал видеотекса).
У меня много постоянных значений, и я хотел бы знать, как лучше всего управлять ими с помощью Haskell. Это распространенный вопрос среди начинающих, но я не нашел удовлетворительного ответа.
Вы можете взглянуть на мой проект(Примечание: да, слишком много констант только в одном модуле, вот над чем я работаю;-))
В настоящее время у меня есть модули, хранящие их как name = value
, Хотя это работает, я хотел бы знать, можно ли его усовершенствовать или я делаю правильно.
aNUL = 0x00 -- Null
-- ...
aUS = 0x1f -- Unit Separator
Этот метод имеет небольшой недостаток: вы не можете использовать сопоставление с образцом, вам нужно использовать охрану, если вы хотите сохранить имена:
completeReturn :: MString -> Bool
completeReturn [] = False
completeReturn [0x19] = False -- eSS2
completeReturn [0x1b, 0x5b, 0x32] = False -- eESC, eCSI, 0x32
completeReturn [0x1b, 0x5b, 0x34] = False -- eESC, eCSI, 0x34
completeReturn [0x19, 0x4b] = False -- eSS2, 0x4b ; cedilla
completeReturn _ = True
Вы также должны использовать параметры GHC, если вы не хотите, чтобы GHC кричал на вас за отсутствующие подписи или тип по умолчанию:
{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-type-defaults #-}
Я однажды попробовал это с data deriving Enum
использование трюка для компенсации неопределенных значений, но становится уродливым, как только значение не начинается с 0. Это также подвержено ошибкам, если вы пропустите или добавите одно значение, следующие имена будут иметь свои значения плюс или минус один:
data ASCII = NUL -- ^ 0x00, Null
-- ...
| US -- ^ 0x1f, Unit Separator
deriving (Enum, Show, Eq, Ord)
data C0 = NUL -- ^ 0x00, NULl
| Res01 -- ^ 0x01, undefined value
-- ...
| APA -- ^ 0x1f, Activate Position Address
deriving (Enum, Show, Eq, Ord)
data SSCFS = Res00 | Res01 | Res02 | Res03 | Res04 | Res05 | Res06 | Res07
-- ...
| Res38 | Res39 | Res3A | Res3B | Res3C | Res3D | Res3E | Res3F
| ABK -- ^ 0x40, Alpha BlacK
-- ...
| RMS -- ^ 0x5f
deriving (Enum, Show, Eq, Ord)
У этого решения есть недостаток: вы не можете легко смешивать значения в списке, потому что они бывают разных типов:
codes = [ASCII.NUL, ASCII.SOH, C0.APB, C0.APF, 0x24] -- Error!
Я подумал о другом решении:
class Value a where
value :: a -> Int
-- ASCII codes
data ASCII = NUL | SOH | STX | ETX {- ... -} deriving Show
instance Value ASCII where
value NUL = 0
value SOH = 1
-- ...
-- C0 codes
data C0 = APB | APF | APD | APU {- ... -} deriving Show
instance Value C0 where
value APB = 10
value APF = 11
-- ...
-- Mini type
data Mini = ASCII ASCII | C0 C0 | Literal Int deriving Show
instance Value Mini where
value (ASCII code) = value code
value (C0 code) = value code
value (Literal int) = int
codes = [ASCII NUL, C0 APB, Literal 0x20]
main = do
print (fmap value codes)
Для этого решения я должен позаботиться о том, чтобы конструкторы не перекрывались. Например, NUL, SO и SI существуют как в ASCII, так и в C0 (к счастью, они дают одинаковые значения:-)). Я могу справиться со случаем, только определив их, например, в ASCII. Использование квалифицированного импорта сделало бы вещи более уродливыми (ASCII ASCII.NUL
).
Видите ли вы другие лучшие способы справиться с этим делом?
2 ответа
Если у вас ghc 7.8, новый синонимы паттерна расширения языка (см. Раздел 7.3.8) элегантно решают эту проблему. Синонимы шаблона включаются с помощью прагмы LANGUAGE или -XPatternSynonyms
флаг.
{-# LANGUAGE PatternSynonyms #-}
Определение синонимов шаблона начинается с префикса pattern
pattern NUL = 0x00
pattern SSC = 0x19
pattern ESC = 0x1b
pattern US = 0x1f
pattern CSI = 0x5b
Мы можем написать ваш пример с точки зрения этих шаблонов.
type MString = [Int]
completeReturn :: MString -> Bool
completeReturn [] = False
completeReturn [SSC] = False -- eSS2
completeReturn [ESC , CSI , 0x32] = False -- eESC, eCSI, 0x32
completeReturn [ESC , CSI , 0x34] = False -- eESC, eCSI, 0x34
completeReturn [SSC , 0x4b] = False -- eSS2, 0x4b ; cedilla
completeReturn _ = True
Синонимы шаблона являются двунаправленными, поэтому мы также можем использовать их для построения выражений.
completeReturn [SSC]
Вы можете написать синонимы образца, которые захватывают переменные.
pattern EscCsi x = [ESC , CSI , x]
И использовать их как конструктор для сопоставления с образцом
completeReturn :: MString -> Bool
completeReturn [] = False
completeReturn [SSC] = False -- eSS2
completeReturn (EscCsi 0x32) = False -- eESC, eCSI, 0x32
completeReturn (EscCsi 0x34) = False -- eESC, eCSI, 0x34
completeReturn [SSC , 0x4b] = False -- eSS2, 0x4b ; cedilla
completeReturn _ = True
и для построения выражений.
completeReturn (EscCsi 0x7e)
Второе, что приходит на ум (после чего-то слишком уродливого) - это использовать Enum
не всегда выводя это. Так что вы можете использовать то же самое ASCII
Вы описали, но
instance Enum ASCII where
fromEnum a = case a of
NUL -> 0x00
...
US -> 0x1f
toEnum a = case a of
0x -> NUL
0x1f -> US
Я не знаком с шаблоном Haskell, но рискну предположить, что вы почти наверняка сможете написать что-нибудь, используя его, что позволит вам преобразовать таблицу одностороннего преобразования в fromEnum
и toEnum
,