수색…


모나드 카운터

모나드 변환기를 사용하여 독자, 작가 및 주 모나드를 구성하는 방법에 대한 예입니다. 소스 코드는 이 저장소에서 찾을 수 있습니다.

우리는 주어진 상수만큼 값을 증가시키는 카운터를 구현하려고합니다.

먼저 몇 가지 유형과 함수를 정의하여 시작합니다.

newtype Counter = MkCounter {cValue :: Int}
  deriving (Show)

-- | 'inc c n' increments the counter by 'n' units.
inc :: Counter -> Int -> Counter
inc (MkCounter c) n = MkCounter (c + n)

카운터를 사용하여 다음 계산을 수행한다고 가정 해 보겠습니다.

  • 카운터를 0으로 설정하십시오.
  • 증분 상수를 3으로 설정
  • 카운터를 3 번 ​​증가시키다.
  • 증분 상수를 5로 설정
  • 카운터를 2 번 증가시킨다.

상태 모나드상태 를 전달하는 추상화를 제공합니다. 우리는 상태 모나드를 사용하고 증분 함수를 상태 변압기로 정의 할 수 있습니다.

-- | CounterS is a monad.
type CounterS = State Counter

-- | Increment the counter by 'n' units.
incS :: Int-> CounterS ()
incS n = modify (\c -> inc c n)

이것은 이미 우리가보다 명확하고 간결한 방식으로 계산을 표현할 수있게 해줍니다 :

-- | The computation we want to run, with the state monad.
mComputationS :: CounterS ()
mComputationS = do
  incS 3
  incS 3
  incS 3
  incS 5
  incS 5

그러나 우리는 여전히 각 호출에서 증분 상수를 전달해야합니다. 우리는 이것을 피하고 싶습니다.

환경 추가하기

독자 모나드 는 환경을 전달하는 편리한 방법을 제공합니다. 이 모나드는 OO 세계에서 의존성 주입 (dependency injection )으로 알려진 것을 수행하는 함수형 프로그래밍에 사용됩니다.

가장 단순한 버전 인 독자 모나드에는 두 가지 유형이 필요합니다.

  • 읽어내는 값의 타입 (즉, 환경, 이하의 r )

  • 리더 모나드 (의해 반환 된 값 아래). a

    독자 ra

그러나 상태 모나드를 사용해야합니다. 따라서 ReaderT 변환기를 사용해야합니다.

newtype ReaderT r m a :: * -> (* -> *) -> * -> *

ReaderT 사용하여 다음과 같이 환경 및 상태로 카운터를 정의 할 수 있습니다.

type CounterRS = ReaderT Int CounterS

우리는 정의 incR (사용 환경에서 증가 상수를 취 기능 ask ), 우리의 관점에서 우리의 증가 함수를 정의 CounterS 우리가 사용하게 모나드 lift 합니다 (에 속하는 기능 모나드 변압기 클래스).

-- | Increment the counter by the amount of units specified by the environment.
incR :: CounterRS ()
incR = ask >>= lift . incS

독자 모나드를 사용하여 다음과 같이 계산을 정의 할 수 있습니다.

-- | The computation we want to run, using reader and state monads.
mComputationRS :: CounterRS ()
mComputationRS = do
  local (const 3) $ do
    incR
    incR
    incR
    local (const 5) $ do
      incR
      incR

요구 사항이 변경되었습니다 : 로깅이 필요합니다!

이제 계산에 로깅을 추가하여 시간 경과에 따른 카운터의 진화를 확인할 수 있다고 가정합니다.

우리는이 작업을 수행하기위한 모나드 인 작가 모나드도 가지고 있습니다. 독자 모나드와 마찬가지로, 우리가 그들을 구성하기 때문에 독자 모나드 변환기를 사용해야합니다.

newtype WriterT w m a :: * -> (* -> *) -> * -> *

여기서 w (우리는이 값을 누적가 허용되는 모노 이드를 수 있음) 축적 출력의 유형을 나타내고, m 내부 모나드이며 연산의 유형. a

다음과 같이 로깅, 환경 및 상태로 카운터를 정의 할 수 있습니다.

type CounterWRS = WriterT [Int] CounterRS

lift 를 사용하면 각 증분 후에 카운터의 값을 기록하는 증분 함수의 버전을 정의 할 수 있습니다.

incW :: CounterWRS ()
incW = lift incR >> get >>= tell . (:[]) . cValue

이제 로깅을 포함하는 계산은 다음과 같이 작성할 수 있습니다.

mComputationWRS :: CounterWRS ()
mComputationWRS = do
  local (const 3) $ do
    incW
    incW
    incW
    local (const 5) $ do
      incW
      incW

한 번에 모든 것을 수행

이 예제는 직장에서 모나드 변압기를 보여주기위한 것입니다. 그러나 모든 측면 (환경, 상태 및 로깅)을 단일 증분 작업으로 작성하여 동일한 효과를 얻을 수 있습니다.

이를 위해 타입 제약을 사용합니다 :

inc' :: (MonadReader Int m, MonadState Counter m, MonadWriter [Int] m) => m ()
inc' = ask >>= modify . (flip inc) >> get >>= tell . (:[]) . cValue

여기서 우리는 위의 제약 조건을 만족시키는 모든 모나드에서 작동 할 수있는 솔루션에 도달합니다. 따라서 계산 함수는 다음과 같이 정의됩니다.

mComputation' :: (MonadReader Int m, MonadState Counter m, MonadWriter [Int] m) => m ()

그 몸 안에서 우리는 inc를 사용합니다. '

예를 들어 ghci REPL에서 다음과 ghci 계산을 실행할 수 있습니다.

runState ( runReaderT ( runWriterT mComputation' ) 15 )  (MkCounter 0)


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow