입문 StateT 모나드

입문 StateT 모나드

2022-10-04 last update

12 minutes reading 하스켈

처음에



이 기사는 Haskell 초보자가 StateT 모나드에 시행 착오하는 기사입니다.
따뜻한 눈으로 지켜 주시면 감사하겠습니다.

정책



출발점인 상태 첨부 계산으로부터, 1개 1개 단계를 밟아 생각해 가고 싶습니다.

예제



Lisp 구문 검사기를 설정합니다.
구문 체커라고 해도 괄호의 위치, 방향, 수만으로 구문 트리의 내용은 일절 접하지 않습니다.
이것을 예로 단계를 밟아 진행해 나갑니다.

#1 재귀에 의한 철회



'(' 를 +1 , ')' 를 -1 로 설정해, 값이 항상 0 이상 그리고 마지막에 0 이면 괄호는 올바른, 라고 하는 형태로 구현합니다.

checkParent1.hs
checkParent :: Int -> String -> Bool
checkParent s "" =
  if (s == 0)
    then True
    else False
checkParent s (x:xs) =
  if (s >= 0)
    then
      case x of
        '(' -> checkParent (s + 1) xs
        ')' -> checkParent (s - 1) xs
        _ -> checkParent s xs
    else False

checkParent 0 "(aaa (bbb ccc))"
-- => True

이와 같이, Haskell에서 단지 상태를 갖고 싶다고 하는 것만이라면 일단은 모나드 없이도 가능합니다.
그러나, 1개라도 인수가 늘어나면(자), 회수로는 매우 손에 질 수 없게 되어 버립니다.
거기서 나오는 것이 State 모나드입니다.

#2 State 모나드



그 1



checkParentS.hs
type StateData = (String, Int)

checkParentS :: State StateData Bool
checkParentS = do
  (str, s) <- get
  if str == ""
    then
      if s == 0
        then return True
        else return False
    else do
      let (x:xs) = str
      case x of
        '(' -> put (xs, s+1)
        ')' -> put (xs, s-1)
        _ -> put (xs, s)
      checkParentS

runState checkParentS  ("(aaa (bbb ccc))", 0)
-- => (True,("",0))

철회 ver.에서 직역했습니다.
다만, 이것만으로는 차이를 이해하기 어렵기 때문에, 문자수의 카운트도 더해 보겠습니다.

그 2



checkParentS2.hs
type StateData2  = (String, Int, Int)

checkParentS2 :: State StateData2 Bool
checkParentS2 = do
  (str, s, count) <- get
  if str == ""
    then
      if s == 0
        then return True
        else return False
    else do
      let (x:xs) = str
      case x of
        '(' -> put (xs, s+1, count+1)
        ')' -> put (xs, s-1, count+1)
        _ -> put (xs, s, count+1)
      checkParentS2

runState checkParentS2 ("(aaa (bbb ccc))", 0, 0)
-- => (True,("",0,15))

철회와 달리 인수를 늘리지 않고도 쉽게 매개 변수를 늘릴 수있었습니다.
더 복잡해지더라도 StateData의 데이터 구조를 변경하면 쉽게 대응할 수 있습니다.

#3 IO 처리 추가



구문 검사기이므로 로그 내보내기 기능을 추가합니다.

checkParentS3.hs
checkParentS3 :: State StateData2 IO Bool
checkParentS3 = do
-- 以下省略

‘State’ is applied to too many type arguments
In the type signature for ‘checkParentS3’:
checkParentS3::State StateData2 IO Bool

분명히 State 모나드 내에서 IO 모나드를 처리 할 수없는 것 같습니다.
여기서 나오는 것이 StateT 모나드입니다.

#4 StateT 모나드



checkParentST.hs

checkParentST :: StateT StateData2 IO Bool
checkParentST = do
  (str, s, count) <- get
  if str == ""
    then
      do
        lift $ writeFile "log.txt" $ (show count) ++ " characters"
        if s == 0
          then return True
          else return False
    else do
      let (x:xs) = str
      case x of
        '(' -> put (xs, s+1, count+1)
        ')' -> put (xs, s-1, count+1)
        _ -> put (xs, s, count+1)
      checkParentST

runStateT checkParentST ("(aaa (bbb ccc))", 0, 0)
-- => (True,("",0,15))

'T'를 추가하고 lift 함수를 사용하는 것만으로 IO 처리를 State 모나드에 쉽게 쓸 수있었습니다.

결론



StateT 모나드는 State 모나드 내에서 IO를 수행할 수 있는, 즉 모나드를 취급할 수 있도록 한 것이다.

마지막으로



Hakell 초보자이므로, 여기가 잘못되었다거나, 뭔가 있으면 코멘트해 주시면 다행입니다.

참고문헌


  • Haskell 모나드 변환기 초입문