当两个结构共享某些内容时,是否有一种惯用的方法来处理这种情况?

Is there an idiomatic way to do deal with this situation when two structures share some content?

我正在制作一个玩具论坛以熟悉 Haskell 和 Servant。

我的 API 看起来像这样:

type UserAPI = "messages" :> ReqBody '[JSON] Msg :> Header "X-Real-IP" String :> Post '[JSON] APIMessage
               :<|> "messages" :> ReqBody '[JSON] Int :> Get '[JSON] [Msg']

我的类型看起来像这样:

data Msg = Msg
  { thread :: Int
  , dname :: String
  , contents :: String
  } deriving (Eq, Show, Generic)
data Msg' = Msg'
  { thread' :: Int
  , stamp' :: UTCTime
  , dname' :: String
  , contents' :: String
  , ip' :: String
  } deriving (Eq, Show, Generic)

他们派生了ToJSON / FromJSON / FromRow个实例,非常方便。

Msg 表示 API 在接收消息时期望的数据和 Msg' 它在查询消息时发送的数据,其中有两个由服务器添加的附加字段,但这感觉不对,必须有一种更简洁的方法来实现这一点。

对处理此类问题的惯用方法的任何见解表示赞赏。

我会在这里考虑你的问题更像是一个概念性的问题(“当我有两种共享某种结构的数据类型时我能做什么?”)而不是简单的“我如何在 [=58= 中建模继承” ]?”已经回复 here.

要回答您的问题,您需要考虑的不仅仅是数据的结构。例如,如果我为您提供 A 和 B,并且如果我声明

data A = A Int String
data B = B Int 

我怀疑你会自动假设 AB 加上一个额外的 String。您可能会尝试弄清楚这两个数据结构之间的确切关系。这是一件好事。

如果 A 的每个实例实际上都可以看作是 B 的一个实例,那么提供在您的代码中表示它的方法可能是相关的。然后你可以使用一个简单的 Haskell 方法和

data A = A { super :: B, theString :: String }
data B = B { id :: Int }

显然,如果不创建一些其他函数,使用这些数据类型并不容易。例如,fromB 函数可能是相关的

fromB :: B -> String -> A 
toB   :: A -> B

而且你也可以使用typeclass来访问id

class HasId a where
    getId :: a -> Int

instance HasId A where
    getId = id . super 

在这里,一些帮助形式 Lens can be useful. And the answer to this question How do I model inheritance in Haskell? 是一个好的开始。 Lens 包提供面向对象的语法糖来处理继承关系。

但是您也可以发现 A 并不完全是 B,但它们具有相同的祖先。你可能更愿意创建类似

的东西
data A = A { share :: C, theString :: String }
data B = B { share :: C }
data C = C Int

当您不想将 A 用作 B 时就是这种情况,但它存在一些可供两者使用的功能。实现会和前面的案例差不多,就不解释了。

最后你会发现实际上并不存在有用的关系(因此,AB 之间没有共享的真正存在的函数)。那么您更愿意保留您的代码。

在您的具体情况下,我认为 MsgMsg' 之间没有直接的“”关系,因为一个是接收,另一个用于发送。但它们可以共享一个共同的祖先,因为它们都是消息。所以他们可能会有一些共同的构造函数和访问器(就 OO 编程而言)。

永远不要忘记结构总是绑定到某些函数。而范畴论告诉我们的是,你不能只看结构,还必须考虑它们的功能,才能看到彼此之间的关系。