Haskell Language
스테이트 모나드
수색…
소개
State sa
운반 잠재적 타입의 상태 변경 계산 나타낸다 s
및 입력의 결과 발생 a
하지만, 용어 "상태 모나드는"보통 상태를 운반 임의 모나드를 참조 할 수있다. mtl
및 transformers
패키지는 상태 모나드의 일반적인 구현을 제공합니다.
비고
하스켈에 새로 온 사람들은 종종 State
모나드를 외면하고 그것을 금기처럼 취급합니다. 함수형 프로그래밍의 장점은 국가를 피하는 것 인 것처럼, State
를 사용할 때 잃지 않으시겠습니까? 좀 더 미묘한 견해는 다음과 같습니다.
- 상태는 작고 통제 된 복용량 에서 유용 할 수 있습니다.
-
State
유형은 선량을 매우 정확하게 제어 할 수있는 기능을 제공합니다.
그 이유는 action :: State sa
가있는 경우 다음과 같이 알려줍니다.
-
action
은 국가에 달려 있기 때문에 특별합니다. - 상태는 입력이
s
때문에action
당신의 모든 이전 값에 의해 영향을받을 수없는 프로그램 만s
또는 일부에서 접근 일부 값s
; -
runState :: State sa -> s -> (a, s)
는 stateful 액션 주위에 "장벽"을 두어 유효성 을 그 장벽 바깥에서 관찰 할 수 없습니다 .
따라서 이것은 특정 시나리오에서 State
를 사용할지 여부에 대한 좋은 기준 집합입니다. 당신은 코드가 좁은 타입을 s
로 선택하고 runState
를 가능한 한 "맨 아래"에 가깝게 두어 상태의 범위를 최소화 하고 있음을보고 싶다. (그래서 당신의 행동은 다음과 같이 몇 가지 영향을받을 수있다. 가능한.
카운터를 사용하여 트리 노드에 번호 매기기
다음과 같은 트리 데이터 유형이 있습니다.
data Tree a = Tree a [Tree a] deriving Show
그리고 우리는 트리의 각 노드에 숫자를 할당하는 함수를 증가 카운터에서 작성하려고합니다.
tag :: Tree a -> Tree (a, Int)
먼 길
우선 State
모나드의 저급 메카니즘을 아주 잘 보여주기 때문에 먼 길을 갈 것입니다.
import Control.Monad.State
-- Function that numbers the nodes of a `Tree`.
tag :: Tree a -> Tree (a, Int)
tag tree =
-- tagStep is where the action happens. This just gets the ball
-- rolling, with `0` as the initial counter value.
evalState (tagStep tree) 0
-- This is one monadic "step" of the calculation. It assumes that
-- it has access to the current counter value implicitly.
tagStep :: Tree a -> State Int (Tree (a, Int))
tagStep (Tree a subtrees) = do
-- The `get :: State s s` action accesses the implicit state
-- parameter of the State monad. Here we bind that value to
-- the variable `counter`.
counter <- get
-- The `put :: s -> State s ()` sets the implicit state parameter
-- of the `State` monad. The next `get` that we execute will see
-- the value of `counter + 1` (assuming no other puts in between).
put (counter + 1)
-- Recurse into the subtrees. `mapM` is a utility function
-- for executing a monadic actions (like `tagStep`) on a list of
-- elements, and producing the list of results. Each execution of
-- `tagStep` will be executed with the counter value that resulted
-- from the previous list element's execution.
subtrees' <- mapM tagStep subtrees
return $ Tree (a, counter) subtrees'
리팩토링
카운터를 postIncrement 액션으로 분할합니다.
우리가 비트 get
다음 팅에 현재 카운터를하고 put
팅 카운터를 + 1로 떨어져 분할 할 수 있습니다 postIncrement
많은 C 스타일의 언어가 제공하는 것과 유사한 조치 :
postIncrement :: Enum s => State s s
postIncrement = do
result <- get
modify succ
return result
트리 함수를 고차 함수로 나눕니다.
트리 워킹 로직은 다음과 같이 자체 함수로 나눌 수 있습니다.
mapTreeM :: Monad m => (a -> m b) -> Tree a -> m (Tree b)
mapTreeM action (Tree a subtrees) = do
a' <- action a
subtrees' <- mapM (mapTreeM action) subtrees
return $ Tree a' subtrees'
이것과 함께 postIncrement
우리가 다시 작성할 수 있습니다 기능 tagStep
:
tagStep :: Tree a -> State Int (Tree (a, Int))
tagStep = mapTreeM step
where step :: a -> State Int (a, Int)
step a = do
counter <- postIncrement
return (a, counter)
Traversable
클래스 사용
위 mapTreeM
솔루션 은 Traversable
클래스 의 인스턴스로 쉽게 재 작성 될 수 있습니다.
instance Traversable Tree where
traverse action (Tree a subtrees) =
Tree <$> action a <*> traverse action subtrees
이 경우 Monad
대신 Applicative
( <*>
연산자)를 사용해야했습니다.
이제 프로와 같은 tag
를 쓸 수 있습니다.
tag :: Traversable t => t a -> t (a, Int)
tag init t = evalState (traverse step t) 0
where step a = do tag <- postIncrement
return (a, tag)
이 방법은 Tree
유형뿐만 아니라 모든 Traversable
유형에 사용할 수 있습니다.
Traversable
상용구 제거하기
GHC에는 위의 인스턴스를 작성할 필요가없는 DeriveTraversable
확장이 있습니다.
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
data Tree a = Tree a [Tree a]
deriving (Show, Functor, Foldable, Traversable)