時代遅れひとりFizzBuzz祭り、今回は……正式名称がちょっと分からないのだが、T4 Text Templateでよいのだろうか? Visual Studio 2005以降に付属しているテキスト変換の仕組みで、専用の記法でテンプレートを記述しておき、それを使ってコードを自動生成させるのだ。
前回のVim scriptとの関連は、形は違うけど「プログラミングの自動化」という点だ。Vim scriptはVimでプログラミングをする際に何かしら処理を自動化する為の汎用の手段だ。一方T4 Text Templateはテンプレート機能に特化しているが、Visual Studioでの開発を自動化する手段の一つだ。どちらも上手に使えばプログラマが幸せになれる、かもしれない。
もっともT4 Text Templateの変換エンジンはVisual StudioのIDEから独立している。テンプレート機能限定だけど、より広範囲に使用できる余地があるのかもしれない。
とりあえずVisual Studio 2010のT4 Text Templateを使ってみた。Visual Studio 2005もインストールしてあるのだが、そちらの方のテンプレートエンジンは見当たらなかった。
まず最初のバージョン。
<#@ template language="C#" culture="ja-JP" #> <#@ output extension=".txt"encoding="utf-8" #> <# for (var i = 1; i <= 100; ++i) { var s = (i%3 == 0) ? "Fizz" : ""; if (i%5 == 0) s += "Buzz"; #> <#= ((s == "") ? i.ToString() : s) #> <# } #>
`<#' と `#>' で囲まれた部分には、テンプレート変換のエンジンに対する設定を記述したり、テンプレートを変換するための処理を記述する。それ以外の部分は基本的にそっくりそのまま変換後のテキストに出力される。
`<#@' で始まるテンプレート命令は、テンプレートエンジンの設定を変更する為に使用する。`<#' で始まるブロックには、テンプレートを変換する処理を記述する。`<#=' で始まるブロックは、その中のコードを評価した結果が出力される。
変換処理のコードはC#に似ている――というかC#そのままだ。先頭行のテンプレート命令の属性languageで `C#' と指定したので、テンプレート変換を制御するコードはC#で記述している。今の所C#以外にはVisual Basicが使えるようだ。
<#@ template language="VB" culture="ja-JP" #> <#@ output extension=".txt"encoding="utf-8" #> <# Dim i As Integer Dim s As String For i = 1 To 100 If i Mod 3 = 0 Then s = "Fizz" Else s = "" End If If i Mod 5 = 0 Then s &= "Buzz" If s = "" Then s = i.ToString() #> <#= s #> <# Next #>
属性languageに `VB' を指定し、Visual Basicで書いてみた。この属性を省略した場合のデフォルトはC#となる。
さて、このテンプレートをfizzbuzz.ttという名前で保存したとする。これを変換することでFizzBuzzした結果が改行区切りで記述されたテキストファイルができあがる。今回はVisual Studio 2010のIDE上ではなく、コマンドプロンプトから付属のコンソールアプリを使う。PATHを通すのが面倒なので、こんなバッチファイルを用意。
@echo off :: t4.bat "C:\Program Files\Common Files\Microsoft Shared\TextTemplating\10.0\TextTransform.exe" %*
このバッチファイルを、こんな風に呼び出す。
t4.bat fizzbuzz.tt
変換結果はfizzbuzz.txtに出力される。
テンプレートにはプロパティやメソッドを記述することもできる。この場合、`<#+' で始まるブロックに記述する。
<#@ template language="C#" culture="ja-JP" #> <#@ output extension=".txt"encoding="utf-8" #> <# for (var i = 1; i <= 100; ++i) { #> <#= fizzbuzz(i) #> <# } #> <#+ static string fizzbuzz(int i) { var s = (i%3 == 0) ? "Fizz" : ""; if (i%5 == 0) s += "Buzz"; return (s == "") ? i.ToString() : s; } #>
メソッドfizzbuzzを定義して、使用している。
ここまでは普通にFizzBuzzしてきたが、折角のテンプレート機能なので、もう少しそれらしい風に攻めてみた。
<#@ template language="C#" culture="ja-JP" #> <#@ output extension=".c"encoding="us-ascii" #> #include <stdio.h> int main(void) { <# for (var i = 1; i <= 100; ++i) { fizzbuzz(i); } #> return 0; } <#+ private void fizzbuzz(int i) { var s = (i%3 == 0) ? "Fizz" : ""; if (i%5 == 0) s += "Buzz"; #> (void) puts("<#= ((s == "") ? i.ToString() : s) #>"); <#+ } #>
C言語による力任せなFizzBuzzのコードを生成するテンプレート。自前でこんなコードを書くと白い目で見られること間違いなしだが、テンプレートから自動生成する分にはセーフ……かもしれない。*1
ちなみに、同じ力任せのコードでも、個人的にはこちらのテンプレートで生成したものの方が好みだ。
<#@ template language="C#" culture="ja-JP" #> <#@ output extension=".c"encoding="us-ascii" #> #include <stdio.h> int main(void) { static const char * const msg[] = { <# for (var i = 1; i <= 100; ++i) { #> "<#= fizzbuzz(i) #>", <# } #> NULL, }; const char * const *p; for (p = msg; *p != NULL; ++p) { (void) puts(*p); } return 0; } <#+ static string fizzbuzz(int i) { var s = (i%3 == 0) ? "Fizz" : ""; if (i%5 == 0) s += "Buzz"; return (s == "") ? i.ToString() : s; } #>
もっとも力任せだという点であまり違いはない。
「テンプレート変換によるテキストの自動生成」というアイデアは、プログラミングに限らず仕事において重宝するものだ。私自身、テンプレートから作業スクリプトやメールの文面を自動生成するRubyのスクリプトを使っている。
で、実際にテンプレートを作るとなると、雛形となる文書中にスクリプトを埋め込むスタイルが望ましいことも多い。私がテンプレートをRubyで書いた理由は、ヒアドキュメントに書いたテンプレートの中に式展開を埋め込むことができるからだ。もっともバックスラッシュを書くときに注意しなくてはならない点が玉に瑕だが。
そういった意味ではT4 Text Templateはかなり使いやすい。というのも `<#' と `#>' 以外はエスケープする必要がないからだ。
そしてC#やVBでテンプレート変換のコードを記述できる点も見逃せない。Windowsアプリの開発に慣れている人なら新たに専用の記法を覚えるよりもC#やVBをそのまま使える方が楽だろう。C++プログラマは……難しいことをやらなければ、C#で記述しても何とかなる*2気がする。
もっともT4 Text Templateの処理系たるテンプレートエンジンを使うためには今の所Visual Studioをインストールする必要があるし、Windows以外の環境で使えるエンジンもMonoに付属のものぐらいだ。単体のツールとしてもうちょっと気楽に使えないものだろうか?