#dictionary #haskell #error-handling
#словарь #haskell #обработка ошибок
Вопрос:
Я изо всех сил пытаюсь найти решение (отличное от использования Data.Map.fromList
и toList
) для следующей проблемы:
Преобразуйте ключ с помощью функции, которая возвращает Either String k
где, как обычно, ошибки выражаются как Left
, и все преобразование завершается неудачей, если одно преобразование ключа завершается неудачей.
import Data.Map.Strict
mapEitherKey :: (k -> Either String k') -> Map k a -> Either String (Map k' a)
Для правой части v
Map k v
это легко сделать с помощью mapM
, потому что
Map k
является экземпляром Monad
.
Но ни одна из функций отображения не предлагает ничего функционального в ключе. (Интересно, есть ли более глубокая причина, по которой, например, Map
это не экземпляр Bifunctor
. Я вижу, что это не тривиально, потому что необходимо учитывать столкновения ключей.)
Будем признательны за любую информацию.
Комментарии:
1. Это не может быть бифунктором из-за
Ord
ограничения на ключи. Кроме того, применение произвольной функции к ключам может нарушить инвариант дерева (функция может быть немонотонной или неинъекционной), поэтому нужно было бы построить новое дерево с нуля, эффективно используя что-то вродеfromList/toList
.2. Библиотека действительно предлагает
mapKeys :: Ord k2 => (k1 -> k2) -> Map k1 a -> Map k2 a
некоторую функциональность для ключей. Это O (n * log n) вместо O (n), потому что дерево необходимо перестроить. Если у вас есть монотонная функция, вы можете использоватьmapKeysMonotonic :: (k1 -> k2) -> Map k1 a -> Map k2 a
, которая выполняется в O (n) — будьте осторожны, чтобы не нарушить инвариант.
Ответ №1:
Я хотел бы поддержать то, что сказал @chi; если вы не знаете, что порядок сохранен, вам придется каждый раз заново вставлять ключ. Это означает, что toList
а затем повторная вставка с fromList
должна быть лучшей, которую вы можете получить асимптотически.
Однако, поскольку вы просили способ сделать это без этих функций, я хотел бы предложить использовать foldMapWithKey
.
import Data.Monoid (Ap(..))
import qualified Data.Map as M
mapEitherKey :: (Ord k, Ord k') => (k -> Either String k') -> Map k a -> Either String (Map k' a)
mapEitherKey f = getAp . M.foldMapWithKey (k v -> Ap (flip M.singleton v <$> f k))
Идея этого состоит в том, чтобы перенести реконструкцию карты с помощью ключа k'
(что мы делаем, объединяя кучу одиночных элементов) в Either String
аппликатив, который закорачивается, если он сталкивается с Left
.
Я написал это довольно быстро и грязно, но вы можете переработать его, чтобы сделать более читаемым. Это должно быть асимптотически идеально, но могут быть более эффективные подходы.
Обратите внимание, что вам нужны Ord
ограничения на k
и k'
.
Пример использования:
Prelude> f k = if k > 10 then Left "bad number" else Right $ show k
Prelude> mapEitherKey f (M.fromList [(0,0),(1,1),(2,2)])
Right (fromList [("0",0),("1",1),("2",2)])
Prelude> mapEitherKey f (M.fromList [(0,0),(1,1),(2,2),(11,11)])
Left "bad number"