Các quốc gia lồng nhau ở Haskell


9

Tôi đang cố gắng xác định một họ các máy trạng thái với các loại trạng thái khác nhau. Cụ thể, các máy trạng thái "phức tạp" hơn có các trạng thái được hình thành bằng cách kết hợp các trạng thái của các máy trạng thái đơn giản hơn.

(Điều này tương tự với cài đặt hướng đối tượng trong đó một đối tượng có một số thuộc tính cũng là đối tượng.)

Dưới đây là một ví dụ đơn giản về những gì tôi muốn đạt được.

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

Tổng quát hơn, tôi muốn một khung tổng quát nơi các tổ này phức tạp hơn. Đây là một cái gì đó tôi muốn biết làm thế nào để làm.

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

Đối với bối cảnh, đây là những gì tôi muốn đạt được với máy móc này:

Tôi muốn thiết kế những thứ gọi là "Stream Transformers", về cơ bản là các chức năng trạng thái: Chúng tiêu thụ một mã thông báo, làm thay đổi trạng thái bên trong của chúng và tạo ra thứ gì đó. Cụ thể, tôi quan tâm đến một lớp Stream Transformers trong đó đầu ra là giá trị Boolean; chúng tôi sẽ gọi những "màn hình" này.

Bây giờ, tôi đang cố gắng thiết kế tổ hợp cho các đối tượng này. Một số trong số họ là:

  • Một pretổ hợp. Giả sử đó monlà một màn hình. Sau đó, pre monlà một màn hình luôn tạo ra Falsesau khi mã thông báo đầu tiên được tiêu thụ và sau đó bắt chước hành vi monnhư thể mã thông báo trước đó đang được chèn ngay bây giờ. Tôi muốn mô hình hóa trạng thái pre monvới StateWithTriggertrong ví dụ trên vì trạng thái mới là boolean cùng với trạng thái ban đầu.
  • Một andtổ hợp. Giả sử rằng m1m2là màn hình. Sau đó, m1 `and` m2là một màn hình cung cấp mã thông báo cho m1, sau đó đến m2 và sau đó tạo ra Truenếu cả hai câu trả lời là đúng. Tôi muốn mô hình hóa trạng thái m1 `and` m2với CombinedStatetrong ví dụ trên vì trạng thái của cả hai màn hình phải được duy trì.

FYI, _innerVal <$> getchỉ là gets _innerVal(như gets f == liftM f get, và liftMchỉ fmapchuyên về các đơn nguyên).
chepner

Nơi mà bạn nhận được một StateT InnerState m Intgiá trị ở nơi đầu tiên ở outerStateFoo?
chepner

6
Bạn có thoải mái với ống kính? Trường hợp sử dụng này dường như là chính xác những gì zoomlà cho.
Carl

1
@Carl Tôi đã thấy một số ống kính nhưng không hiểu rõ về chúng. Có lẽ bạn có thể giải thích trong một câu trả lời làm thế nào để sử dụng zoom?
Agamelom Hayopadhyay

5
Một quan sát: Mục này không chứa một câu hỏi.
Simon Shine

Câu trả lời:


4

Đối với câu hỏi đầu tiên của bạn, như Carl đã đề cập, zoomtừ lensthực hiện chính xác những gì bạn muốn. Mã của bạn với ống kính có thể được viết như thế này:

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

Chỉnh sửa: Trong khi chúng tôi đang ở đó, nếu bạn đã đưa vào lensthì innerStateFoocó thể được viết như vậy:

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

5

Đối với bối cảnh, đây là những gì tôi muốn đạt được với máy móc này:

Tôi muốn thiết kế những thứ gọi là "Stream Transformers", về cơ bản là các chức năng trạng thái: Chúng tiêu thụ một mã thông báo, làm thay đổi trạng thái bên trong của chúng và tạo ra thứ gì đó. Cụ thể, tôi quan tâm đến một lớp Stream Transformers trong đó đầu ra là giá trị Boolean; chúng tôi sẽ gọi những "màn hình" này.

Tôi nghĩ rằng những gì bạn muốn đạt được không cần nhiều máy móc.

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)

Điều này StreamTransformerkhông nhất thiết phải có trạng thái, nhưng thừa nhận những người có trạng thái. Bạn không cần (và IMO không nên! Trong hầu hết các trường hợp !!) tiếp cận với các kiểu chữ để xác định những điều này (hoặc thực sự là bao giờ! :) nhưng đó là một chủ đề khác).

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)

Điều này là rất mát mẻ, cảm ơn! Là mô hình này được gọi là một cái gì đó?
Agamelom Hayopadhyay

3
Tôi chỉ gọi nó là lập trình chức năng thuần túy! Nhưng tôi biết đó không phải là câu trả lời bạn đang tìm kiếm :) StreamTransformer là trong thực tế, một "máy Mealy" hackage.haskell.org/package/machines-0.7/docs/...
Alexander VIETH

Không, đầu ra biến mất không phải là những gì tôi dự định. Tôi muốn trì hoãn đầu ra đầu tiên là đầu ra thứ hai.
Agamelom Hayopadhyay

2
Và cứ như vậy để mỗi đầu ra bị chậm một bước? Điều đó có thể được thực hiện.
Alexander Vieth

1
Rất đẹp, cảm ơn bạn đã đăng bài! (xin lỗi vì đã bình luận trước đó mà không nặng nề đọc Q đúng cách). Tôi nghĩ OP có nghĩa 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.