模拟Haskell中的交互有状态对象 [英] Simulating interacting stateful objects in Haskell

查看:72
本文介绍了模拟Haskell中的交互有状态对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个Haskell程序,它涉及模拟一个抽象机器,它具有内部状态,接受输入并给出输出。我知道如何使用state monad来实现这个功能,这会产生更清洁,更易于管理的代码。



我的问题是我不知道如何拉同样的东西当我有两个(或更多)有状态的对象互相交互时就会诡计多端。下面我给出了一个高度简化版本的问题,并概括了我到目前为止的内容。



为了解决这个问题,我们假设一台机器的内部状态只包含一个单一的整数寄存器,所以它的数据类型是

$ $ p $ $ $ $ c $> Machine Machine = Register Int
deriving(Show)

(实际的机器可能有多个寄存器,程序指针,调用堆栈等等,但让我们现在不用担心。)在上一个问题之后,我知道如何使用state monad实现机器,这样我不必明确地通过它的内部状态。在这个简化的例子中,实现如下所示,在导入 Control.Monad.State.Lazy 之后:

  addToState :: Int  - > State Machine()
addToState i = do
(Register x)< - get
put $ Register(x + i)

getValue :: State Machine Int
getValue = do
(Register i)< - get
return

这让我可以写下如下内容:

  program :: State Machine Int 
program = do
addToState 6
addToState(-4)
getValue
$ b $ runProgram = evalState程序(Register 0)

这会在寄存器中加6,然后减4,然后返回结果。状态monad跟踪机器的内部状态,以便程序代码不必显式地跟踪它。



在命令式语言中面向对象的风格中,这个程序代码可能看起来像:

$ $ p $ $ $ c $ def def runProgram(machine)
machine.addToState(6)
machine.addToState(-4)
返回machine.getValue()

例如,如果我想模拟两台互相交互的机器,我可能会写:

  def doInteraction(machine1,machine2):
a = machine1.getValue()
machine1.addToState(-a)
machine2.addToState(a)
返回machine2.getValue()

它将 machine1 的状态设置为0,将其值添加到 machine2 的状态并返回结果。



我的问题很简单,在Haskell中编写这种命令式代码的典型方法是什么?最初我以为我需要链接两个状态monad,但在Benjamin Hodgson提示意见后,我意识到我应该可以用一个状态monad来完成,状态是一个包含两台机器的元组。



问题是,我不知道如何以一种很好的干净命令式来实现它。目前我有以下几种方法,但效果不好但很脆弱:

  interaction :: State(Machine,Machine)Int 
interaction = do
(m1,m2)< - get
let a = evalState(getValue)m1
let m1'= execState(addToState(-a))m1
让m2'= execState(addToState a)m2
let result = evalState(getValue)m2'
put $(m1',m2')
返回结果

doInteraction = runState交互(注册3,注册5)

类型签名 interaction :: State(Machine,Machine)Int 是Python函数声明 def doInteraction(machine1,machine2)的一个很好的直接翻译:,但该代码是脆弱的,因为我使用显式 let 绑定来使用线程状态通过函数。这需要我每次想要改变其中一台机器的状态时引入一个新名称,这意味着我必须手动跟踪哪个变量代表最新的状态。对于较长时间的交互,这可能会使代码容易出错并且难以编辑。



我期望结果与镜头有关。问题是我不知道如何在两台机器中的一台上运行monadic动作。镜头有一个运算符<<〜,其文档中显示运行一个单一动作,并将镜头的目标设置为其结果,但此动作在当前运行monad,状态为(Machine,Machine)而不是 Machine 。



所以在这一点上,我的问题是,我怎样才能以更强制/面向对象的风格实现上面的交互函数,使用状态单元或者其他一些技巧)隐式地跟踪两台机器的内部状态,而不必明确地传递状态?

最后,我意识到想写面向对象的代码在纯粹的函数式语言中可能是我做错了事的标志,所以我非常乐于以另一种方式来思考模拟多个有状态事物相互作用的问题。基本上我只想知道在Haskell中处理这类问题的正确方法。 解决方案

