수색…


소개

상태 모나드는 모나드에서 계산을 실행할 때마다 바뀔 수있는 상태를 유지하는 일종의 모나드입니다. 구현은 일반적 형태의 아르 State sa 운반 잠재적 타입의 상태 변경 계산 나타낸다 s 및 입력의 결과 발생 a 하지만, 용어 "상태 모나드는"보통 상태를 운반 임의 모나드를 참조 할 수있다. mtltransformers 패키지는 상태 모나드의 일반적인 구현을 제공합니다.

비고

하스켈에 새로 온 사람들은 종종 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)


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