Do 記法の意外な挙動 (Haskell)
ちょっとイロイロあって Do 記法についていじってたら,知らない動作をしてくれたので,メモっておく.
いきさつ
前回の記事 で書いたように, Haskell の language-dockerfile というライブラリをいじってた. が,ちょっと違和感を感じる挙動の関数があったので,いじってプルリクでも送ろうかと思い,コードを読んでいたら...
何が変か
問題のコードは,例えばコレ.
prettyPrintBaseImage :: BaseImage -> Doc
prettyPrintBaseImage b =
case b of
DigestedImage name digest -> do
text name
char '@'
text (ByteString.unpack digest)
UntaggedImage name -> text name
TaggedImage name tag -> do
text name
char ':'
text tag
where
(>>) = (<>)
return = (mempty <>)Do 記法が使われている. が,Doc 型は Monad 型クラスのインスタンスではない. ちなみに,Doc は pretty ライブラリで定義されている.
どっかでインスタンス化されてるのかと思ったが見つからない. というか,そもそも Kind が * -> * ではなく,* だ.
で,けっきょく Do 記法が使える原因は何だったかというと,where 以下にあった.
where
(>>) = (<>)
return = (mempty <>)である(コレと同じ動作をしてる). もしかして,(>>) や return が定義されてればいいのか?
Do 記法は構文糖衣
まぁここら辺は想像できる. 基本的に,(>>=), (>>), fail が置換されるようだ.
テスト
試しに次のようなコード書いてテストしてみた.
data Hoge a = Hoge a | Fuga deriving (Show)
test :: a -> Hoge a
test a = do
Fuga
return a
where
(>>) :: Hoge a -> Hoge a -> Hoge a
_ >> a = a
return = Hoge$ stack ghci
Configuring GHCi with the following packages:
GHCi, version 8.0.1: http://www.haskell.org/ghc/ :? for help
Prelude> :l test-do-notation1.hs
[1 of 1] Compiling Test1 ( test-do-notation1.hs, interpreted )
test-do-notation1.hs:10:3: error:
? No instance for (Monad Hoge) arising from a do statement
? In a stmt of a 'do' block: Fuga
In the expression:
do { Fuga;
return a }
In an equation for ‘test’:
test a
= do { Fuga;
return a }
where
(>>) :: Hoge a -> Hoge a -> Hoge a
_ >> a = a
return = Hoge
Failed, modules loaded: none.
あれ,ダメだ.
次の場合はどうだろうか.
doubleStr :: String -> String
doubleStr s = do
s
return s
where
(>>) :: String -> String -> String
(>>) = mappend
return = idPrelude> :l test-do-notation1.hs
[1 of 1] Compiling Test1 ( test-do-notation1.hs, interpreted )
Ok, modules loaded: Test1.
*Test1> doubleStr "aaa"
"aaaaaaaaa"
これはいける...
まとめ
あくまで実験結果ですが.
Kind が * の型は (>>=) や (>>) を定義すれば,Monad 型クラスに関係なく Do 記法が使える.
ようである.
この件について書いてある資料は見つからなかった...(英語ダメ)
* -> * の型は強制的に,Monad 型クラスのクラスメソッドを探されるのかなぁ?
ちなみに
- haskell - Do notation without monads: possible? - Stack Overflow
- 9.3.15. Rebindable syntax and the implicit Prelude import -- Glasgow Haskell Compiler
Users Guide
“Do” notation is translated using whatever functions (>>=), (>>), and fail, are in scope (not the Prelude versions).
つまり,言語拡張 {-# LANGUAGE RebindableSyntax #-} をすることで,一つ目のエラーだった例も使える.
Prelude> :l test-do-notation1.hs
[1 of 1] Compiling Test1 ( test-do-notation1.hs, interpreted )
Ok, modules loaded: Test1.
*Test1> test 1
Hoge 1
おしまい
以外って程ではなかったかも...? まぁ,型クラスのインスタンス探すのって大変ですよね... 型クラスは本当に便利だけど,その代わりの弊害って感じだ.