指示你实际上应该使用 System 数据类型来包装你的两台机器,然后你可以使用 lens

  { - #LANGUAGE TemplateHaskell,FlexibleContexts# - } 

导入Control.Lens
导入Control.Monad.State.Lazy

- 使用这些记录将很容易添加额外的机器或注册
- 无需重构任何后面的
数据的代码Machine = Machine {_register :: Int}派生(显示)
data系统= System {_machine1,_machine2 :: Machine}派生(显示)

- 这是一些TemplateHaskell魔术,它使特殊的`register`,`machine1`,
和`machine2`功能。
makeLenses''Machine
makeLenses''System


doInteraction :: MonadState System m => m int
doInteraction = do
a < - use(machine1.register)
machine1.register - = a
machine2.register + = a
use(machine2。注册)

另外,为了测试这段代码,我们可以检查GHCi它是否做我们想要的:

  ghci> runState doInteraction(System(Machine 3)(Machine 4))
(7,System {_machine1 = Machine {_register = 0},_machine2 = Machine {_register = 7}})



优点:




  • lens ,如果我决定添加额外的字段,将不会有重构。例如,假设我想要第三个机器,那么我只需要更改 System

      data System = System 
    {_machine1,_machine2,_machine3 :: Machine}派生(显示)

    但是现有代码中没有其他内容会改变 - 现在我可以使用 machine3 就像我使用 machine1 machine2


  • c $ c> lens ,我可以更容易地缩放到嵌套结构。请注意,我完全避免了简单的 addToState getValue 函数。由于 Lens 实际上只是一个函数, machine1.register 只是常规的函数组合。例如,假设我想让一台机器现在拥有一个数组的寄存器,那么获取或设置特定寄存器仍然很简单。我们只修改机器 doInteraction

     导入Data.Array.Unboxed(UArray)
    数据Machine = Machine {_registers :: UArray Int Int}派生(显示)

    - 代码剪切

    doInteraction2 :: MonadState System m => m int
    doInteraction2 = do
    只需一个< - 预先使用(machine1.registers.ix 2) - 获得machine1上的第三个注册
    machine1.registers.ix 2 - = a - 修改machine1上的第三个注册
    machine2.registers.ix 1 + = a - 修改机器上的第二个注册2
    只需b < - 预先使用(machine2.registers.ix 1) - 获得machine2上的第二个注册
    return b

    请注意,这相当于在Python中具有如下函数:

      def doInteraction2(machine1,machine2):
    a = machine1.registers [2]
    machine1.registers [ 2] - = a
    machine2.registers [1] + = a
    b = machine2.registers [1]
    返回b

    您可以在GHCi上再次测试:

      ghci> import Data.Array.IArray(listArray)
    ghci> let regs1 = listArray(0,3)[0,0,6,0]
    ghci>让regs2 = listArray(0,3)[0,7,3,0]
    ghci> runState doInteraction(System(Machine regs1)(Machine regs2))
    (13,System {_machine1 = Machine {_registers = array(0,3)[(0,0),(1,0),(2, 0),(3,0)]},_machine2 = Machine {_registers = array(0,3)[(0,0),(1,13),(2,3),(3,0)]}} )




编辑



OP已指定他想要将状态机a 嵌入到状态系统a中。一如既往,镜头具有这样的功能,如果你深入挖掘。 缩放 (及其兄弟 magnify )为缩小状态 / 读者 State 并放大到 Reader )。



然后,如果我们想要实现 doInteraction ,同时保留黑箱 getValue addToState ,我们得到

  getValue :: State Machine Int 
addToState :: Int - > State Machine()

doInteraction3 :: State System Int
doInteraction3 = do
a < - zoom machine1 getValue - 调用状态为`machine1`的`getValue`
放大machine1(addToState(-a)) - 调用带有状态`machine1`的`addToState(-a)`
放大machine2(addToState a) - 调用状态为`machine2`的`addToState a`
zoom machine2 getValue - 调用状态为`machine2`的`getValue`

