Haskell Language
모나드 변압기
수색…
모나드 카운터
모나드 변환기를 사용하여 독자, 작가 및 주 모나드를 구성하는 방법에 대한 예입니다. 소스 코드는 이 저장소에서 찾을 수 있습니다.
우리는 주어진 상수만큼 값을 증가시키는 카운터를 구현하려고합니다.
먼저 몇 가지 유형과 함수를 정의하여 시작합니다.
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)