Вложенные состояния в Haskell

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

Для контекста это то, чего я хочу достичь с помощью этого механизма:

Я хочу создать такие вещи, как «потоковые трансформеры», которые в основном являются функциями с состоянием: они потребляют токен, изменяют свое внутреннее состояние и что-то выводят. В частности, меня интересует класс Stream Transformers, где выводом является логическое значение; мы будем называть эти «мониторы».

Сейчас я пытаюсь разработать комбинаторы для этих объектов. Некоторые из них:

  • preКомбинатор. Предположим, что monэто монитор. Затем pre monэто монитор, который всегда выдает Falseпосле использования первого токена, а затем имитирует поведение, monкак будто предыдущий токен вставляется сейчас. Я хотел бы смоделировать состояние pre monс StateWithTriggerв приведенном выше примере, так как новое состояние является логическим, наряду с исходным состоянием.
  • andКомбинатор. Предположим, что m1и m2есть мониторы. Затем m1 `and` m2монитор, который подает токен в m1, а затем в m2, а затем производитTrue если оба ответа верны. Я хотел бы смоделировать состояние m1 `and` m2с CombinedStateв приведенном выше примере, так как необходимо поддерживать состояние обоих мониторов.
Агнишом Чаттопадхяй
источник
К вашему сведению, _innerVal <$> getэто просто gets _innerVal(как gets f == liftM f getи liftMтолько fmapспециализируется на монады).
Chepner
Где вы получаете StateT InnerState m Intзначение в первую очередь outerStateFoo?
Chepner
6
Вам удобно с объективом? Этот вариант использования, кажется, именно zoomдля этого.
Карл
1
@ Карл Я видел некоторые линзы, но не очень хорошо их понимаю. Может быть, вы можете объяснить в ответ, как использовать зум?
Агнишом Чаттопадхяй
5
Замечание: эта запись не содержит ни одного вопроса.
Саймон Шайн

Ответы:

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

Изменить: Пока мы на это, если вы уже вносите, lensто innerStateFooможно написать так:

innerStateFoo :: Monad m => StateT InnerState m Int
innerStateFoo = innerVal <<+= 1
Джон
источник
5

Для контекста это то, чего я хочу достичь с помощью этого механизма:

Я хочу создать такие вещи, как «потоковые трансформеры», которые в основном являются функциями с состоянием: они потребляют токен, изменяют свое внутреннее состояние и что-то выводят. В частности, меня интересует класс Stream Transformers, где выводом является логическое значение; мы будем называть эти «мониторы».

Я думаю, что то, чего ты хочешь достичь, не требует много машин.

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)
Александр Вьет
источник
Это очень круто, спасибо! Эта модель называется чем-то?
Агнишом Чаттопадхяй
3
Я бы назвал это чисто функциональным программированием! Но я знаю, что это не тот ответ, который вы ищете :) StreamTransformer на самом деле является hackage.haskell.org/package/machines-0.7/docs/
Александр Виет
Нет, первое исчезновение результата не то, что я намеревался. Я хотел бы отложить первый вывод, чтобы быть вторым.
Агнишом Чаттопадхяй
2
И так далее, чтобы каждый вывод задерживался на один шаг? Это можно сделать.
Александр Вьет
1
очень приятно, спасибо за публикацию! (извините за комментирование ранее, не читая правильно Q). Я думаю, что ОП имел в виду 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')).
Уилл Несс