5

このようなものを書く別の方法はありますか:

data Message = Message1 Int Int ByteString
             | Message2 Double Int Int
             | Message3 Double Double
             .....
             | Message256 CustomType

コンストラクターが多すぎて、レコード構文を使用するのが困難です。私が本当にやりたいのは、パーサーを書くことです。これに対する代替アプローチはありますか?

parse :: Bytestring -> Parser Message
4

1 に答える 1

2

まず、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
    

私はこのパターンを本番環境で何度も使用しており、非常に堅牢なコードにつながります。

于 2013-09-08T08:14:26.743 に答える