하스켈에 중첩 된 국가


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 monwith 의 상태를 모델링하고 싶습니다 StateWithTrigger.
  • 결합기 and. 그 가정 m1m2모니터입니다. 그런 다음 m1 `and` m2토큰을 m1에 공급 한 다음 m2에 공급 한 다음 True두 대답이 모두 참인 경우 생성하는 모니터 입니다. 두 모니터의 상태를 유지해야하므로 위의 예에서 m1 `and` m2with 의 상태를 모델링하고 싶습니다 CombinedState.

참고로, _innerVal <$> get그냥 gets _innerVal(로 gets f == liftM f get하고, liftM그냥 fmap모나드에 전문).
chepner

StateT InnerState m Int처음에 어디에서 가치 를 얻 outerStateFoo습니까?
chepner

6
렌즈에 편안합니까? 이 사용 사례는 정확히 무엇인가 인 것 같습니다 zoom.
Carl

1
@Carl 렌즈를 보았지만 잘 이해하지 못했습니다. 아마도 줌을 사용하는 방법을 대답으로 설명 할 수 있습니까?
Agnishom Chattopadhyay

5
관찰 :이 항목에는 단일 질문이 없습니다.
Simon Shine

답변:


4

Carl이 언급했듯이 첫 번째 질문 zoom에서 from 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아닌 반드시 상태,하지만 상태 사람들을 인정하고있다. 이러한 클래스를 정의하기 위해 타입 클래스에 도달 할 필요는 없으며 (그리고 대부분의 경우 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는 사실에 "반점이 기계"입니다 hackage.haskell.org/package/machines-0.7/docs/...
알렉산더 Vieth

아니요, 첫 번째 출력 소실은 내가 의도 한 것이 아닙니다. 첫 번째 출력을 두 번째 출력 으로 지연 하고 싶습니다 .
Agnishom Chattopadhyay

2
그리고 모든 출력이 한 단계 씩 지연되도록? 할 수 있습니다.
Alexander Vieth

1
게시 해 주셔서 감사합니다. (히빙없이 주석을 달아 죄송합니다 .Q를 올바르게 읽으십시오). 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.