6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

HaskellAdvent Calendar 2015

Day 13

入門 StateTモナド

Last updated at Posted at 2015-12-12

初めに

この記事は、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初心者なので、ここが間違っているとか、何かあったらコメントしていただけると幸いです。

参考文献

6
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?