Amerika Serikat di Haskell

9

Saya mencoba untuk mendefinisikan keluarga mesin negara dengan jenis negara yang agak berbeda. Secara khusus, mesin negara yang lebih "kompleks" memiliki keadaan yang dibentuk dengan menggabungkan keadaan mesin negara yang lebih sederhana.

(Ini mirip dengan pengaturan berorientasi objek di mana objek memiliki beberapa atribut yang juga objek.)

Ini adalah contoh sederhana dari apa yang ingin saya capai.

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

Secara umum, saya ingin kerangka kerja umum di mana sarang ini lebih kompleks. Ini adalah sesuatu yang saya ingin tahu bagaimana melakukannya.

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

Untuk konteks, inilah yang ingin saya capai dengan mesin ini:

Saya ingin merancang hal-hal ini yang disebut "Stream Transformers", yang pada dasarnya adalah fungsi stateful: Mereka mengkonsumsi token, mengubah keadaan internal mereka dan mengeluarkan sesuatu. Khususnya, saya tertarik pada kelas Stream Transformers di mana outputnya adalah nilai Boolean; kami akan memanggil ini "monitor".

Sekarang, saya mencoba merancang kombinator untuk objek-objek ini. Beberapa dari mereka adalah:

  • Sebuah precombinator. Misalkan itu monadalah monitor. Kemudian, pre monadalah monitor yang selalu menghasilkan Falsesetelah token pertama dikonsumsi dan kemudian meniru perilaku monseolah-olah token sebelumnya sedang dimasukkan sekarang. Saya ingin memodelkan keadaan pre mondengan StateWithTriggerdalam contoh di atas karena negara baru adalah boolean bersama dengan keadaan aslinya.
  • Sebuah andcombinator. Anggaplah m1dan m2monitor. Kemudian, m1 `and` m2adalah monitor yang mengumpan token ke m1, dan kemudian ke m2, dan kemudian menghasilkan Truejika kedua jawaban itu benar. Saya ingin memodelkan keadaan m1 `and` m2dengan CombinedStatedalam contoh di atas karena keadaan kedua monitor harus dipertahankan.
Agnishom Chattopadhyay
sumber
FYI, _innerVal <$> getadil gets _innerVal(seperti gets f == liftM f get, dan liftMhanya fmapkhusus untuk monad).
chepner
Di mana Anda mendapatkan StateT InnerState m Intnilai di tempat pertama outerStateFoo?
chepner
6
Apakah Anda nyaman dengan lensa? Kasus penggunaan ini tampaknya tepat untuk apa zoom.
Carl
1
@ Carl Saya telah melihat beberapa lensa tetapi tidak memahaminya dengan baik. Mungkin Anda bisa menjelaskan dalam jawaban bagaimana menggunakan zoom?
Agnishom Chattopadhyay
5
Pengamatan: Entri ini tidak mengandung satu pertanyaan.
Simon Shine

Jawaban:

4

Untuk pertanyaan pertama Anda, seperti yang disebutkan Carl, zoomdari lenslakukan persis apa yang Anda inginkan. Kode Anda dengan lensa dapat ditulis seperti ini:

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

Sunting: Ketika kita sedang berada di sana, jika Anda sudah membawa lensmaka innerStateFoodapat ditulis seperti:

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

Untuk konteks, inilah yang ingin saya capai dengan mesin ini:

Saya ingin merancang hal-hal ini yang disebut "Stream Transformers", yang pada dasarnya adalah fungsi stateful: Mereka mengkonsumsi token, mengubah keadaan internal mereka dan mengeluarkan sesuatu. Khususnya, saya tertarik pada kelas Stream Transformers di mana outputnya adalah nilai Boolean; kami akan memanggil ini "monitor".

Saya pikir apa yang ingin Anda capai tidak membutuhkan banyak mesin.

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)

Ini StreamTransformertidak selalu stateful, tetapi mengakui yang stateful. Anda tidak perlu (dan IMO tidak boleh! Dalam kebanyakan kasus !!) meraih typeclasses untuk mendefinisikan ini (atau memang pernah! :) tapi itu topik lain).

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)
Alexander Vieth
sumber
Ini sangat keren, terima kasih! Apakah pola ini disebut sesuatu?
Agnishom Chattopadhyay
3
Saya hanya menyebutnya pemrograman fungsional murni! Tapi aku tahu itu bukan jawaban yang Anda cari :) StreamTransformer sebenarnya adalah "mesin Mealy" hackage.haskell.org/package/machines-0.7/docs/...
Alexander Vieth
Tidak, output pertama menghilang bukan yang saya maksudkan. Saya ingin menunda output pertama menjadi yang kedua.
Agnishom Chattopadhyay
2
Dan seterusnya sehingga setiap output tertunda satu langkah? Itu bisa dilakukan.
Alexander Vieth
1
Bagus sekali, terima kasih sudah memposting! (maaf karena berkomentar sebelumnya tanpa naik membaca Q dengan benar). Saya pikir OP maksudnya 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