週に一回は書きますよ 月に4つ記事を書けばノルマは満たされます。
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Template Haskellを調べてみました。直前のseqAllに使えるかと思いまして。

Template HaskellはHaskell用のテンプレートプログラミングあるいはマクロです。Haskellのプログラム中に「Haskellコードを生成するHaskellコード」を貼り付けてコンパイル時に実行します。lisp/schemeのマクロとかc++ templatesと同じのりです。lispのマクロがc++のtemplatesでないように、Template Haskellもそれらとは結構違いますが。

Template Haskellを使えばたとえば次のコードがかけます。

  • n-タプルのm番目を選択する関数sel n m。$(sel 1 3) (x, y, z) == x
  • zip, zip3, zip4などを統一的にできるzipN。$(zipN 3) == zip3
  • 上のzipNを1からNまで一度に宣言するgenZipN。

上のコード例にあるように、Template Haskellの部分は$(..)で囲まれています。この部分がコンパイル時に評価され、その結果のHaskellプログラムがここに挿入されます。

  • 引数によって型が変わる関数が定義できる。上の例を参照。
  • Template Haskellによって生成されたコードにはちゃんと型検査が行われる。 $(sel 1 3) (x, y)と書くと型エラーになる。
  • 引数に変数は入れられない。\x -> $(sel x 3)なんてコードはかけない。

とりあえずちょっと地味な例として、template haskellで書いたprintf関数が論文の頭に載っています。haskellにはすでにText.Printfモジュールにprintf関数を持っているので、それとの比較をしてみます。

まずは普通のprintfを使ったコードから。

import Text.Printf
main :: IO ()
main = do
    let x :: Int
        x = 3
        y :: Int
        y = 4
    putStrLn "trial: 1"
    printf "variable %s = %d\n" "x" x
    putStrLn "trial: 2"
    printf "variable %s = %s\n" "y" y

実行してみます。

>ghc --make NormPrintf.hs
Linking NormPrintf.exe ...

>NormPrintf.exe
trial: 1
variable x = 3
trial: 2
NormPrintf.exe: Printf.printf: bad argument
次にTemplate Haskellで書いたPrintfを使ってみます。
import ThPrintf (printf) -- definition of printf.
main :: IO ()
main = do
    let x :: Int
        x = 3
        y :: Int
        y = 4
    putStrLn "trial: 1"
    putStrLn ( $(printf "variable %s = %d") "x" x)
    -- putStrLn "trial: 2"
    -- putStrLn ( $(printf "variable %s = %s\n") "y" y)
実行してみます。ghcでは、コンパイラオプションに -XTemplateHaskellをつけるとTemplate Haskellが有効になります。
>ghc --make -XTemplateHaskell TPrintf.hs
[2 of 2] Compiling Main             ( TPrintf.hs, TPrintf.o )
Loading package base ... linking ... done.
Loading package array-0.1.0.0 ... linking ... done.
Loading package packedstring-0.1.0.0 ... linking ... done.
Loading package containers-0.1.0.0 ... linking ... done.
Loading package pretty-1.0.0.0 ... linking ... done.
Loading package template-haskell ... linking ... done.
Linking TPrintf.exe ...

>TPrintf.exe
trial: 1
variable x = 3
ちなみに、ソースの中のコメントアウトを除くと、次のようにコンパイルエラーになります。
>ghc --make -XTemplateHaskell TPrintf.hs
[2 of 2] Compiling Main             ( TPrintf.hs, TPrintf.o )
Loading package base ... linking ... done.
Loading package array-0.1.0.0 ... linking ... done.
Loading package packedstring-0.1.0.0 ... linking ... done.
Loading package containers-0.1.0.0 ... linking ... done.
Loading package pretty-1.0.0.0 ... linking ... done.
Loading package template-haskell ... linking ... done.

TPrintf.hs:12:47:
    Couldn't match expected type `[Char]' against inferred type `Int'
    In the second argument of `$(printf "variable %s = %s\n")', namely
        `y'
    In the first argument of `putStrLn', namely
        `($(printf "variable %s = %s\n") "y" y)'
    In the expression: putStrLn ($(printf "variable %s = %s\n") "y" y)

まず、Template Haskellのprintfだと、引数を間違えると型エラーが生じます。標準のprintfは実行時までエラーがわかりません。これは便利。その一方で、printfのフォーマットを動的に変えたりすることができなくなります。

Template Haskellの便利さがわかりましたか?私にはまったくわかりません。そもそもすでにあるprintfを作ってもまったくアピールになりません。もっとえぐいものが必要です。

使用したOSはWinXPSP2Home, コンパイラはGHC 6.8.1でした。

printfのソースを続きに貼ります。元論文にこれの作りかけのものが貼ってあります。

module ThPrintf where

-- Skeletal printf from the paper.
-- It needs to be in a separate module to the one where
-- you intend to use it.

-- Import some Template Haskell syntax
import Language.Haskell.TH

-- Describe a format string
data Format = D | S | L String

-- Parse a format string.  This is left largely to you
-- as we are here interested in building our first ever
-- Template Haskell program and not in building printf.
parse :: String -> [Format]
parse ('%' : x : xs) =case x of
			'd' -> D : parse xs
			's' -> S : parse xs
			'%' -> L "%" : parse xs
			_ -> error "illegal pattern format"
parse (x : xs)   = L [x] : parse xs
parse [] = []

-- Generate Haskell source code from a parsed representation
-- of the format string.  This code will be spliced into
-- the module which calls "pr", at compile time.
gen :: [Format] -> ExpQ -> ExpQ
gen [] first = first
gen (D : xs) first = [| \n -> $(gen xs [| $first ++ show n |]) |]
gen (S : xs) first = [| \s -> $(gen xs [| $first ++ s |]) |]
gen (L s : xs) first = gen xs [| $first ++ $(stringE s) |]

-- Here we generate the Haskell code for the splice
-- from an input format string.
printf :: String -> ExpQ
printf s      = gen (parse s) [| "" |]
スポンサーサイト
コメント
この記事へのコメント
コメントを投稿する
URL:
Comment:
Pass:
秘密: 管理者にだけ表示を許可する
 
トラックバック
この記事のトラックバックURL
http://gusmachine.blog49.fc2.com/tb.php/299-e4063290
この記事にトラックバックする(FC2ブログユーザー)
この記事へのトラックバック
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。