每种类型有多个实例:如何做得更好?

时间:2019-07-02 12:22:04

标签: haskell

我有一些需要ToJSON实例的数据类型,但是我需要将这些对象发送到期望不同表示形式的不同梨。因此,每种数据类型我需要多个(不同)实例。如何实现呢?我只知道用newtype包装的一种方式。

是否可以在不使用newtype的情况下将相同模块/程序中相同类型的不同实例用于相同类型?我也想到了类似的东西:

data Peer = PeerA | PeerB | ...

class AsJSON peer a where
  asJSON :: peer -> a -> Value

此类AsJSON将替换ToJSON:我将需要实现这样的实例,但要使用我自己的类约束的AsJSON a =>而不是任何地方的标准ToJSON a =>

那么,什么是最好的方式(您认为更可取)呢?您通常如何解决此类任务?

1 个答案:

答案 0 :(得分:2)

您有一些选择。

最简单的选择是避免类型类并简单地编写函数。这具有简单性的好处。但是,这使得编写抽象函数更加困难。考虑函数callEndpoint_ :: ToJSON a => a -> Url -> IO ()。必须重写此函数以使其具有类型签名callEndpoint_ :: (a -> Value) -> a -> IO (),如果您有一整堆这些通用函数,则必须手动传递该函数。

您的解决方案类似,您必须传递一个Peer值才能调用asJson;您不妨绕过该功能。

新类型解决了大多数此类问题。您可以编写通用函数,然后将数据包装在newtype中,并将其传递给该函数,它将自动调用正确的实例方法。

另一种解决方案是使用幻像类型。 https://wiki.haskell.org/Phantom_type涵盖了基础知识,但您实际上应该编写:

data MyData a = MyData Int String Float --'a' is NOT used in the right hand side.

data Peer = PeerA | PeerB

instance ToJSON (MyData PeerA) where 
    ...
instance ToJSON (MyData PeerB) where
    ...

这将需要启用FlexibleInstances扩展名(可能还需要启用FlexibleContexts)。

您还可以使用KindSignaturesDataKinds来使此操作更加严格;那么您可以编写data MyData (a :: Peer) = ...,然后编译器将允许您编写签名MyData PeerA,但不能编写签名MyData Int