哈斯克尔的嵌套国家


9

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

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

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

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现在插入前一个令牌的行为。我想在上面的示例中对pre monwith 的状态进行建模StateWithTrigger,因为新状态和原始状态都是布尔值。
  • 一个and组合子。假设m1m2是监视器。然后m1 `and` m2是一个监视器,该监视器将令牌馈送给m1,然后馈送到m2,然后生成True两个答案是否均为真的信息。我想在上面的示例中对m1 `and` m2with 的状态进行建模CombinedState,因为必须保持两个监视器的状态。

仅供参考,_innerVal <$> get只是gets _innerVal(如gets f == liftM f get,和liftM只是fmap专用于单子)。
chepner

StateT InnerState m Int首先从哪里获得价值outerStateFoo
chepner

6
您对镜头舒适吗?这个用例似乎恰好zoom是针对的。
卡尔,

1
@卡尔我看过一些镜头,但不太了解。也许您可以在答案中解释如何使用缩放?
Agnishom Chattopadhyay

5
观察:此条目不包含单个问题。
Simon Shine

Answers:


4

正如卡尔提到的那样,第一个问题确实zoom来自lens您想要的。带镜头的代码可以这样写:

{-# 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

编辑:当我们在讨论时,如果您已经带进来,lensinnerStateFoo可以这样写:

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

5

对于上下文,这是我要使用此机器实现的目标:

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

我认为您想要实现的目标不需要太多的机械。

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)

这很酷,谢谢!这种模式叫什么吗?
Agnishom Chattopadhyay

3
我将其称为纯函数式编程!但我知道这是不是你要找的答案:) StreamTransformer事实上是一个“米利机” hackage.haskell.org/package/machines-0.7/docs/...
亚历山大Vieth

不,第一个输出消失不是我想要的。我想将第一个输出延迟为第二个输出。
Agnishom Chattopadhyay

2
依此类推,这样每个输出都会延迟一个步骤?可以做到的。
亚历山大·威斯

1
非常好,感谢您的发帖!(很抱歉以前发表评论而没有正确阅读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'))
尼斯,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.