rub*_*oor 6 haskell state-monad lenses
在处理一个名为AppStateI want 的状态时,需要跟踪实例的数量.这些实例具有不同类型的ID InstanceId.
因此,我的州看起来像这样
import Control.Lens
data AppState = AppState
{ -- ...
, _instanceCounter :: Map InstanceId Integer
}
makeLenses ''AppState
Run Code Online (Sandbox Code Playgroud)
当没有计算具有给定id的实例时,跟踪计数的函数应该产生1,n + 1否则:
import Data.Map as Map
import Data.Map (Map)
countInstances :: InstanceId -> State AppState Integer
countInstances instanceId = do
instanceCounter %= incOrSetToOne
fromMaybe (error "This cannot logically happen.")
<$> use (instanceCounter . at instanceId)
where
incOrSetToOne :: Map InstanceId Integer -> Map InstanceId Integer
incOrSetToOne m = case Map.lookup instanceId m of
Just c -> Map.insert instanceId (c + 1) m
Nothing -> Map.insert instanceId 1 m
Run Code Online (Sandbox Code Playgroud)
虽然上面的代码有效,但希望有一种方法可以改进它.我不喜欢的:
instanceCounter两次唤起地图(首先是设置,然后是获取值)fromMaybe总是Just在预期的地方使用(所以我不妨使用fromJust)incOrSetToOne.原因是at不允许处理lookup收益率的情况,Nothing而是fmap结束Maybe.建议改进?
使用镜头的方法是:
countInstances :: InstanceId -> State AppState Integer
countInstances instanceId = instanceCounter . at instanceId . non 0 <+= 1
Run Code Online (Sandbox Code Playgroud)
这里的关键是使用 non
non :: Eq a => a -> Iso' (Maybe a) a
Run Code Online (Sandbox Code Playgroud)
这允许我们将instanceCounter Map中的缺失元素视为0
一种方法是使用<%=运算符。它允许您更改目标并返回结果:
import Control.Lens
import qualified Data.Map as M
import Data.Map (Map)
import Control.Monad.State
type InstanceId = Int
data AppState = AppState { _instanceCounter :: Map InstanceId Integer }
deriving Show
makeLenses ''AppState
countInstances :: InstanceId -> State AppState Integer
countInstances instanceId = do
Just i <- instanceCounter . at instanceId <%= Just . maybe 1 (+1)
return i
initialState :: AppState
initialState = AppState $ M.fromList [(1, 100), (3, 200)]
Run Code Online (Sandbox Code Playgroud)
它具有逻辑上应始终匹配的“部分”模式。
> runState (countInstances 1) initialState
(101,AppState {_instanceCounter = fromList [(1,101),(3,200)]})
> runState (countInstances 2) initialState
(1,AppState {_instanceCounter = fromList [(1,100),(2,1),(3,200)]})
> runState (countInstances 300) initialState
(201,AppState {_instanceCounter = fromList [(1,100),(3,201)]})
Run Code Online (Sandbox Code Playgroud)