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"