Реализация экземпляра Applicative для словарей (карта, связанные массивы)

Кажется простым реализовать экземпляр функтора (по сути, операцию отображения) для связанных массивов (например, см. Functor определение [1]). Однако экземпляр Applicative не определен. Есть ли веская теоретическая причина того, что Карты не являются аппликативами? Какие дополнительные ограничения необходимы для того, чтобы они были аппликативами?

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


person hyiltiz    schedule 14.08.2020    source источник
comment
Я не могу привести теоретический аргумент, но кажется очевидным, что вы, конечно, даже не можете, например. напишите общий pure :: v -> Map k v, который работает для любого k. (Предположительно, это будет одноэлементная карта, но какой ключ вы бы поместили в значение?) Также неясно, как, если бы вы решили эту проблему (что вы можете сделать, выбрав определенный тип или набор типов для k), вы бы реализовать <*>. По крайней мере, для меня это не так, тогда как с fmap есть очень очевидная вещь, которую вы можете сделать, и это работает.   -  person Robin Zigmond    schedule 15.08.2020
comment
@RobinZigmond есть «очевидный» способ поведения <*>: пересечение наборов ключей. Но это формирует действительный Applicative только в том случае, если pure генерирует постоянную карту, то есть такую, которая содержит все возможные ключи. Не очень практично!   -  person leftaroundabout    schedule 15.08.2020
comment
@leftaroundabout Справедливое замечание, я думал об этом после написания своего комментария, но не полностью обдумал его.   -  person Robin Zigmond    schedule 15.08.2020
comment
Это кажется практичным, поскольку есть простое конечное представление, но, возможно, оно бесполезно? @leftroundabout   -  person moonGoose    schedule 15.08.2020
comment
На самом деле это было бы точно так же, как экземпляр Applicative для ZipList, если кто-то думает о списках как о Maps, чьи ключи являются неотрицательными целыми числами. Теоретически это, конечно, кажется возможным, но, в отличие от бесконечных списков, я предполагаю, что общие бесконечные карты просто не будут работать на практике.   -  person Robin Zigmond    schedule 15.08.2020
comment
В соответствующем примечании пакеты semigroupoids дают экземпляр Ord k => Apply (Map k) с примечанием об отсутствии pure для аппликативного. Хотя вы можете легко написать структуру карты, которая обеспечит необходимую поддержку значения по умолчанию / для каждого несуществующего значения ключа, необходимого для pure, структура данных, предоставленная в базе, не имеет этого.   -  person moonGoose    schedule 15.08.2020
comment
Я хотел бы указать, что потенциально бесконечный Map k в основном всего на один шаг удален от (->) k(/MaybeT ((->) k)?), чей Applicative действительно имеет быстрое поведение.   -  person HTNW    schedule 15.08.2020
comment
@HTNW, да, ты вполне можешь это сделать. Проблема в том, что если у вас нет чего-то более сильного, чем просто Ord k, кажется, что реализация потенциально может привести к утечке пространства.   -  person dfeuer    schedule 15.08.2020
comment
@moonGoose Я имел в виду непрактично WRT для самого Data.Map, который действительно хранит каждую пару ключ/значение отдельно, поэтому для большинства типов pure x будет потреблять всю память для полного создания. Интересно, может ли лень прийти на помощь? Но, как вы сказали (я думаю?), более практичным способом было бы добавить оболочку Global x | Partial (Map k x), чтобы получить такое поведение.   -  person leftaroundabout    schedule 15.08.2020
comment
@leftroundabout Я думаю, что представление MapNormal (Map k v) | MapConstant v (Map k v) . Затем Map.singleton 0 0 == MapNormal (Map.fromList [(0,0)]), pure 0 & at 0 ?~ 1 == MapConstant 0 (Map.fromList [(0,1)]), т.е. вы можете представить карту (пустую или постоянную) с конечным числом примененных модификаций, тогда как ваше представление не может выполнить второй пример. Насколько я вижу, утечки пространства нет, с естественной реализацией для <*>.   -  person moonGoose    schedule 15.08.2020
comment
@HTNW Такая карта, изоморфная (->) k, представляет собой Representable: stackoverflow.com/questions/54261967/   -  person danidiaz    schedule 15.08.2020
comment
@moonGoose, ваш MapNormal нельзя редактировать так, как это может сделать Map.   -  person dfeuer    schedule 16.08.2020
comment
@dfeuer, не могли бы вы развить эту мысль? Я вижу, как вы не можете сделать, например. вещи с минимальными/максимальными элементами без Bounded k, теряют lookupXX и различные функции обхода/свертывания. Я думаю, вы все еще можете получить разумную функциональность поиска, модификации, объединения/пересечения?   -  person moonGoose    schedule 16.08.2020
comment
@moonGoose, основная проблема в том, что вы никогда не знаете, когда закончите с элементом по умолчанию. Это нормально, если домен бесконечен, но в противном случае нет.   -  person dfeuer    schedule 16.08.2020
comment
@dfeuer Я доверяю вашему опыту, но я не знаю, как вы пришли к такому выводу - не могли бы вы объяснить или указать на соответствующие ресурсы? Кроме того, это изоморфно Map (Maybe k) v, где значение по умолчанию необязательно присутствует в Nothing, будет ли это работать вместо этого? Т.е. является ли проблема в объявлении данных заданной или обязательно возникает при изменении структуры?   -  person moonGoose    schedule 16.08.2020
comment
@moonGoose, предположим, вы начинаете с pure enormousThing :: Mappish Int8 Whatever. Затем используйте insert для редактирования Mappish для каждого отдельного Int8. Теперь enormousThing никогда больше не будет использоваться, но Mappish этого не знает и должен держаться за него.   -  person dfeuer    schedule 16.08.2020


Ответы (1)


Как отмечалось в комментариях, вы не можете реализовать действительный экземпляр Applicative для 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, используя DefaultMap k (Maybe v) со значением по умолчанию Nothing и инвариантом постоянного сопоставления определенных ключей с Just.

Я думаю, что существует также instance Monad (DefaultMap k), благодаря изоморфизму с instance Monad ((->) k) или instance Monad (Stream k), поскольку, как и Stream, DefaultMap всегда бесконечно, тогда как возможно конечное ZipList не может иметь Monad случае, потому что он неизбежно нарушает закон ассоциативности a >=> (b >=> c) = (a >=> b) >=> c.

person Jon Purdy    schedule 18.08.2020