Вкладені штати в Хаскелл


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 monз StateWithTriggerу наведеному вище прикладі, оскільки новий стан є булевим разом із початковим станом.
  • andКомбінатор. Припустимо, що це m1і m2монітори. Потім, m1 `and` m2це монітор, який подає маркер до m1, а потім до m2, а потім виробляє, Trueякщо обидва відповіді були правдивими. Я хотів би, щоб змоделювати стан m1 `and` m2з CombinedStateв наведеному вище прикладі , так як стан обох моніторів повинні бути збережені.

FYI, _innerVal <$> getє справедливим gets _innerVal(як gets f == liftM f get, і спеціалізується liftMтільки fmapна монадах).
чепнер

Де ви отримуєте StateT InnerState m Intцінність в першу чергу outerStateFoo?
чепнер

6
Вам подобається лінза? Цей випадок використання, здається, саме те zoom, для чого.
Карл

1
@Carl Я бачив деякі лінзи, але не дуже добре їх розумію. Можливо, ви можете пояснити у відповіді, як використовувати зум?
Agnishom Chattopadhyay

5
Спостереження: Цей запис не містить жодного запитання.
Simon Shine

Відповіді:


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

У контексті цього я хочу досягти завдяки цій техніці:

Я хочу створити ці речі під назвою "Трансформатори потоку", які в основному є функціональними станами: Вони споживають маркер, мутують свій внутрішній стан і щось виводять. Зокрема, мене цікавить клас потокових трансформаторів, де на виході є булеве значення; ми будемо називати цих "моніторів".

Я думаю, що для того, що ви хочете досягти, не потрібно дуже багато техніки.

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не обов'язково державно , але визнає заповітне. Вам не потрібно (а ІМО не повинен! У більшості випадків !!) тягнутися до класів типів, щоб визначити ці (чи справді колись! :), але це вже інша тема).

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 - це насправді "машина страви
Олександр

Ні, перший вихід зникає не те, що я задумав. Я хотів би затримати перший вихід, щоб бути другим.
Agnishom Chattopadhyay

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')).
Чи буде Несс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.