我想发送一些JSON,它将包含从(Database.Persist.Sql)键到值的映射。显而易见的解决方案是使用Map (Key x) v
,但这不是ToJSON
的实例。
一些替代方案是:
Map k v
,返回Map Text v
(但此处我丢失了某些类型信息)type JsonKey = Text
和Map JsonKey v
,但这不是更好ToJSON
实例。后者似乎是需要较少变化的那个,而更清晰的变化。
另外,请注意我只需要ToJSON
个实例,而不是FromJSON
,所以我不需要整个往返。
所以,这就是我想写的:
import Database.Persist.Sql (Key)
instance ToJSON (ToJSON v => (Map (Key a) v))
假设Key
有一个友好的Show
实例(它没有,但这只是一个细节)......我正要写:
instance ToJSON (ToJSON v => (Map (Key a) v)) where
toJSON m = toJSON $ mapKeysMonotonic show m
但是我立即意识到这很糟糕(键就像整数一样):
> 9 < 10
True
> "9" < "10"
False
这违反了mapKeysMonotonic
前提条件。
现在,与此同时,我可以使用mapKeys
......但我试着想一想,违反这个的风险是什么? mapKeys
显然效率较低。我理解这可能是过早优化的原因。
但是我仍然想知道这可能会破坏的方式......或者如果它对我的有限用例确实安全的话。我唯一关心的是没有价值丢失。我不在乎他们的订单。
现在,this is instance (ToJSON v) => ToJSON (M.Map String v)
。它会将Data.Map
转换为Data.HashMap
依赖mapHashKeyVals
...使用Data.Map.foldrWithKey
我还想过使用HashMap (Key x) v
,以避免N*log(N)
计算或mapKeysMonotonic
。但我仍然定义了ToJSON实例,显然mapKeys
没有HashMap
(可以理解的是,因为这需要重新计算所有哈希值,但我&#39} ;我不确定这实际上是否比mapKeys
)
现在,我尝试了这个简单的例子:
> mapKeysMonotonic show $ fromList [(10,"a"), (9, "b"), (99, "c"), (100, "d")]
fromList [("9","b"),("10","a"),("99","c"),("100","d")]
并且由于show
为每个不同的String
返回了不同的Int
,显然没有值会丢失。地图现在可能不平衡......但后果是什么?我猜union
,difference
,intersection
可能行为不端......但是遍历和折叠呢?在所有情况下,这些仍然会保留所有元素吗?
instance ToJSON (ToJSON v => [(Key x, v)])
并将其转换为内部的Map
......但我想这将是一个重叠的实例
答案 0 :(得分:10)
您可以使用Map.showTree
检查地图的二叉树结构。 mapKeysMonotonic
函数保持相同的树顺序,只是替换节点,因此任何涉及搜索树的访问者都会得到错误的结果。
不搜索的遍历很可能会起作用,但使用这样的映射会违反数据结构的逻辑,这会导致混淆和错误。
要定义ToJSON
实例,您不需要构建中间地图;您可以使用foldrWithKey
直接构建Value
。请注意,Aeson中的Object
构造函数只是HashMap Text Value
。折叠本身是O(n)并且插入HashMap
是(实际上)恒定的时间。像这样:
instance ToJSON v => ToJSON (Map (Key a) v) where
toJSON = Object . Map.foldrWithKey f mempty
where f = HashMap.insert . Text.pack . show
答案 1 :(得分:0)
如果您无法保证该功能是单调的,您可以使用
mapKeys :: Ord k2 => (k1 -> k2) -> Map k1 a -> Map k2 a
这是O(n*log n)
,因此它比mapKeysMonotonic
慢O(n)
。但是,它只有log n
因子较慢,所以它可能已经足够了。
在任何情况下,我都不会违反Map
不变量:您可能会导致元素从地图中消失或被复制。