Data.Map.Map
约束, Ord key =>
不是逆变函子。但我们可以将它与辅助函数打包成一个存在主义:
{-# LANGUAGE GADTs #-}
import qualified Data.Map as M
import Data.Profunctor
data MapPK k v where
MapPK :: Ord i => (k -> i) -> M.Map i v -> MapPK k v
instance Profunctor MapPK where
dimap f g (MapPK ff mm) = MapPK (f `lmap` ff) (g `fmap` mm)
我使用了Profunctor
,但这是技术细节:lmap f = dimap f id
是contramap
。我还使用lmap
(->) a b
的{{1}}实例,如果它令人困惑,请抱歉。
MapPK
在实践中非常有用 - 它本质上是一个带有计算键的映射,因此它浪费了CPU周期但可以节省空间。我们可以为它实现Data.Map.lookup
:
lookupPK k (MapPK f m) = M.lookup (f k) m
我想把这个想法变成一个库。关于现代键值容器界面的任何提示?对于MapPK
通常有用,应该实现更多功能(例如插入 - 删除)。
然而,重用Data.Map
中的函数名称对我来说似乎很古怪。我应该考虑提供Data.Lens.At
个实例吗?
Data.Containers.IsMap
的更新:mono-traversable
无法实现,因为无法获取密钥列表。折叠/遍历也不能。我们甚至无法show
我们的“地图”。
从某种意义上说,MapPK
不再是普通地图,而是与type FunctionalMap k v = k -> Maybe v
一起属于类似奇怪地图的类别。
尽可能枚举值并不是很有用。如果f = const 5
,地图最多只包含一个值,那么内部Data.Map
中的大多数值都是垃圾,但我们无法知道。
即使我们能够神奇地反转f
这对我们没有多大帮助,因为逆并不总是存在,即使它存在非单调f
碰巧有问题反过来。
请注意,我们也获得了一些功能。与FunctionalMap
不同,我们的insert
可以像Data.Map
一样有效地实施。与Data.Map
不同,一些奇特的键是可能的 - 现在我们可以将函数作为键。但并非所有 - IO Int
都可以用作FunctionalMap
中的“关键字”,但不能用于MapPK
。