First, there is a Plan for implementing overloaded record fields for Haskell, which allows you to use the same name in different records, and you will need to explicitly specify which one you want, in cases where the compiler cannot figure it out by oneself.
It is said ...
I found the most reliable and convenient way to handle this: one Haskell type for the message type .
You will have:
data Message1 = Message1 Int Int ByteString -- can use records here data Message2 = Message2 Double Int Int data Message3 = Message3 { m3_a :: Double, m3_b :: Double } -- ..... data Message256 = Message256 CustomType -- A sum type over all possible message types: data AnyMessage = M1 Message1 | M2 Message2 | M3 Message3 -- ... | M256 Message256
Benefits of this include:
- You can use entries (use different prefixes anyway, but this is often good enough)
This is much safer than sharing records between constructors:
data T = A { field :: Int } | B { field :: Int } | C { bla :: Double } -- no field record print (field (C 2.3)) -- will crash at runtime, no compiler warning
Now you can write functions that work only with certain types of messages.
- Now you can write functions that work only with a subset (for example, 3 of them) of message types: all you need is a different type of sum.
The code related to this is still pretty elegant:
process :: AnyMessage -> IO () process anyMsg = case anyMsg of M1 (Message1 xy bs) -> ... ... M3 Message3{ m3_a, m3_b } -> ... -- using NamedFieldPuns
I have used this template several times in production, and this leads to very reliable code.
source share