use*_*628 6 dictionary haskell state-monad monad-transformers st-monad
我有一个计算,我在其中将值插入Map
,然后再次查找它们。我知道我从来没有在插入密钥之前使用过密钥,但是(!)
无论如何随意使用都会使我感到紧张。我正在寻找一种获取总查询功能的方法,该方法不会返回Maybe
,并且类型系统可以防止我意外滥用。
我的第一个想法是制作一个类似于的monad转换器StateT
,其中状态为a Map
,并且在monad中有用于插入和查找的特殊功能。插入函数返回一个新类型Receipt s k
,其中s
是ST
monad 样式的幻像索引类型,并且k
是键的类型,而查找函数则使用a 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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
编辑:一些评论者问我为什么要保留a Receipt
而不是实际值。我希望能够使用Receipt
in Set
和Map
s(我没有在MVCE中添加Eq
和Ord
实例Receipt
,但是我在项目中添加了它们),但是我的值Map
不相等。如果Receipt
用新的键值对替换,则必须Eq
为该对实现一个不诚实的实例,而忽略该值,然后对此我会感到不安。该Map
有保证,只有一个在任何给定的时间考虑值任我equatable“代理”键。
我想一个替代的解决方案,将工作就好了,我会是一个单子转换提供的供应Ref
s,其中data Ref v = Ref Int v
,与单子确保Ref
s的给出了独特Int
的ID,和Eq Ref
等仅看Int
(现在诚实由Int
s 的唯一性保证。我也会在野外接受指向这种转换器的指针。
您的解决方案类似于合理容器使用的技术来保证键存在于映射中。但也存在一些差异:
justified-containers使用连续传递风格。
在合理容器中插入新密钥需要“回收”现有收据以在更新的地图中工作。似乎您不需要“回收”收据,因为您永远不会同时拥有多个版本的地图。
合理容器所使用的技术的扩展描述可以在功能性珍珠“Ghosts of Dedeed Pros”中找到。
归档时间: |
|
查看次数: |
137 次 |
最近记录: |