用于在地图上进行插入和总查找的Monad转换器?

时间:2019-05-16 07:42:04

标签: dictionary haskell monad-transformers state-monad st-monad

我进行了一次计算,将值插入Map中,然后再次查找它们。我知道我从来没有在插入密钥之前使用过密钥,但是无论如何随意使用(!)会让我感到紧张。我正在寻找一种获取不返回Maybe且类型系统可以防止我意外滥用的总查询功能的方法。

我的第一个想法是制作一个类似于StateT的monad转换器,其中状态为Map,并且在monad中有用于插入和查找的特殊功能。 insert函数返回一个Receipt s k新类型,其中s是一种ST monad样式的幻像索引类型,而k是键的类型,并进行查找函数使用Receipt而不是裸键。通过隐藏Receipt构造函数并使用类似于runST的量化运行函数,这应确保仅在插入同一映射后才进行查找。 (完整代码如下。)

但是我担心我已经重新发明了轮子,或者担心还有一种替代的方法来获取已经使用的安全的总地图查找。在某个地方的公共包装中是否存在针对此问题的现有技术?

{-# LANGUAGE DeriveFunctor, LambdaCase, RankNTypes #-}

module KeyedStateT (KeyedStateT, Receipt, insert, lookup, receiptToKey, runKeyedStateT)
where

import Prelude hiding (lookup)
import Control.Arrow ((&&&))
import Control.Monad (ap, (>=>))
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (fromJust)

newtype KeyedStateT s k v m a = KeyedStateT (Map k v -> m (a, Map k v)) deriving Functor

keyedState :: Applicative m => (Map k v -> (a, Map k v)) -> KeyedStateT s k v m a
keyedState f = KeyedStateT (pure . f)

instance Monad m => Applicative (KeyedStateT s k v m) where
  pure = keyedState . (,)
  (<*>) = ap

instance Monad m => Monad (KeyedStateT s k v m) where
  KeyedStateT m >>= f = KeyedStateT $ m >=> uncurry ((\(KeyedStateT m') -> m') . f)

newtype Receipt s k = Receipt { receiptToKey :: k }

insert :: (Applicative m, Ord k) => k -> v -> KeyedStateT s k v m (Receipt s k)
insert k v = keyedState $ const (Receipt k) &&& Map.insert k v

lookup :: (Applicative m, Ord k) => Receipt s k -> KeyedStateT s k v m v
lookup (Receipt k) = keyedState $ (Map.! k) &&& id

runKeyedStateT :: (forall s. KeyedStateT s k v m a) -> m (a, Map k v)
runKeyedStateT (KeyedStateT m) = m Map.empty
module Main where

import Data.Functor.Identity (runIdentity)
import qualified KeyedStateT as KS

main = putStrLn . fst . runIdentity $ KS.runKeyedStateT $ do
  one <- KS.insert 1 "hello"
  two <- KS.insert 2 " world"
  h <- KS.lookup one
  w <- KS.lookup two
  pure $ h ++ w

编辑:一些评论者问我为什么要保留Receipt而不是实际值。我希望能够在Receipt s和Set s中使用Map(我没有为{{1}添加EqOrd实例}},但是我的Receipt中的值不能相等。如果我用键值对newtype替换了Map,我将不得不为那个忽略值的对实现一个不诚实的Receipt实例,然后我对此会感到不安。 Eq可以确保在任何给定时间,我的任何等价“代理”键都只考虑一个值。

我想一个对我来说很好用的替代解决方案是monad变压器,它提供Map s,其中Ref的电源,而monad确保data Ref v = Ref Int v s仅使用Ref给出了唯一的Int ID和Eq Ref等ID(现在,Int的唯一性保证了诚实性)。我也会在野外接受指向这种转换器的指针。

1 个答案:

答案 0 :(得分:3)

您的解决方案类似于justified-containers使用的技术,以确保键在地图中存在。但是有一些区别:

在功能性珍珠justified-containers中可以找到"Ghosts of departed proofs"使用的技术的扩展说明。