Twitterでこんなつぶやきをしました。
が、ちょっと違う感じに受け取られたかな、と思ったので補足します。
つぶやきの中でいちいち「プログラミング言語としての」と書いたのは、
「普通の」SQLや「普通の」XSLTとは違って、ということを明示したかったからです。
普通はSQLは問合せのための言語だし、XSLTはXMLを変換するための言語*1であって、JavaやC#などのプログラミング言語と同じようなものと見ている人はほぼいないでしょう。
ただ、ちょっとしたテクニックを知っていると、これらを使って「普通の」プログラミング言語でやるような処理が書けてしまうのです。
XSLTにはループや分岐が組み込まれているので、手続き型プログラミングであればどうとでもなります。
なりますが、それでは面白くないのでループを封印してみましょう。
ループを使わないXSLTプログラミング
XSLTでは template を関数とみなすことで、ループを使わなくても再帰呼び出しが使えます。
あとは高階関数が使えればざっくり関数プログラミング的なものが出来ますね。
詳しい説明は そのアイディアを形にした人が書いたドキュメント に譲るとして、
そのテクニックを使うことでFizzBuzzが range 関数と map 関数と fizzbuzz 関数を組み合わせることで書けてしまいます。
xml version="1.0" encoding="UTF-8"
<xslstylesheet xmlnsxsl="http://www.w3.org/1999/XSL/Transform"
xmlnsxs="http://www.w3.org/2001/XMLSchema"
xmlnsfizzbuzz="fizzbuzz"
version="3.0">
<xsloutput method="text"/>
<xsltemplate name="range" as="xs:integer *">
<xslparam name="from" as="xs:integer"/>
<xslparam name="to" as="xs:integer"/>
<xslchoose>
<xslwhen test="$from <= $to">
<xslsequence select="$from"/>
<xslcall-template name="range">
<xslwith-param name="from" select="$from + 1"/>
<xslwith-param name="to" select="$to"/>
</xslcall-template>
</xslwhen>
<xslotherwise>
</xslotherwise>
</xslchoose>
</xsltemplate>
<xsltemplate name="map">
<xslparam name="sequence"/>
<xslparam name="f"/>
<xslchoose>
<xslwhen test="empty($sequence)">
</xslwhen>
<xslotherwise>
<xslvariable name="v">
<xslapply-templates select="$f">
<xslwith-param name="arg0" select="$sequence[1]"/>
</xslapply-templates>
</xslvariable>
<xslsequence select="$v"/>
<xslcall-template name="map">
<xslwith-param name="sequence" select="$sequence[position() > 1]"/>
<xslwith-param name="f" select="$f"/>
</xslcall-template>
</xslotherwise>
</xslchoose>
</xsltemplate>
<fizzbuzzfizzbuzz/>
<xsltemplate name="fizzbuzz" match="*[namespace-uri()='fizzbuzz']">
<xslparam name="arg0" />
<xslvariable name="n" select="number($arg0)"/>
<xslchoose>
<xslwhen test="$n mod 15 = 0">
<xslvalue-of select="'FizzBuzz
'"/>
</xslwhen>
<xslwhen test="$n mod 5 = 0">
<xslvalue-of select="'Buzz
'"/>
</xslwhen>
<xslwhen test="$n mod 3 = 0">
<xslvalue-of select="'Fizz
'"/>
</xslwhen>
<xslotherwise>
<xslvalue-of select="$n"/>
<xslvalue-of select="'
'"/>
</xslotherwise>
</xslchoose>
</xsltemplate>
<xsltemplate match="/">
<xslvariable name="from" select="input/from"/>
<xslvariable name="to" select="input/to"/>
<xslvariable name="xs" as="xs:integer *">
<xslcall-template name="range">
<xslwith-param name="from" select="$from"/>
<xslwith-param name="to" select="$to"/>
</xslcall-template>
</xslvariable>
<xslcall-template name="map">
<xslwith-param name="sequence" select="$xs"/>
<xslwith-param name="f" select="document('')/*/fizzbuzz:*[1]"/>
</xslcall-template>
</xsltemplate>
</xslstylesheet>
xml version="1.0" encoding="UTF-8"
<input>
<from>1</from>
<to>100</to>
</input>
# 実行方法
$ saxon -s:input.xml -xsl:style.xsl -o:output.txt
長いので折り畳みましたが、これと同じようなことを F# で書くとこうなります。
let rec range from ``to`` =
if from <= ``to`` then from :: range (from + 1) ``to``
else []
let rec map f = function
| [] -> []
| x::xs -> f x :: map f xs
let fizzbuzz n =
if n % 15 = 0 then "FizzBuzz\n"
elif n % 5 = 0 then "Buzz\n"
elif n % 3 = 0 then "Fizz\n"
else string n + "\n"
let xs = range 1 100
xs |> map fizzbuzz |> List.iter (printf "%s")
大体機械的に対応は取れると思います。
SQLでプログラミング
SQLは再帰クエリーによって再帰が書けます。
しかし、XSLTとは違い関数に対応させられるようなものが(SELECT文の範疇では)ありませんし、高階関数など夢のまた夢です。
そのため、SQLでのプログラミングはXSLTよりも困難です。
ただ、再帰クエリーでも使う WITH を「入力固定の関数」のようなものとみなすことで、ある程度の構造化は可能です。
WITH RECURSIVE range_ (n_) AS (
SELECT 1
UNION ALL
SELECT n_ + 1 FROM range_ WHERE n_ < 100
)
, fizzbuzz_ (n_, result_) AS (
SELECT
n_
, CASE
WHEN n_ % 15 = 0 THEN 'FizzBuzz'
WHEN n_ % 5 = 0 THEN 'Buzz'
WHEN n_ % 3 = 0 THEN 'Fizz'
ELSE CAST(n_ AS varchar)
END
FROM
range_
)
SELECT * FROM fizzbuzz_;
さらに複雑なことがしたい場合、空白区切りなどの文字列をリストと見立てて操作することでよりいろいろなことができるようになります*2。
構文って大事
こんな感じで、XSLTでプログラミングしようとすると大量のノイズで本来書きたい処理はタグの中に埋もれてしまいますし、
SQL(select)でプログラミングしようとするとそもそも関数からしてないので考え方からして変えないといけません(そのための例としてはFizzBuzzは小さすぎたかも)。
SQLで複雑なやつだと、 SQL で数式を評価 (完全版 + α) - ぐるぐる~ あたりがオススメです。数式評価だけど、演算子の優先順位を設定可能な完全に頭おかしいやつです。
ということで、他の人にはあまりオススメできない、オススメの構文のありがたみが体感できる方法でした。
制約された環境であれこれ考えるのが好きな人であればあるいは・・・