Как обрабатывать множество констант в 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,

Другие вопросы по тегам