如何在 Haskell 中修改状态的一部分

How to modify parts of a State in Haskell

我有很多修改a的操作System。系统定义如下:

data System = Sys {
            sysId   :: Int,
            sysRand :: StdGen,
            sysProcesses :: ProcessDb,
            sysItems :: ItemDb
}  

例如

type ProcessDb = M.Map Int Process

但我也有一些功能,不需要访问完整的系统,但有这样的类型:

foo' :: (Process, ItemDb) -> ((Process, ItemDb),[Event])

目前我给他们的类型是

foo: System -> (System, [Event])

但这是一个不必要的宽泛界面。要将上面的窄界面与 System 结合使用,我必须从 System、运行 foo' 中提取单个 ProcessItemDb然后用结果修改 System

这是相当多的解包和包装,产生的代码行比仅将系统作为一个整体传递并让 foo 提取它需要的任何内容要多。在后一种情况下,包装和展开与实际的 foo' 操作混合在一起,我觉得这两个方面应该分开。

我想我需要某种提升操作,将狭窄的 foo' 变成 foo。我想我可以写这个,但我必须为窄函数的每个签名编写这样一个提升器,结果是很多不同的提升器。

一个常见的解决方案是使用 class,可能是由 Control.Lens.TH.makeClassy 的模板 Haskell 魔法创建的。要点是你传递了整个 System,但你不让函数 知道 那就是你给它的。它所允许知道的是,您提供的内容提供了获取 and/or 修改它应该处理的片段的方法。

我最终编写了一个适用于任何状态的函数,它需要一个 "Lens" 来捕获从较大状态到较小状态并返回的特定转换

focus :: (Lens s' s) -> State s' a -> State s a
focus lens ms'= do
    s <- get
    let (s', set) = lens s
        (a, s'')  = runState ms' s'
    put (set s'')
    return a

它让我可以写

run :: ExitP -> State SimState Log
...
   do
       evqs' <-focus onSys $ step (t,evt)
       ...

步骤在 "smaller" 状态下运行

step :: Timed Event -> State Sys.System [EventQu]

这里的 onSys 是一个 "Lens",它的工作原理是这样的:

onSys :: Lens Sys.System SimState 
onSys (Sis e s) = (s, Sis e)

哪里

data SimState = Sis {
            events  :: EventQu,
            sisSys  :: Sys.System

我想现有的 Lens 库遵循类似的方法,但更神奇,比如自动创建镜头。我确实回避镜头。相反,我很高兴地意识到只需要几行代码就可以得到我需要的东西。