syd_sydの日記

2006-06-13

今のところ出せるネタも無いので(鋭意作成中)、ひとくちメモを書いていきます。GHCライブラリドキュメント丸写しです。果たして需要はあるのか?

フィボナッチとか高階関数とか継続渡しとか型クラスとか色々は置いといて、Haskellを使って信頼性の高い、実行効率の良いアプリを書くには?を観点に書いてい(けたらいいなあ)。。。

ふつケル持ってないのでかぶっていたらごめんなさい。

今回は例外についてですが、純粋関数的な(IO以外の)例外的状況には Maybeモナドやリストモナドを使いましょう。ErrorTというモナド変換子もあるらしい(使ったことない)。「モナドのすべて」参照。

[][] evaluate と return : 項の強制評価

Control.Exception.evaluate

evaluate :: a -> IO a

returnと似て非なるもの。渡された項を評価する。これが効いてくるのは、「無限に続く(止まらない) 計算」や「エラーを投げる計算」を食わせた時。

例:

do
  return $ 1 `div` 0 -- 0除算
  putStrLn "I'm still alive!"

I'm still alive!

となるのに対し*1

do
  Control.Exception.evaluate $ 1 `div` 0 -- 0除算
  putStrLn "I'm still alive!"

は 1/0が評価されて、

*** Exception: divide by zero

となる。

必ず評価する、という点ではseq と似てるけど、seq は第一引数を捨てるし、 evaluate は IOモナドを返す点で違う(本文参照)。 次のthrowとthrowIOの違いに似てる?

[] throw

Control.Exception.throw,Control.Exception.throwIO

Haskellの例外はIOモナド内でしかcatchすることができませんが、投げるのはどこでもできます:

throwIO :: Exception -> IO a 
throw :: Exception -> a 

throwIOはIOモナドの中で、throwはどこでも。

ただし throwIO ex `seq` a は例外をraiseしない (マニュアルより)。 ややこしいですが、前述の evaluate同様、 IO a 型の値はIOモナドの中でしか効果がないです。このような場合はunsafePerformIOを使うか、素直にthrow exとやりましょう。

[] catch

Control.Exception.catch

catch 
  :: IO a -- 例外をraiseするかもしれないアクション
  -> (Exception -> IO a) -- 例外ハンドラ
  -> IO a 

省略。

引数を入れ替えただけのhandleや、catchJust, handleJustなども。

[] bracket

Control.Exception.bracket

bracket 
  :: IO a -- 最初に行う (e.g.リソースを取得して返す) アクション
  -> (a -> IO b) -- 最後に行う (リソースの開放を行う) アクション
  -> (a -> IO c) -- 間に行う (メインの)  アクション
  -> IO c 

メインアクションの中で例外がraiseされた場合に、必ずリソースを開放してから例外がre-raiseされます。

高レベル言語には必ず存在する try-finally はこれでやりましょう。

特にリソースを意識しない場合、文字通りfinallyなんかもあります。

本文にはopenFileと組み合わせる例が載っています。この例では必ずファイルが閉じられます。

[][][] bracketの例 : HAppSより

404 Not Found

を偶々読んでいて知りました。これは実装に標準のNetwork.Socketを使っているぽい。

http://happs.org/HAppS/src/HAppS/Protocols/SMTP.hs からの例:

hostSend :: HostName -> Int -> Envelope String -> IO ()
hostSend host portNum msg = bracket (connectTo host (PortNumber $ fromIntegral portNum))
                                    (\h -> hClose h `E.catch` (\_ -> return ()))
                                    (flip handleSend msg)

handleSend :: Handle -> Envelope String -> IO ()

connectToで接続したらhandleSendを実行する。bracketにより、handleSendが失敗するしないに関わらず、hCloseによりハンドルは正しく閉じられる。hCloseの例外処理は書いておく必要がある。 Javaのfinallyブロック内で例外が投げられうるのと同じ。


[] block と asynchronous exception

パス。

実行効率の測定(プロファイリング)

あとで書けたら書く。

[][]network-alt

network-alt

Networkとは別のライブラリらしい。後で調べる。

*1:return (1`div`0) >>= \_-> putStrLn "..." となり、遅延評価により除算されない。これに対し return (1`div`0) >>= \x-> putStrLn $ show x とやればputStrLnに必要なため1/0が評価され例外がraiseされる。