やや異なる種類の状態を持つ状態マシンのファミリーを定義しようとしています。特に、より「複雑な」状態機械には、より単純な状態機械の状態を組み合わせることによって形成される状態があります。
(これは、オブジェクトがオブジェクトでもあるいくつかの属性を持つオブジェクト指向の設定に似ています。)
これが私が達成したいことの簡単な例です。
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 mon
の状態をStateWithTrigger
でモデル化したいと思います。これは、新しい状態が元の状態とともにブール値であるためです。and
コンビネーター。 m1
およびm2
がモニターであるとします。次に、m1 `and` m2
は、トークンをm1に、次にm2にフィードし、両方の答えがtrueの場合はTrue
を生成するモニターです。上記の例では、両方のモニターの状態を維持する必要があるため、m1 `and` m2
の状態をCombinedState
でモデル化したいと思います。最初の質問については、Carlが述べたように、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
編集:私たちがそれをしている間、あなたがすでにlens
を持っているなら、innerStateFoo
は次のように書くことができます:
innerStateFoo :: Monad m => StateT InnerState m Int
innerStateFoo = innerVal <<+= 1
コンテキストに関しては、これは私がこの機械で達成したいことです:
「ストリームトランスフォーマー」と呼ばれるこれらのものを設計したいと思います。これらは基本的にステートフルな機能です。トークンを消費し、内部状態を変更して、何かを出力します。具体的には、出力がブール値であるストリームトランスフォーマーのクラスに興味があります。これらを「モニター」と呼びます。
あなたが達成したいことはあまり機械を必要としないと私は思います。
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)