7

我有一个地图,其中的关键是一种新型文本。我想自动(尽可能)为这个 Map 派生ToJSONFromJSONaeson已经为 Map Text v 提供了 ToJSON 和 FromJSON 的实例。

我的详细代码有效:

{-# LANGUAGE DeriveGeneric    #-}

module Test where

import           ClassyPrelude

import           Data.Aeson                  
import           GHC.Generics                (Generic)

import qualified Data.Map                    as M

newtype MyText = MyText {unMyText::Text} deriving (Eq, Ord)

data Bar = Bar deriving (Generic)
instance ToJSON Bar
instance FromJSON Bar

data Foo = Foo (Map MyText Bar)

instance ToJSON Foo where
  toJSON (Foo x) = toJSON mp
    where mp = M.fromList . map (\(x,y) -> (unMyText x,y)) . M.toList $ x

instance FromJSON Foo where
  parseJSON v = convert <$> parseJSON v
    where convert :: Map Text Bar -> Foo
          convert =  Foo . mapFromList . map (\(x,y) -> (MyText x,y)) . mapToList

我可以做更多类似以下的事情吗?

data Foo = Foo (Map MyText Bar) deriving (Generic)

instance ToJSON Foo 
instance FromJSON Foo

编辑

我试过(但仍然没有运气):

newtype MyText = MyText {unMyText::Text} deriving (Eq, Ord, ToJSON, FromJSON)
instance ToJSON Foo where
  toJSON (Foo x) = toJSON x

newtype MyText = MyText {unMyText::Text} deriving (Eq, Ord, ToJSON, FromJSON)
instance ToJSON Foo
4

1 回答 1

8

您无法自动派生此实例这一事实是 100% 正确的行为。您期望的不起作用的原因是无法知道实例FromJSON (Map Text v)可以用于 type 的值Map MyText v。这是因为 a 的创建和操作基于其键MapOrd实例,并且(对于编译器)没有办法知道所有 xy (x == y) == (MyText x == MyText y),这是安全强制Map Text v转换为Map MyText v. 从技术上讲,角色声明Map

type role Map nominal representational

从本质上讲,这Map k v仅对第一个类型参数相同的其他映射是强制的。维基说:

我们有实例 Coercible ab => Coercible (T a) (T b) 当且仅当第一个参数具有表示作用。

在最新版本的 GHC(7.8?)中,该类Coercible用于执行类型安全强制。有关类型角色及其在类型安全中的作用的更多信息,请参见此处。这

如果您计划为 派生实例Ord MyText,那么强制Map Text v转换为 确实是安全的Map MyText v,因为Ord实例是相同的。这需要使用unsafeCoerce. 但是,您仍然必须自己编写实例:

instance ToJSON v => ToJSON (Map MyText v) where
  toJSON = toJSON . (unsafeCoerce :: Map MyText v -> Map Text v)

instance FromJSON v => FromJSON (Map MyText v) where 
  parseJSON = (unsafeCoerce :: Parser (Map Text v) -> Parser (Map MyText v)) . parseJSON 

如果您打算编写自己的Ord实例,则上述内容绝对不安全。您的解决方案是正确的,但不是很有效。使用以下内容:

  toJSON = toJSON . M.mapKeys (coerce :: MyText -> Text)
  parseJSON = fmap (M.mapKeys (coerce :: Text -> MyText)) . parseJSON

根据您的 Ord 实例,您可能可以mapKeysMonotonic改用它,这样会更有效。当Data.Map你可以使用mapKeysMonotonic.

然后,显而易见的事情将起作用:

data Bar = Bar deriving (Eq, Ord, Generic)
instance ToJSON Bar
instance FromJSON Bar

data Foo = Foo (Map MyText Bar) deriving (Generic)
instance ToJSON Foo 
instance FromJSON Foo

-- Using GeneralizedNewtypeDeriving
newtype Foo2 = Foo2 (Map MyText Bar) deriving (FromJSON, ToJSON)

完整代码:

{-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving, FlexibleInstances #-}

module Test where

import Data.Aeson                  
import GHC.Generics (Generic)
import qualified Data.Map as M
import Data.Map (Map)
import Data.Text (Text)
import GHC.Prim (coerce)
import Unsafe.Coerce (unsafeCoerce)
import Data.Aeson.Types (Parser)

newtype MyText = MyText {unMyText::Text} deriving (Eq, Ord, Generic, ToJSON, FromJSON)

instance ToJSON v => ToJSON (Map MyText v) where
  -- toJSON = toJSON . M.mapKeys (coerce :: MyText -> Text)
  toJSON = toJSON . (unsafeCoerce :: Map MyText v -> Map Text v)

instance FromJSON v => FromJSON (Map MyText v) where 
  -- parseJSON x = fmap (M.mapKeys (coerce :: Text -> MyText)) (parseJSON x)
  parseJSON x = (unsafeCoerce :: Parser (Map Text v) -> Parser (Map MyText v)) (parseJSON x)

data Bar = Bar deriving (Eq, Ord, Generic)
instance ToJSON Bar
instance FromJSON Bar

data Foo = Foo (Map MyText Bar) deriving (Generic)
instance ToJSON Foo 
instance FromJSON Foo

newtype Foo2 = Foo2 (Map MyText Bar) deriving (FromJSON, ToJSON)
于 2014-10-20T10:07:23.750 回答