lookupの罠

ghc のバージョンが上る際に標準添付されているライブラリのAPIの型が微妙に変わることがあって、これが原因で、旧バージョンではコンパイルできたものが、新しいバージョンではコンパイルできなくなることがある。

ghc-6.8.3 から ghc-6.10.1 へのバージョンアップでもそのようなことが起きた。 このバージョンアップに伴い、付属の containers パッケージのバージョンが 0.1.0.1 から 0.2.0.0 になったのだが、Data.MapあるいはData.IntMap モジュールに 含まれている lookup の型が変更になっている。

containers-0.1.0.x では

Data.Map.lookup :: (Monad m, Ord k) => k -> Data.Map.Map k a -> m a
Data.IntMap.lookup :: Monad m => Key -> Data.IntMap.IntMap a -> m a

containers-0.2.0.0 では

Data.Map.lookup :: Ord k => k -> Data.Map.Map k a -> Maybe a
Data.IntMap.lookup :: Key -> Data.IntMap.IntMap a -> Maybe a

containers-0.1.0.x 由来の lookup を期待して書いたコードうち、Maybe を期待している文脈では containers-0.2.0.0 でも OK である。しかし、Maybe 以外の Monad クラスのを期待する文脈で、lookup を使うときには型チェックが通らなくなる。

ではどう書き換えるかだが、

lookup key table

return $ fromJust $ lookup key table

などではどうか。これは lookup が成功することが保証されている文脈では問題ないが、そうでない場合、lookup が失敗すると、すなわち、lookup が Nothing を返したときの挙動が違う場合がある。恣意的ではあるが例を挙げると、

import Control.Monad.Identity
import Control.Monad.Error
import Data.Maybe
import Data.IntMap

foo = runIdentity $ runErrorT 
    $ (M.lookup 3 M.empty :: ErrorT String Identity Int)
bar = runIdentity $ runErrorT 
    $ (return :: a -> ErrorT String Identity a) $ fromJust
    $ (M.lookup 3 M.empty :: Maybe Int)

これで、containers-0.1.0.1 の環境で foo、bar をそれぞれ評価すると

*Main> foo
Left "Data.Map.lookup: Key not found"
*Main> bar
Right *** Exception: Maybe.fromJust: Nothing

前者はLeft構成子による値、後者はRight構成子による値になっている(しかも、Right構成子への引数評価の際に実行時エラーにより例外が発生している)。

maybe (fail "lookup: Key not found") return $ lookup key table

などと書くのが正解だろう。

baz = runIdentity $ runErrorT 
    $ maybe (fail "lookup: Key not found") (return :: a -> ErrorT String Identity a)
    $ (M.lookup 3 M.empty :: Maybe Int)

baz を評価すると

*Main> baz
Left "lookup: Key not found"

Copyright (c) 2008-2009 HOP project Contact | About Us