ハスケルの入れ子になった州


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常に生成Falsemonれ、前のトークンが今挿入されているかのような動作を模倣するモニターです。上の例ではpre monwith の状態をモデル化したいと思いStateWithTriggerます。新しい状態は元の状態と一緒にブール値だからです。
  • andコンビネータ。それがモニターであるm1としm2ます。次に、m1 `and` m2トークンをm1に送り、次にm2に送り、True両方の答えが真であるかどうかを生成するモニターです。両方のモニターの状態を維持する必要があるため、上の例でm1 `and` m2with の状態をモデル化したいと思いCombinedStateます。

FYI、_innerVal <$> getちょうどであるgets _innerVal(とgets f == liftM f get、とliftMだけされてfmapモナドに特化しました)。
chepner

StateT InnerState m Intそもそもどこで価値を得ているのouterStateFooですか?
chepner

6
レンズは快適ですか?このユースケースはまさにそのzoomためのものです。
Carl

1
@Carlいくつかのレンズを見たことがありますが、よくわかりません。たぶんあなたは答えをズームの使い方を説明できますか?
Agnishom Chattopadhyay

5
観察:このエントリには単一の質問は含まれていません。
Simon Shine

回答:


4

カールが述べたように、最初の質問については、zoomfrom 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

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

いいえ、最初の出力の消失は、私が意図したものではありません。最初の出力を2番目の出力に遅延させたい。
Agnishom Chattopadhyay

2
そして、すべての出力が1ステップ遅れるように?それを行うことができます。
Alexander Vieth

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'))
Will Ness、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.