为关联数组实现函式实例(本质上是映射操作)似乎很简单(例如,请参阅Functor
定义1)。但是,没有定义Applicative
实例。地图不是应用语有一个很好的理论原因吗?他们需要什么额外的约束才能成为应用程序?
发布于 2020-08-17 16:13:14
正如人们在评论中指出的那样,您不能为Map
实现一个有效的Map
实例,因为您不能以一种守法的方式实现pure
。由于标识规则pure id <*> v
= v
,pure
实现需要维护所有键,同时将映射与函数应用程序相交。对于部分映射,您不能这样做,因为通过参数化,您可能在一个映射或另一个映射中没有一个键来调用函数a -> b
或参数a
,从而在结果映射中生成b
。pure x
需要像ZipList
那样工作(它使用repeat
),生成一个映射,将每个键映射到相同的值x
,但这在Map
中是不可能的,因为它是有限的。但是,使用允许无限映射的替代表示是可能的,例如基于函数和Eq
的映射。
-- Represent a map by its lookup function.
newtype EqMap k v = EM (k -> Maybe v)
-- Empty: map every key to ‘Nothing’.
emEmpty :: EqMap k v
emEmpty = EM (const Nothing)
-- Singleton: map the given key to ‘Just’ the given value,
-- and all other keys to ‘Nothing’.
emSingleton :: (Eq k) => k -> v -> EqMap k v
emSingleton k v = EM (\ k' -> if k == k' then Just v else Nothing)
-- Insertion: add an entry that overrides any earlier entry
-- for the same key to return ‘Just’ a new value.
emInsert :: (Eq k) => k -> v -> EqMap k v -> EqMap k v
emInsert k v (EM e) = EM (\ k' -> if k == k' then Just v else e k')
-- Deletion: add an entry that overrides any earlier entry
-- for the same key to return ‘Nothing’.
emDelete :: (Eq k) => k -> EqMap k v -> EqMap k v
emDelete k (EM e) = EM (\ k' -> if k == k' then Nothing else e k')
emLookup :: EqMap k v -> k -> Maybe v
emLookup (EM e) = e
instance Functor (EqMap k) where
-- Map over the return value of the lookup function.
fmap :: (a -> b) -> EqMap k a -> EqMap k v
fmap f (EM e) = EM (fmap (fmap f) e)
instance Applicative (EqMap k) where
-- Map all keys to a constant value.
pure :: a -> EqMap k a
pure x = EM (const (Just x))
-- Intersect two maps with application.
(<*>) :: EqMap k (a -> b) -> EqMap k a -> EqMap k b
fs <*> xs = EM (\ k -> emLookup k fs <*> emLookup k xs)
不幸的是,这不仅仅是无限的语义:当您添加或删除键值对时,它在内存中也会无限增长!这是因为条目是一个链接的闭包列表,而不是数据结构:您只能通过添加一个条目来删除映射中的值,就像版本控制系统中的还原一样。对于查找来说,效率也很低,因为查找的键数是线性的,而不是Map
的对数。它充其量是初学者-中级功能程序员的一个好的学术练习,只是为了了解如何用函数来表示事物。
这里的一个简单的替代方法是将不存在的键映射为常量值的“默认映射”。
data DefaultMap k v = DM v (Map k v)
dmLookup :: (Ord k) => k -> DefaultMap k v -> v
dmLookup k (DM d m) = fromMaybe d (Map.lookup k m)
-- …
然后,Applicative
的实现很简单:现有键的交集,加上与默认值一起应用的不存在的键。
instance Functor (DefaultMap k) where
-- Map over the return value of the lookup function.
fmap :: (a -> b) -> DefaultMap k a -> DefaultMap k b
fmap f (DM d m) = DM (f d) (fmap f m)
instance Applicative (DefaultMap k) where
-- Map all keys to a constant value.
pure x = DM x mempty
-- Intersect two maps with application, accounting for defaults.
DM df fs <*> DM dx xs = DM (df dx) $ Map.unions
[ Map.intersectionWith ($) fs xs
, fmap ($ dx) fs
, fmap (df $) xs
]
DefaultMap
有点不寻常,因为您可以删除键值对,但是只有通过有效地“重置”它们的默认值才能删除键值对,因为即使删除了相同的键,对给定密钥的查找也总是成功的。当然,您可以使用默认的Map
和始终将定义的键映射到Just
的DefaultMap k (Maybe v)
恢复类似于Just
的部分行为。
我认为也有一个instance Monad (DefaultMap k)
,通过与instance Monad ((->) k)
或instance Monad (Stream k)
的同构,因为与Stream
一样,DefaultMap
总是无限的,而可能有限的ZipList
不可能有Monad
实例,因为它必然违反了结合律a >=> (b >=> c)
= (a >=> b) >=> c
。
https://stackoverflow.com/questions/63420753
复制相似问题