รัฐต่างๆใน 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

สำหรับบริบทนี่คือสิ่งที่ฉันต้องการบรรลุด้วยเครื่องจักรนี้:

ฉันต้องการออกแบบสิ่งเหล่านี้เรียกว่า "สตรีม Transformers" ซึ่งโดยทั่วไปแล้วเป็นหน้าที่ของรัฐ: พวกเขาใช้โทเค็นเปลี่ยนสถานะภายในและส่งออกบางอย่าง โดยเฉพาะฉันสนใจในคลาสของ Stream Transformers ที่เอาต์พุตเป็นค่าบูลีน เราจะเรียก "จอภาพ" เหล่านี้

ตอนนี้ฉันพยายามออกแบบ combinators สำหรับวัตถุเหล่านี้ บางส่วนของพวกเขาคือ:

  • เครื่องคอมpreบิเนเตอร์ สมมติว่าmonเป็นจอภาพ จากนั้นpre monเป็นจอมอนิเตอร์ที่สร้างเสมอFalseหลังจากที่โทเค็นแรกถูกใช้ไปแล้วจะเลียนแบบพฤติกรรมของmonราวกับว่าโทเค็นก่อนหน้านี้ถูกแทรกอยู่ตอนนี้ ฉันต้องการสร้างแบบจำลองสถานะpre monด้วยStateWithTriggerในตัวอย่างข้างต้นเนื่องจากสถานะใหม่เป็นบูลีนพร้อมกับสถานะดั้งเดิม
  • andCombinator สมมติว่าm1และm2เป็นจอภาพ จากนั้นm1 `and` m2เป็นจอภาพที่ป้อนโทเค็นไปยัง m1 และจากนั้นไปที่ m2 จากนั้นสร้างTrueหากคำตอบทั้งคู่เป็นจริง ฉันต้องการสร้างแบบจำลองสถานะm1 `and` m2ด้วยCombinedStateในตัวอย่างด้านบนเนื่องจากต้องรักษาสถานะของจอภาพทั้งสอง

FYI _innerVal <$> getเป็นเพียงgets _innerVal(ตามgets f == liftM f getและliftMเป็นเพียงfmapความเชี่ยวชาญในการ monads)
chepner

คุณจะได้รับความStateT InnerState m Intคุ้มค่าในสถานที่แรกในouterStateFooที่ไหน?
chepner

6
คุณพอใจกับเลนส์หรือไม่ กรณีการใช้งานนี้ดูเหมือนจะเป็นสิ่งที่zoomถูกต้อง
Carl

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

สำหรับบริบทนี่คือสิ่งที่ฉันต้องการบรรลุด้วยเครื่องจักรนี้:

ฉันต้องการออกแบบสิ่งเหล่านี้เรียกว่า "สตรีม Transformers" ซึ่งโดยทั่วไปแล้วเป็นหน้าที่ของรัฐ: พวกเขาใช้โทเค็นเปลี่ยนสถานะภายในและส่งออกบางอย่าง โดยเฉพาะฉันสนใจในคลาสของ 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)

นี่มันเจ๋งมากขอบคุณ! รูปแบบนี้เรียกว่าอะไร?
Agnishom Chattopadhyay

3
ฉันแค่เรียกมันว่าการเขียนโปรแกรมใช้งานได้จริง! แต่ฉันรู้ว่านั่นไม่ใช่คำตอบที่คุณกำลังมองหา :) StreamTransformer ในความเป็นจริงแล้ว "Mealy machine" hackage.haskell.org/package/machines-0.7/docs/…
Alexander Vieth

ไม่เอาต์พุตแรกหายไปไม่ใช่สิ่งที่ฉันตั้งใจ ฉันต้องการที่จะชะลอการส่งออกครั้งแรกที่จะเป็นคนที่สอง
Agnishom Chattopadhyay

2
และอื่น ๆ เพื่อให้ทุกการส่งออกล่าช้าโดยขั้นตอนเดียว? ที่สามารถทำได้
Alexander Vieth

1
ดีมากขอบคุณสำหรับการโพสต์! (ขออภัยที่แสดงความคิดเห็นก่อนหน้านี้โดยไม่ต้องอ่านคำถามอย่างถูกต้อง) ผมคิดว่า 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.