我正在开发一个允许开发人员控制 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)。
你有没有其他更好的方法来处理这个案子?