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
……書き換える前の方が読みやすい気がする。
*1:http://ja.wikipedia.org/wiki/Hello_world%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E4%B8%80%E8%A6%A7#Lazy_K
*2:http://legacy.e.tir.jp/wiliki?%CB%DD%CC%F5%3A%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0%B8%C0%B8%ECLazy_K
*3:これ見ると分かるように、bracket の中はインデントとか関係なくなるみたいです。
*4:http://legacy.e.tir.jp/wiliki?%cb%dd%cc%f5%3a%a5%d7%a5%ed%a5%b0%a5%e9%a5%df%a5%f3%a5%b0%b8%c0%b8%ecUnlambda
*5:ImageShack - Best place for all of your image hosting and image sharing needs
*6:公開早々修正: https://twitter.com/fumieval/status/226655795574607872 https://twitter.com/fumieval/status/226656190938103808