【问题标题】:Nested States in HaskellHaskell 中的嵌套状态
【发布时间】:2020-03-29 14:10:22
【问题描述】:

我正在尝试定义一个状态机系列,它们具有一些不同的状态。特别是,更“复杂”的状态机具有通过组合更简单状态机的状态而形成的状态。

(这类似于面向对象的设置,其中一个对象具有多个属性,这些属性也是对象。)

这是我想要实现的简化示例。

data InnerState = MkInnerState { _innerVal :: Int }

data OuterState = MkOuterState { _outerTrigger :: Bool, _inner :: InnerState }

innerStateFoo :: Monad m => StateT InnerState m Int
innerStateFoo = do
  i <- _innerVal <$> get
  put $ MkInnerState (i + 1)
  return i

outerStateFoo :: Monad m =>  StateT OuterState m Int
outerStateFoo = do
  b <- _outerTrigger <$> get
  if b
    then
       undefined
       -- Here I want to "invoke" innerStateFoo
       -- which should work/mutate things
        -- "as expected" without
       -- having to know about the outerState it
       -- is wrapped in
    else
       return 666

更一般地说,我想要一个通用框架,其中这些嵌套更复杂。这是我想知道的事情。

class LegalState s

data StateLess

data StateWithTrigger where
  StateWithTrigger :: LegalState s => Bool -- if this trigger is `True`, I want to use
                                   -> s    -- this state machine
                                   -> StateWithTrigger

data CombinedState where
  CombinedState :: LegalState s => [s] -- Here is a list of state machines.
                                -> CombinedState -- The combinedstate state machine runs each of them

instance LegalState StateLess
instance LegalState StateWithTrigger
instance LegalState CombinedState

liftToTrigger :: Monad m, LegalState s => StateT s m o -> StateT StateWithTrigger m o
liftToCombine :: Monad m, LegalState s => [StateT s m o] -> StateT CombinedState m o

就上下文而言,这就是我想用这台机器实现的目标:

我想设计这些叫做“流转换器”的东西,它们基本上是有状态的函数:它们消耗一个令牌,改变它们的内部状态并输出一些东西。具体来说,我对输出为布尔值的一类流转换器感兴趣;我们将这些称为“监视器”。

现在,我正在尝试为这些对象设计组合器。其中一些是:

  • pre 组合器。假设mon 是一个监视器。然后,pre mon 是一个监视器,它总是在第一个令牌被消耗后产生False,然后模仿mon 的行为,就好像现在正在插入前一个令牌一样。我想在上面的示例中使用StateWithTriggerpre mon 的状态进行建模,因为新状态与原始状态一样是布尔值。
  • and 组合器。假设m1m2 是监视器。然后,m1 `and` m2 是一个监视器,它将令牌提供给 m1,然后提供给 m2,如果两个答案都为真,则生成 True。我想在上面的示例中使用CombinedStatem1 `and` m2 的状态进行建模,因为必须保持两个监视器的状态。

【问题讨论】:

  • 仅供参考,_innerVal &lt;$&gt; get 只是 gets _innerVal(如 gets f == liftM f getliftM 只是 fmap 专门用于 monads)。
  • 您从哪里获得StateT InnerState m Int 值在outerStateFoo 中的首位?
  • 您对镜头感到满意吗?这个用例似乎正是 zoom 的用途。
  • @Carl 我看过一些镜头,但不是很了解。也许您可以在答案中解释如何使用缩放?
  • 观察:此条目不包含一个问题。

标签: haskell state monads monad-transformers state-monad


【解决方案1】:

对于您的第一个问题,正如 Carl 所提到的,来自 lenszoom 正是您想要的。你的镜头代码可以这样写:

{-# LANGUAGE TemplateHaskell #-}

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

newtype InnerState = MkInnerState { _innerVal :: Int }
  deriving (Eq, Ord, Read, Show)

data OuterState = MkOuterState
  { _outerTrigger :: Bool
  , _inner        :: InnerState
  } deriving (Eq, Ord, Read, Show)

makeLenses ''InnerState
makeLenses ''OuterState

innerStateFoo :: Monad m => StateT InnerState m Int
innerStateFoo = do
  i <- gets _innerVal
  put $ MkInnerState (i + 1)
  return i

outerStateFoo :: Monad m =>  StateT OuterState m Int
outerStateFoo = do
  b <- gets _outerTrigger
  if b
    then zoom inner $ innerStateFoo
    else pure 666

编辑:当我们这样做的时候,如果你已经引入了lens,那么innerStateFoo 可以这样写:

innerStateFoo :: Monad m => StateT InnerState m Int
innerStateFoo = innerVal <<+= 1

【讨论】:

    【解决方案2】:

    就上下文而言,这就是我想用这台机器实现的目标:

    我想设计这些叫做“流转换器”的东西,它们基本上是有状态的函数:它们消耗一个令牌,改变它们的内部状态并输出一些东西。具体来说,我对输出为布尔值的一类流转换器感兴趣;我们将这些称为“监视器”。

    我认为你想要达到的目标并不需要太多的机器。

    newtype StreamTransformer input output = StreamTransformer
      { runStreamTransformer :: input -> (output, StreamTransformer input output)
      }
    
    type Monitor input = StreamTransformer input Bool
    
    pre :: Monitor input -> Monitor input
    pre st = StreamTransformer $ \i ->
      -- NB: the first output of the stream transformer vanishes.
      -- Is that OK? Maybe this representation doesn't fit the spec?
      let (_, st') = runStreamTransformer st i
      in  (False, st')
    
    and :: Monitor input -> Monitor input -> Monitor input
    and left right = StreamTransformer $ \i ->
      let (bleft,  mleft)  = runStreamTransformer left  i
          (bright, mright) = runStreamTransformer right i
      in  (bleft && bright, mleft `and` mright)
    

    这个StreamTransformer 不是必要有状态的,但承认有状态的。您不需要(而且 IMO 不应该!在大多数情况下!!)为了定义这些类型类(或者确实永远!:),但这是另一个主题)。

    notStateful :: StreamTransformer input ()
    notStateful = StreamTransformer $ \_ -> ((), notStateful)
    
    stateful :: s -> (input -> s -> (output, s)) -> StreamTransformer input output
    stateful s k = StreamTransformer $ \input ->
      let (output, s') = k input s
      in  (output, stateful s' k)
    
    alternateBool :: Monitor anything
    alternateBool = stateful True $ \_ s -> (s, not s)
    

    【讨论】:

    • 我称之为纯函数式编程!但我知道这不是你要找的答案 :) StreamTransformer 实际上是一台“Mealy 机器”hackage.haskell.org/package/machines-0.7/docs/…
    • 不,第一个输出消失不是我想要的。我想延迟第一个输出是第二个。
    • 等等让每一个输出都延迟一步?这是可以做到的。
    • 非常好,感谢发帖! (很抱歉之前没有正确阅读 Q 就发表评论)。我认为 OP 的意思是 pre st = stateful (Nothing, st) k where k i (s,st) = let (o, st') = runStreamTransformer st i in ( maybe False id s , (Just o, st'))
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-13
    • 2014-12-16
    • 2017-02-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多