レベル: 中級 Elliotte Rusty Harold (elharo@metalab.unc.edu), Adjunct Professor, Polytechnic University
2005年 6月 29日 XSLTはHaskellやSchemeに似た関数型プログラミング言語であり、CやFortranには似ていません。したがって、ループもなければ、mutable変数もありません。代わりに、これらの構成体を再帰とパラメーターに置き換える必要があります。このTipでは、名前付きテンプレートとxsl:call-template、xsl:with-param、およびxsl:param要素を使用してこの機能を備える方法を説明します。
XSLTはチューリング完備です。つまり、十分なメモリーさえあれば、XSLTは他のチューリング完備言語(C++など)で計算できるものなら何でも計算できます。これは、より伝統的な言語に慣れているプログラマーにとって、ちょっとした驚きです。結局、XSLTには、ループやmutable変数も含めて、多くのアルゴリズムにとって重要ないくつかの機能が欠けています。
注:XSLTで変数と呼ばれるものは、他のほとんどの言語では定数と呼ばれています。従来のプログラミングでの変数よりは代数変数に近いものです。
関数型プログラミング
先ほど述べた欠落は、過失によるものではありません。XSLTは、手続き型言語ではなく関数型言語です。CやPascalなどの手続き型言語では、プログラムは一連のステップとして定義され、それらを指定された順序で実行することによって、シーケンスの最終ステップとして最終結果が生成されます。関数型言語では、プログラムは他の関数から成る関数として定義され、それらを評価することで最終結果が導き出されます。関数型言語の大きな利点は、実行の順序は問題ではないということです。単純な例として、次のような2つの(代数)関数を考えてみましょう。
関数h(x)はfとgの合成であるとします。
この関数は、gを先に評価してもかまいませんし、
h(x) = f(x - 3) = 2 * (x - 3) = 2x - 6 |
fを先に評価してもかまいません。
h(x) = 2 * g(x) = 2 * (x - 3) = 2x - 6 |
どちらも同じ答えになります。言語の関数性は、言語を並列処理になじみやすくします。どの部分を他の部分より先に評価するか気にせずに、プログラムの複数の部分を同時に評価できるからです。当然、スレッドセーフです。
関数型言語は、XSLTも含めて、従来のループを含むことができません。ループには時間的な前後関係があるからです。すなわち、典型的なループは、i==1がi==2よりも前に起こるように作成され、コンパイルされます。もちろん、ループを前方ではなく後方に実行したり、ループ・カウンターを1以外の値で増分したり、あるいは、while文のようにループ・カウンターを完全になくすこともできます。しかし、ループの種類に関係なく、実行の順序が重要であり、この点が関数型プログラミングとは対照的です。
再帰
関数型言語では、従来の言語ではループで行われているタスクのほとんどが、代わりに再帰によって行われます。パラメーターが変数の代わりです。たとえば、最近、私は、コンパイル時には個数がわからないが、一定の数のドット(ピリオド)をプリントする方法を尋ねられました。これは、たとえばレストランのメニューを書式化するときに便利です。メニューでは、料理名と価格の間のドットの数を料理によって変えなければならないことが多いからです。
Crawfish Etoufee.......$9.95
Fried Chicken..........$6.95 |
Cでは、次のような単純な関数を使います。
void printDots(int n) {
int i;
for (i = 0; i < n; i++) {
printf(".");
}
} |
しかし、これが問題を解決する唯一の方法ではありません。ループの代わりに、次のような再帰を使うこともできます。
void printDotsRecursively(int n) {
if (n > 0) {
printf(".");
printDots(n-1);
}
} |
これはCでは珍しいやり方かもしれません。しかし、XSLTでは、再帰が唯一の選択肢です。
次のテンプレートは、countパラメーターの値として渡された正確な数のドットを生成します。ロジックは簡単です。$countの値がゼロより大きければ、ピリオドを出力し、countパラメーターを1だけ減らして、関数を再び呼び出します。そうでない場合は何もしません。これは基本的に、printDotsRecursively関数で使用されているのと同じアルゴリズムであり、CではなくXSLTで実装されているだけのことです。
<xsl:template name="dots">
<xsl:param name="count" select="1"/>
<xsl:if test="$count > 0">
<xsl:text>.</xsl:text>
<xsl:call-template name="dots">
<xsl:with-param name="count" select="$count - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template> |
たとえば、ドットを100個プリントするには、countパラメーターの値として100を指定してテンプレートを呼び出します。
<xsl:call-template name="dots">
<xsl:with-param name="count" select="100"/>
</xsl:call-template>
|
定数値を渡す代わりに、プリントするドットの数を何か他の値に基づいて計算することもできます。たとえば、次の命令は、価格と料理名の長さ(より具体的に言うと、コンテキスト・ノードのpriceおよびentree子要素の文字列値の長さ)を除いて、メニューの1行80文字を埋めるのに必要な数のドットをプリントします。
<xsl:call-template name="dots">
<xsl:with-param name="count"
select="80 - string-length(entree) - string-length(price)"/>
</xsl:call-template>
|
まとめ
ループを再帰に置き換えるには、C、XSLT、またはSchemeのいずれでも、ある程度の慣れが必要です。しかし、このテクニックにはエレガンスさがあります。XSLTで頻繁に使用する必要があるわけではありませんが、標準のXSLTでは他に方法がない厄介なタスクを成し遂げることができます。
参考文献
- Wikipediaにある、関数型プログラミングに関する記事を読んでください。ここでは、チューリング完備に関しても学ぶことができます。
- Michael Kay著によるXSLTへの標準的な入門記事、XSLT Programmer's Referenceを読んでください。彼はdeveloperWorksにもXSLTに関する2本の記事、「XSLTはどのような言語か」(2005年4月)と「Saxon: XSLTプロセッサーの解体新書」(2005年4月)を書いています。
- XMLテンプレートの基礎を解説したElliotte Haroldの著書、Processing XML with JavaのChapter 17で、再帰について詳しく学んでください。
- W3CのXSLT 2.0仕様を読むと、この記事でXSLT 1.0での再帰を使って行ったタスクを、もっと容易に行う方法が他にも幾つかあることが分かります。しかしXSLT 2.0は、XSLTの基本的な機能は変えていません。ですから多くのタスクでは、やはり再帰が必要になります。
- ここで議論したメニューの問題を解決するために、EXSLTのパディング機能を試してみてください。ただしこれは、一つのことしかできない代物で、再帰によって解決できるような他の多くの問題には対応していません。EXSLTパディング機能の純粋なXSLT実装では、この記事で紹介した、もう少し効率的な再帰アルゴリズムを使用しています。
-
developerWorksのXMLゾーンでは、XMLに関する資料が他にも豊富に用意されています。
- XMLおよび関連技術においてIBM認証開発者になる方法については こちら を参照してください。
著者について
記事の評価
|