まず、Haskell のオーバーロードされたレコード フィールドを実装する計画があります。これにより、異なるレコードで同じ名前を使用できるようになり、コンパイラがそれを理解できない場合に、必要な名前を明示的に指定するだけで済みます。自力で出ます。
そうは言っても...
これに対処する最も信頼性が高く便利な方法は、メッセージ タイプごとに 1 つの Haskell タイプであることがわかりました。
次のようになります。
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
これには次のような利点があります。
- レコードを使用できます (別のプレフィックスを使用する必要がありますが、多くの場合、それで十分です)。
コンストラクター間でレコードを共有するよりもはるかに安全です。
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
特定のメッセージ タイプでのみ機能する関数を記述できるようになりました。
- メッセージ タイプのサブセット (たとえば、そのうちの 3 つ) でのみ機能する関数を記述できるようになりました。必要なのは、別の合計タイプだけです。
これを扱うコードはまだ非常に洗練されています:
process :: AnyMessage -> IO ()
process anyMsg = case anyMsg of
M1 (Message1 x y bs) -> ...
...
M3 Message3{ m3_a, m3_b } -> ... -- using NamedFieldPuns
私はこのパターンを本番環境で何度も使用しており、非常に堅牢なコードにつながります。