ちょっとイロイロあって 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 型クラスのインスタンスではない. ちなみに,Docpretty ライブラリで定義されている

どっかでインスタンス化されてるのかと思ったが見つからない. というか,そもそも 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 = id
Prelude> :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 型クラスのクラスメソッドを探されるのかなぁ?

ちなみに

“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

おしまい

以外って程ではなかったかも...? まぁ,型クラスのインスタンス探すのって大変ですよね... 型クラスは本当に便利だけど,その代わりの弊害って感じだ.