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 实现重载记录字段的计划,这将允许在不同的记录中使用相同的名称,并且您只需在编译器无法计算的情况下明确指定您想要的名称自己出去。

话虽如此 ...


我发现处理这个问题的最可靠和最方便的方法是每个消息类型一个 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 个)的函数:您所需要的只是另一个 sum 类型。
  • 处理这个的代码仍然很优雅:

    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 回答