2011年2月17日木曜日

Haskell の Data.IORef を使い IO モナドの中で変数を更新

Haskell で変数の更新を伴うインデックス付きループを再帰で置き換えるには」のつづき

1. Data.IORef で変数の更新

Python のような言語では、変数の更新はごく普通に書ける。

x = 100
print x      #=> 100
x = 200
print x      #=> 200
x = 200 * 2
print x      #=> 400

Haskell では Data.IORef を使うと、一見、変数の更新をしているかのような記述ができる。

このモジュールの機能は、

Mutable references in the IO monad.

使う関数は以下の 4 つ。返される値が IO で包まれている。

それぞれ関数名が示しているように、IORef 型の値を生成し、読み取り、書き出し、変更する。

import Data.IORef

main = newIORef 100 >>= \x ->
       readIORef x  >>= print >>   -- 100
       writeIORef x 200       >>   
       readIORef x  >>= print >>   -- 200
       modifyIORef x (*2)     >>
       readIORef x  >>= print      -- 400

do 式を使うなら、

main = do x <- newIORef 100
          print =<< readIORef x   -- 100
          writeIORef x 200        
          print =<< readIORef x   -- 200
          modifyIORef x (*2)
          print =<< readIORef x  -- 400

 

2. 変数を更新する例

「文字列の配列の中から最長の文字列と、そのインデックスを返す」関数Data.IORef を使って書くなら、

import Data.IORef

longestWord :: [String] -> IO (String, Int)
longestWord (x:xs) = newIORef x >>= \word ->
                     newIORef 0 >>= \idx ->
                     loop word idx 1 xs 
    where
      loop word idx i []     = readIORef word >>= \word' ->
                               readIORef idx  >>= \idx'  ->
                               return (word', idx')
      loop word idx i (x:xs) = readIORef word >>= \word' ->
                               (if length word' < length x 
                                then writeIORef word x   >>
                                     writeIORef idx i 
                                else return ())          >>
                               loop word idx (i+1) xs 
                                     
main = (longestWord $ words "The quick brown fox") >>= print

うーん、読みやすくはないなぁ。。 (@_@;

do 式で置き換えるなら、

longestWord :: [String] -> IO (String, Int)
longestWord (x:xs) = do word <- newIORef x 
                        idx  <- newIORef 0
                        loop word idx 1 xs 
    where
      loop word idx i []     = do word' <- readIORef word
                                  idx'  <- readIORef idx
                                  return (word', idx')
      loop word idx i (x:xs) = do word' <- readIORef word
                                  if length word' < length x 
                                     then do writeIORef word x
                                             writeIORef idx i 
                                     else return ()
                                  loop word idx (i+1) xs

ループのインデックスに相当する変数も IORef 型の値にするなら、

longestWord :: [String] -> IO (String, Int)
longestWord (x:xs) = do word <- newIORef x 
                        idx  <- newIORef 0
                        i    <- newIORef 1
                        loop word idx i xs 
    where
      loop word idx i []     = do word' <- readIORef word
                                  idx'  <- readIORef idx
                                  return (word', idx')
      loop word idx i (x:xs) = do word' <- readIORef word
                                  i'    <- readIORef i
                                  if length word' < length x 
                                     then do writeIORef word x
                                             writeIORef idx i' 
                                     else return ()
                                  modifyIORef i (+1)
                                  loop word idx i xs

 

参考サイト