Я не знаю ни одного, который вам предоставлен, но at
принадлежит классу типов At
, поэтому мы могли бы написать его сами.Чтобы не запачкать руки гибкими (и, возможно, перекрывающимися) расширениями экземпляров, мы сделаем это в новом типе.
newtype AList k v = AList [(k, v)]
Сначала нам понадобится пара экземпляров семейства.
{-# LANGUAGE TypeFamilies #-}
type instance IxValue (AList k v) = v
type instance Index (AList k v) = k
Это просто определяет, что такое «ключ» и «значение» в нашем новом типе, что просто.Теперь нам нужно иметь возможность читать и записывать значения по определенному ключу.Haskell уже дает нам способ чтения значений (Data.List.lookup
), но мы должны сами сделать функцию записи.Здесь нет ничего необычного или объективного: только обычные старые фильтры и карты Haskell.
replaceAt :: Eq k => k -> Maybe v -> AList k v -> AList k v
replaceAt k Nothing (AList m) = AList $ filter (\(k', _) -> k /= k') m
replaceAt k (Just v) (AList m) =
case lookup k m of
Nothing ->
-- Not present in the list; add it
AList ((k, v) : m)
Just _ ->
-- Present; replace it
AList $ map (\(k', v') -> if k == k' then (k', v) else (k', v')) m
Теперь нам нужно написать экземпляр At
, который зависит от экземпляра Ixed
.К счастью, библиотека линз обеспечивает реализацию по умолчанию для Ixed
, поскольку мы реализуем At
, поэтому объявление первого экземпляра просто.
instance Eq k => Ixed (AList k v)
Запись at
также довольно проста.,Просто посмотрите на типы и немного следите за своим носом, и мы приходим к реализации, к которой мы стремимся.
instance Eq k => At (AList k v) where
at k f (AList m) = fmap (\v' -> replaceAt k v' (AList m)) $ f (lookup k m)
И мы закончили.Теперь at
будет работать для AList
.Если вас беспокоит обертка нового типа, вы можете довольно легко создать новую функцию (at'
, если хотите), которая выполняет упаковку / распаковку нового типа.
Доказательство того, что этот экземпляр удовлетворяет законам объектива, осталосьв качестве упражнения для читателя.
Полный код
{-# LANGUAGE TypeFamilies #-}
import Control.Lens.At
import Data.List(lookup)
newtype AList k v = AList [(k, v)]
type instance IxValue (AList k v) = v
type instance Index (AList k v) = k
replaceAt :: Eq k => k -> Maybe v -> AList k v -> AList k v
replaceAt k Nothing (AList m) = AList $ filter (\(k', _) -> k /= k') m
replaceAt k (Just v) (AList m) =
case lookup k m of
Nothing ->
-- Not present in the list; add it
AList ((k, v) : m)
Just _ ->
-- Present; replace it
AList $ map (\(k', v') -> if k == k' then (k', v) else (k', v')) m
-- Just take the default implementation here.
instance Eq k => Ixed (AList k v)
instance Eq k => At (AList k v) where
at k f (AList m) = fmap (\v' -> replaceAt k v' (AList m)) $ f (lookup k m)