注意,如果我们这样做我们确实必须承诺一个特定的状态monad变换器(与通用的 MonadState 不同),因为并不是所有的存储方式都必须以这种方式可缩放 。也就是说, RWST 是由 zoom 支持的另一个状态单子变换器。


I'm currently writing a Haskell program that involves simulating an abstract machine, which has internal state, takes input and gives output. I know how to implement this using the state monad, which results in much cleaner and more manageable code.

My problem is that I don't know how to pull the same trick when I have two (or more) stateful objects interacting with one another. Below I give a highly simplified version of the problem and sketch out what I have so far.

For the sake of this question, let's assume a machine's internal state consists only of a single integer register, so that its data type is

data Machine = Register Int
        deriving (Show)

(The actual machine might have multiple registers, a program pointer, a call stack etc. etc., but let's not worry about that for now.) After a previous question I know how to implement the machine using the state monad, so that I don't have to explicitly pass its internal state around. In this simplified example the implementation looks like this, after importing Control.Monad.State.Lazy:

addToState :: Int -> State Machine ()
addToState i = do
        (Register x) <- get
        put $ Register (x + i)

getValue :: State Machine Int
getValue = do
        (Register i) <- get
        return i

This allows me to write things like

program :: State Machine Int
program = do
        addToState 6
        addToState (-4)
        getValue

runProgram = evalState program (Register 0)

This adds 6 to the register, then subtracts 4, then returns the result. The state monad keeps track of the machine's internal state so that the "program" code doesn't have to explicitly track it.

In object oriented style in an imperative language, this "program" code might look like

def runProgram(machine):
    machine.addToState(6)
    machine.addToState(-4)
    return machine.getValue()

In that case, if I want to simulate two machines interacting with each other I might write

def doInteraction(machine1, machine2):
    a = machine1.getValue()
    machine1.addToState(-a)
    machine2.addToState(a)
    return machine2.getValue()

which sets machine1's state to 0, adding its value onto machine2's state and returning the result.

My question is simply, what is the paradigmatic way to write this kind of imperative code in Haskell? Originally I thought I needed to chain two state monads, but after a hint by Benjamin Hodgson in the comments I realised I should be able to do it with a single state monad where the state is a tuple containing both machines.

The problem is that I don't know how to implement this in a nice clean imperative style. Currently I have the following, which works but is inelegant and fragile:

interaction :: State (Machine, Machine) Int
interaction = do
        (m1, m2) <- get
        let a = evalState (getValue) m1
        let m1' = execState (addToState (-a)) m1
        let m2' = execState (addToState a) m2
        let result = evalState (getValue) m2'
        put $ (m1',m2')
        return result

doInteraction = runState interaction (Register 3, Register 5)

The type signature interaction :: State (Machine, Machine) Int is a nice direct translation of the Python function declaration def doInteraction(machine1, machine2):, but the code is fragile because I resorted to threading state through the functions using explicit let bindings. This requires me to introduce a new name every time I want to change the state of one of the machines, which in turn means I have to manually keep track of which variable represents the most up-to-date state. For longer interactions this is likely to make the code error-prone and hard to edit.

I expect that the result will have something to do with lenses. The problem is that I don't know how to run a monadic action on only one of the two machines. Lenses has an operator <<~ whose documentation says "Run a monadic action, and set the target of Lens to its result", but this action gets run in the current monad, where the state is type (Machine, Machine) rather than Machine.

So at this point my question is, how can I implement the interaction function above in a more imperative / object-oriented style, using state monads (or some other trick) to implicitly keep track of the internal states of the two machines, without having to pass the states around explicitly?

Finally, I realise that wanting to write object oriented code in a pure functional language might be a sign that I'm doing something wrong, so I'm very open to being shown another way to think about the problem of simulating multiple stateful things interacting with each other. Basically I just want to know the "right way" to approach this sort of problem in Haskell.

解决方案

I think good practice would dictate that you should actually make a System data type to wrap your two machines, and then you might as well use lens.

{-# LANGUAGE TemplateHaskell, FlexibleContexts #-}

import Control.Lens
import Control.Monad.State.Lazy

-- With these records, it will be very easy to add extra machines or registers
-- without having to refactor any of the code that follows
data Machine = Machine { _register :: Int } deriving (Show)
data System = System { _machine1, _machine2 :: Machine } deriving (Show)

-- This is some TemplateHaskell magic that makes special `register`, `machine1`,
-- and `machine2` functions.
makeLenses ''Machine
makeLenses ''System


doInteraction :: MonadState System m => m Int
doInteraction = do
    a <- use (machine1.register)
    machine1.register -= a
    machine2.register += a
    use (machine2.register)

Also, just to test this code, we can check at GHCi that it does what we want:

ghci> runState doInteraction (System (Machine 3) (Machine 4))
(7,System {_machine1 = Machine {_register = 0}, _machine2 = Machine {_register = 7}})

Advantages:

  • By using records and lens, there will be no refactoring if I decide to add extra fields. For example, say I want a third machine, then all I do is change System:

    data System = System
      { _machine1, _machine2, _machine3 :: Machine } deriving (Show)
    

    But nothing else in my existing code will change - just now I will be able to use machine3 like I use machine1 and machine2.

  • By using lens, I can scale more easily to nested structures. Note that I just avoided the very simple addToState and getValue functions completely. Since a Lens is actually just a function, machine1.register is just regular function composition. For example, lets say I want a machine to now have an array of registers, then getting or setting particular registers is still simple. We just modify Machine and doInteraction:

    import Data.Array.Unboxed (UArray)
    data Machine = Machine { _registers :: UArray Int Int } deriving (Show)
    
    -- code snipped
    
    doInteraction2 :: MonadState System m => m Int
    doInteraction2 = do
        Just a <- preuse (machine1.registers.ix 2) -- get 3rd reg on machine1
        machine1.registers.ix 2 -= a               -- modify 3rd reg on machine1
        machine2.registers.ix 1 += a               -- modify 2nd reg on machine2
        Just b <- preuse (machine2.registers.ix 1) -- get 2nd reg on machine2
        return b
    

    Note that this is equivalent to having a function like the following in Python:

    def doInteraction2(machine1,machine2):
      a = machine1.registers[2]
      machine1.registers[2] -= a
      machine2.registers[1] += a
      b = machine2.registers[1]
      return b
    

    You can again test this out on GHCi:

    ghci> import Data.Array.IArray (listArray)
    ghci> let regs1 = listArray (0,3) [0,0,6,0]
    ghci> let regs2 = listArray (0,3) [0,7,3,0]
    ghci> runState doInteraction (System (Machine regs1) (Machine regs2))
    (13,System {_machine1 = Machine {_registers = array (0,3) [(0,0),(1,0),(2,0),(3,0)]}, _machine2 = Machine {_registers = array (0,3) [(0,0),(1,13),(2,3),(3,0)]}})
    

EDIT

The OP has specified that he would like to have a way of embedding a State Machine a into a State System a. lens, as always, has such a function if you go digging deep enough. zoom (and its sibling magnify) provide facilities for "zooming" out/in of State/Reader (it only makes sense to zoom out of State and magnify into Reader).

Then, if we want to implement doInteraction while keeping as black boxes getValue and addToState, we get

getValue :: State Machine Int
addToState :: Int -> State Machine ()

doInteraction3 :: State System Int
doInteraction3 = do
  a <- zoom machine1 getValue     -- call `getValue` with state `machine1`
  zoom machine1 (addToState (-a)) -- call `addToState (-a)` with state `machine1` 
  zoom machine2 (addToState a)    -- call `addToState a` with state `machine2`
  zoom machine2 getValue          -- call `getValue` with state `machine2`

Notice however that if we do this we really must commit to a particular state monad transformer (as opposed to the generic MonadState), since not all ways of storing state are going to be necessarily "zoomable" in this way. That said, RWST is another state monad transformer supported by zoom.

这篇关于模拟Haskell中的交互有状态对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