Template Haskell のお勉強ちう、そのさん - 準クォート

そのいち、そのに、とはあまり関係ないですが、いちおう続きです。quasi-quote とか準クォートとかいうもののお話です。

はいっ、そういうものです。

パーサに渡す文字列は、

[quasiquoter|hogehoge|]

の、ように、通常の Template Haskell と同じようなやつ (Oxford bracket というらしい) で括ります。quasiquoter の部分は、プログラマが好きな名前に決めます。括った文字列は、呼ばれた文法上の位置に応じたパーサに渡され、パーサは Q モナドを返します。ExpQ とか PatQ とか。

Common Lisp のリーダーマクロとか、同じような仕組みなんでしょうか。Common Lisp よく知りません。

様々な DSL を実装するのに使われているようです。よく知りません。

もっとちゃんとした解説を読みたい方は、例によって @mr_konn さんのチュートリアル (準クォートでもてかわゆるふわメタプログラミング! - はてな使ったら負けだと思っている deriving Haskell - haskell) とか読むといいと思います。

このような仕組みですので、Haskell に好きな言語を埋め込めます。そう! 例えば Lazy K とか!

そんなわけで lazyK という QuasiQuoter を作ってみました。これを使うと Hello World がこんな感じです *1

{-# LANGUAGE TemplateHaskell,QuasiQuotes #-}

import LazyKQQ
import Control.Applicative ((<$>))

program =
    [lazyK|
     `k``s``si`k``s`k```sii``s``s`kski``s``s`ksk``s``s`ksk```s``siii``s``s`k
     ski`k``s``si`k``s``s`ksk```s``s`kski``s`k``s``s`kski``s``s`ksk```sii``s
     ``s`kski`k``s``si`k``s`k```sii``s``s`kski```sii``s``s`ksk``s``s`kski`k`
     `s``si`k``s`k```sii``s``s`kski```sii``s``s`ksk``s``s`kski`k``s``si`k``s
     ``s`ksk``s`k``s``s`kski``s``s`ksk``s`k``s``s`kski```sii``s``s`ksk``s``s
     `kski`k``s``si`k````s``s`ksk```s``siii``s``s`kski`s``s`ksk```sii``s``s`
     ksk``s``s`kski`k``s``si`k``s`k``s``s`kski```s``siii``s``s`kski`k``s``si
     `k``s`k``s``s`ksk``s`k``s``s`kski``s``s`ksk``s``s`kski``s``s`ksk```s``s
     iii``s``s`kski`k``s``si`k``s``s`ksk``s`k``s``s`kski``s``s`ksk``s`k``s``
     s`kski```sii``s``s`ksk``s``s`kski`k``s``si`k``s`k``s``s`kski``s``s`ksk`
     `s`k``s``s`kski``s``s`ksk```sii``s``s`ksk``s``s`kski`k``s``si`k``s`k```
     sii``s``s`kski```sii``s``s`ksk``s``s`kski`k``s``si`k```s``s`kski``s`k``
     s``s`kski``s``s`ksk```sii``s``s`kski`k``s``si`k``s``s`ksk``s`k``s``s`ks
     ki```s``siii``s``s`kski`k``s``si`k``s`k``s``s`kski``s``s`ksk```sii``s``
     s`kski`k``s``si`k```sii```sii``s``s`kski`k```sii```sii``s``s`kski
     |]
main = output . eval =<< (program :$) . encode <$> getContents

[lazyK| 〜 |] で括った部分を書き換えると、当然ですが別のプログラムになります。Unlambda インタプリタ *2 とか。 *3

{-# LANGUAGE TemplateHaskell,QuasiQuotes #-}

import LazyKQQ
import Control.Applicative ((<$>))

program =
    [lazyK|
````sii``s``s`ks``s`k`s`ks``s``s`ks``s`kk``s`ks``s`k`s`k``si`kk``s`k`s``s``si`
kk`k``si`k`ki``s`kk``s``s`k``s``s`ks``s`kk``s`k``s``s`ksk``s`k``s``s`kski```s`
`siii``s``s`kski``s``s`ks``s`kk``s`ks``s`k`sik`kk``s`k`s``s`k``s``s`kski``s``s
`ks``s`kk``s`ks``s`k`sik`kk``s``s`ks``s`kk``s`ks``s`k`si``s`kk``s`k`s`k``sii``
s``s`ks``s`k`s`ks``s`k`s`kk``s`k`s`ks`s`k`s``s``s``s``si`kk`k``si`k`ki`k````s`
`s`kski```s``s`ksk```sii``s``s`kski``s`k`s``si`k`kik``s``si`kk`k```sii``s`k``s
`k`s``si`k`kik``sii`kk`k`k``s``s`ks``s`kk``sii`k``si`k`ki``s`k`s`kk``s`k`s``s`
k``s``s`kski``s`k``s``s`ksk```sii``s``s`kski``s``s`ks``s`kk``s`ks``s`k`sik`kk`
`s`k`s`k``s`k`s``si`k``s``s`ks``s``s`ksk`k``s`k`si``s`kk``s`k`s``s`ksk``s`kk``
s`ks``s`kk``s`ks```ss`s``s`ks``s`kk``s`ks``s`k`si```ss`si`kk`kk`k``si`k`kik``s
`k`s``s`k```s``siii``s``s`kski``s``s`ks``s`kk``s`ks``s`k`sik`kk``s`k`s`k``s`k`
s``si`k``s``s`ks``s``s`ksk`k``s`k`si``s`kk``s`k`s``s`ksk``s`kk``s``s`ks``s`k`s
`ks``s`k`s`k`s`ks``s``s`ks``s`kk``s`ks``s`k`s`ks``s`k`s``s`ksk``s`kk``s`k`s`k`
`s`k`sik``s`k`s`k``si`kk``s`k`s``s``si`kk`k``si`k`ki``s`kk``s``s``si`kk`k``s`k
`s``si`kik`k``s``si`k```sii``s`k`s``s`ksk``s`kk``s`k`s`kk``s`k`si``s`kk``sii`k
```sii``s`k``s`k`s``si`kik``sii`k``s`kkk`k`k`ki`k``si`k`kik``s`k`s`k``s`k`s``s
i`k``si`k``si`k``s``s`ksk`k``s``s`ks``s`k`s`ks``s`k`s``s`ks``s``s`ksk`k``s`k`s
i``s`kk``s`k``si`kk``s``s``s``si`k`ki`kk`k``si`k`ki`k`````sii```sii``s``s`kski
``s`k`s``si`kik`k```sii``s`k`s``s`ksk``s`kk``s`k`s`kk``s`k`si``s`kk``sii``s`kk
k`k`k``si`k`kik``s`k`s`k``si`k`ki``s`k`s``s`k``s``s`kski``s`k```s``siii``s``s`
kski``s``s`ks``s`kk``s`ks``s`k`sik`kk``s``s`ks``s`kk``s`ks``s`k`si``s`kk``s``s
`ksk``s``s`ks``s`kk``s`ksk`k``s``s`ks``s`kk``s`ksk`k``s``s`ks``s`kk``s`ksk`k``
s``s`ks``s`kk``s`ks``s`k`sik`kk`k``s`kk``s``s`k``s``s`kski``s``s`ks``s`kk``s`k
s``s`k`sik`kk``s`k`s``si`k``si`k``si`k``s``s`ksk`k``s``s`ks``s`k`si``s`kk``s`k
`si``s`kk``s`k`s`kk``s`k`sikk``s`kk``s`k`s``si`k``si`k``si`k``s`k`si``s`kk``s`
k`s``s`ksk``s`kk``s``s`ks``s`kk``s`ksk`k``s`k`s``s`ks``s`k`si``s`kk``s`k`sik``
s`kkk``s`kk``s`k`s``si`k``si`k``si`k``s`kk``si`k`k`k`k```sii```sii``s``s`kski`
`s`kk``s``s`k``s``s`ksk``s``s`kski``s``s`ks``s`kk``s`ks``s`k`sik`kk``s`k`s``si
`k``si`k``si`ki``s`kk``s``s`ks``s`k`sik``s`kk``s`k`s``si`k``si`k``si`k``s``s`k
sk`k``s``s`ksk`k``s`k`s``s`ksk``s`kk``s`k`s`kk``s`k`sik``s`kk``s``s`k``s``s`ks
ki``s`k``s``s`ksk``s``s`kski``s``s`ks``s`kk``s`ks``s`k`sik`kk``s`k`s``si`k``si
`k``si`k``s``s`ksk`k`s`k`s`k``s`k`s``si`k``s`k``s``s`kski``s``s`ksk```sii``s``
s`kskik``s`kk``s`k`s``si`k``si`k``si`k``s``s`ksk`k``s``s`ksk`k``s`k`s``s`ksk``
s`kk``s`k`s``s`ksk``s`kk``s`k`s`k`s``s`ksk``s`k`s`kk``s``s`ks``s`kk``s`ks``s`k
k``s`ks``s``s`ksk`k``s`k`sik`k``s``s`ks``s`kk``s`ks``s`k`s`ks``s`k`s`k`si``s`k
`s`kk``s``s`ksk`k``s`k`sik`k``s`kkk``s`kk``s``s`k``s``s`kski``s``s`ks``s`kk``s
`ks``s`k`sik`kk``s`k`s``si`k``si`k``si`k```sii``s`k`s``s`ksk``s`kk``s`k`s`kk``
s`k`si``s`kk``sii``s`kk``s``s`k``s``s`ksk```sii``s``s`kski``s``s`ks``s`kk``s`k
s``s`k`sik`kk``s`k`s``si`k``si`k``si`k``s``s`ksk`k``s``s`ks``s`k`s`ks``s`k`s``
s`ks``s``s`ksk`k``s`k`si``s`kk``s``s``s`k``si`kk``s``s``si`kk`k``si`k`ki`k````
`sii```sii``s``s`kski``s`k`s``si`kkk`k`ki``s`k`s``s`ksk``s`kk``s`ks``s`kk``s`k
s```ss`s``s`ks``s`kk``s`ks``s`k`si```ss`si`kk`kk`k```sii``s`k`s``s`ksk``s`kk``
s`k`s`kk``s`k`si``s`kk``sii``s`kkk`k`ki``s`kk```ss`s``s`ks```ss`s``s`ks``s`kk`
`s`ks``s`k`sik`kk`k``sii``sii`k`k``s`k`s``si`k``s``s`ksk``s`k``s``s`kski`````s
ii``s``s`kski`s``s`ksk```sii``s``s`ksk``s``s`kskik`kk`k`k``si`k`ki``s``s`ks``s
`kk``si`k`k`k`k```sii```sii``s``s`kski`k``s`k`s``si`k```sii```sii``s``s`kskik
    |]
main = output . eval =<< (program :$) . encode <$> getContents

Unlambda インタプリタなので、コンパイルして、実行して、標準入力に Unlambda プログラム *4 を流し込むと、実行できます。当たり前ですが。

$ ghc --make unlambda.hs 
[2 of 2] Compiling Main             ( unlambda.hs, unlambda.o )
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Loading package pretty-1.1.1.0 ... linking ... done.
Loading package array-0.4.0.0 ... linking ... done.
Loading package deepseq-1.3.0.0 ... linking ... done.
Loading package containers-0.4.2.1 ... linking ... done.
Loading package template-haskell ... linking ... done.
Linking unlambda ...

$ ./unlambda < fibonacci.unlambda | head

*
*
**
***
*****
********
*************
*********************
**********************************

github リポジトリはこちら → https://github.com/h-hirai/LazyKQQ

Unlambda でなく Lazy K セルフインタプリタとかでも面白いと思います。今回、Lazy K 処理系自体は Lazy K セルフインタプリタのお守り *5で有名 (?) な @fumieval さんのインタプリタ *6 をほぼそのまま使わせていただいています。

書き換えた部分は、parse 関数くらいで、こんな感じになってます。もともとは Expr を返すようになっていたのを ExpQ を返すように書き換えたわけですね。

parse :: String -> (ExpQ, String)
parse ('`':xs) = let (a0, xs') = parse xs
                     (a1, xs'') = parse xs' in
                 (infixE (Just a0) (conE '(:$)) (Just a1), xs'')
parse ('s':xs) = ([|S|], xs)
parse ('k':xs) = ([|K|], xs)
parse ('i':xs) = ([|I|], xs)
parse (_:xs) = parse xs
parse "" = ([|I|], "")

で、QuasiQuoter として使えるようにするためには、この parse を QuasiQuoter 値コンストラクタでラップして lazyK 変数に束縛します。

lazyK = QuasiQuoter { quoteExp = fst . parse
                    , quotePat = undefined
                    , quoteType = undefined
                    , quoteDec = undefined
                    }

あと、準クォートなしの Template Haskell と同様に、利用側より先にコンパイルされるように、別モジュールにする必要があります。

余談

もともと main はこのように書いてました。

main = do
  input <- getContents
  output $ eval $ program :$ (encode input)

これを、もともとの @fumieval さんのコードを参考に、このように書き換えてみたりしたわけなんですが、

main = output . eval =<< (program :$) . encode <$> getContents

……書き換える前の方が読みやすい気がする。