I’m working on a library allowing a developper to control a Minitel (the french videotex terminal).
I have a lot of constant values and I would like to know the best way to manage them with Haskell. It's a common question among beginners but I haven't found a satisfying answer.
You can have a look at my project (Note: yes, there are too many constants in only one module, that's what I'm working on ;-) )
I currently have modules keeping them as name = value
. Though it works, I
would like to know if it can be perfected or if I'm doing right.
aNUL = 0x00 -- Null
-- ...
aUS = 0x1f -- Unit Separator
This method has a minor drawback: you cannot use pattern matching, you need to use guards if you want to keep the names:
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
You must also use GHC options if you don't want GHC to yell at you for the missing signatures or the type defaults:
{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-type-defaults #-}
I once tried it with data deriving Enum
using the trick to compensate
undefined values but it becomes ugly as soon as the value does not start at 0.
It is also error prone, if you omit or add one value, the following names will
have their values plus or minus one:
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)
This solution has a drawback: you cannot easily mix the values in a list because they are of different types:
codes = [ASCII.NUL, ASCII.SOH, C0.APB, C0.APF, 0x24] -- Error!
I thought of another solution:
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)
For this solution, I must take care that constructors don't overlap. For
example, NUL, SO and SI exist in both ASCII and C0 (They fortunately give the
same values :-) ). I can handle the case by only defining them in ASCII for
example. Using qualified import would make things uglier (ASCII ASCII.NUL
).
Do you see other better ways to handle this case ?
ASCII ASCII.NUL
would be replaced byASCII ASCII_NUL
(the ASCII type must be encapsulated in the Mini type if I want to create lists combining many types) – Feverwort