【问题标题】:Implementing Applicative instance for dictionaries (Map, associated arrays)为字典实现 Applicative 实例(映射、关联数组)
【发布时间】:2020-08-14 22:30:20
【问题描述】:

为关联数组实现函子实例(本质上是映射操作)似乎很简单(例如,参见Functor 定义 [1])。但是,Applicative 实例未定义。地图不是应用程序是否有充分的理论理由?要成为 Applicative,还需要哪些额外的约束?

[1]https://hackage.haskell.org/package/containers-0.6.3.1/docs/Data-Map-Strict.html

【问题讨论】:

  • 我不能给出一个理论上的论据,但很明显你当然不能,例如。写一个适用于任何k 的通用pure :: v -> Map k v。 (它可能是一个单例映射,但你会将值放在哪个键上?)也不清楚如果你解决了这个问题(你可以通过为k 选择一个特定类型或一组类型),你会实现<*>。至少对我来说不是——而使​​用fmap 基本上你可以做一件非常明显的事情,它有效。
  • @RobinZigmond <*> 应该有一种“明显”的方式:键集的交集。但是,如果pure 生成一个常量映射,即包含每一个可能的键,那只会形成一个有效的Applicative。不太实用!
  • 在相关说明中,semigroupoids 包提供了一个 Ord k => Apply (Map k) 实例,并说明缺少 pure 用于应用程序。虽然您可以轻松编写 Map 结构,为 pure 所需的默认值/“在每个不存在的键”值提供必要的支持,但 base 中提供的数据结构没有它。
  • 我要指出,“可能无限 Map k”基本上只是从 (->) k(/MaybeT ((->) k)?) 中删除的一步,其 Applicative 确实具有“zippy”行为。
  • @HTNW,是的,你完全可以这样做。问题是,除非你有比Ord k 更强大的东西,否则实现似乎肯定会泄漏空间。

标签: haskell data-structures functional-programming separation-of-concerns


【解决方案1】:

正如人们在 cmets 中指出的那样,您无法为 Map 实现有效的 Applicative 实例,因为您无法以守法的方式实现 pure。由于恒等律,pure id <*> v = vpure 实现需要在将映射与函数应用程序相交时维护所有键。您不能对部分映射执行此操作,因为根据参数化,您可能在一个映射或另一个映射中没有键来召唤您需要在其中生成 b 的函数 a -> b 或参数 a生成的地图。 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 有点不寻常,因为您可以删除键值对,但只能通过有效地将它们“重置”为默认值,因为对给定键的查找总是会成功即使在删除相同的密钥之后。尽管您当然可以使用 DefaultMap k (Maybe v) 恢复类似于 Map 的部分行为的东西,默认值为 Nothing 和始终将定义的键映射到 Just 的不变量。

认为还有一个instance Monad (DefaultMap k),与instance Monad ((-&gt;) k)instance Monad (Stream k)同构,因为像Stream一样,DefaultMap总是无限的——而可能是有限的ZipList 不能有Monad 实例,因为它必然违反结合律a &gt;=&gt; (b &gt;=&gt; c) = (a &gt;=&gt; b) &gt;=&gt; c

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-26
    • 2012-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多