我有一些需要ToJSON
实例的数据类型,但是我需要将这些对象发送到期望不同表示形式的不同梨。因此,每种数据类型我需要多个(不同)实例。如何实现呢?我只知道用newtype
包装的一种方式。
是否可以在不使用newtype
的情况下将相同模块/程序中相同类型的不同实例用于相同类型?我也想到了类似的东西:
data Peer = PeerA | PeerB | ...
class AsJSON peer a where
asJSON :: peer -> a -> Value
此类AsJSON
将替换ToJSON
:我将需要实现这样的实例,但要使用我自己的类约束的AsJSON a =>
而不是任何地方的标准ToJSON a =>
。
那么,什么是最好的方式(您认为更可取)呢?您通常如何解决此类任务?
答案 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
)。
您还可以使用KindSignatures
和DataKinds
来使此操作更加严格;那么您可以编写data MyData (a :: Peer) = ...
,然后编译器将允许您编写签名MyData PeerA
,但不能编写签名MyData Int
。