普段Javaなんてめったに使わないのですが、たまたまコーディングをせざるを得なくなり、同業の女房の持っている本を見たら「Javaによる関数プログラミング」というのがあったので読んでみました.
Java8になって追加されたメソッドを使い、コレクションをスマートに処理する例が載っています.例えば
final List<String> friends = Arrays.asList("Brian", "Nate", "Neal", "Raju", "Sara", "Scotto");
とあったとき、これを1行毎に列挙して出力するのを昔はfor文で
for(int i=0; i < friends.size(); i++){ System.out.println(friends.get(i)); }
なんてベタでやっていたのを、IterableインタフェースにあるforEach()というメソッドを使って、
friends.forEach((name) -> System.out.println(name));
とするのが今のやり方.
また、大文字に変換して1行に列挙するのはStreamインタフェースに追加されたmap()メソッドで
friends.stream()
.map(name -> name.toUppercase())
.forEach(name -> System.out.print(name + " "));
System.out.println();
とやってしまいます.昔のfor文で回すイメージとは随分変わったものです.
ではXSLTではどうなのか?ちょっと試してみました.
まず次のような準備を整えます.ahf:system.out.printlnという関数でメッセージを出力します.XSLTではXPath式からは間接に関数を介さないと標準出力に出せないからです.
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:math="http://www.w3.org/2005/xpath-functions/math" xmlns:ahf="http://www.antennahouse.com/names/XSLT/Functions/Document" exclude-result-prefixes="#all" version="3.0"> <xsl:variable name="friends" as="xs:string+" select="('Brian','Nate','Neal','Raju','Sara','Scott')"/> <xsl:function name="ahf:system.out.println" as="empty-sequence()"> <xsl:param name="prmStr" as="xs:string"/> <xsl:message select="$prmStr"/> </xsl:function> <xsl:template match="/"> <!-- ここにfriendに対してやりたい処理を書く--> </xsl:template> </xsl:stylesheet>
名前を1行毎に列挙して出力する.
これはSimple Map Operator !を使えばいとも簡単です.決してxsl:for-eachなんて使ってはいけません.
<xsl:sequence select="$friends ! ahf:system.out.println(.)"/>
[結果:oXygenのログ]
[xslt] Brian [xslt] Nate [xslt] Neal [xslt] Raju [xslt] Sara [xslt] Scott
名前を大文字に変換して1行に列挙する.
これも"!"とArrow Operator "=>" にupper-case()を使えば簡単です.
<xsl:sequence select="$friends ! upper-case(.) => string-join(' ') => ahf:system.out.println()"/>
[結果]
[xslt] BRIAN NATE NEAL RAJU SARA SCOTT
名前の文字数をカウントして1行に出力する.
Javaでは
friends.stream()
.map(name -> name.length())
.forEach(name -> System.out.print(name + ""));
XSLTでは
<xsl:sequence select="$friends ! string-length(.) => string-join(' ') => ahf:system.out.println()"/>
[結果]
[xslt] 5 4 4 4 4 5
"N"から始まる名前の数をカウントして出力する.
Javaでは、
final List<string> startsWithN = friends .stream() .filter(name -> name.startsWith("N")) .collect(Collections.toList()); System.out.println(String.format("Found %d names", startsWithN.size()));
XSLTではもう少し頑張って、filter()なんてifで自前で書きます.
<xsl:variable name="times" select="$friends ! (if (starts-with(.,'N')) then . else ()) => count() => string()"/> <xsl:sequence select="('Found '|| $times ||' times') => ahf:system.out.println()"/>
[結果]
[xslt] Found 2 times
最も長い文字数の名前を出力する.複数あったら最初のものを出す.
Javaでは、
final Option<String> aLongName = friends.stream() .reduce((name1, name2) -> name1.length() >= name2.length()? name1 : name2); aLongname.ifPresent(name -> System.out.println(String.format("A longest name: %s", name)));
XSLTではreduceなんていうのはないので、高階関数を使えば次のように書けます.(もっとスマートな方法あったら教えてください)
<xsl:variable name="aLongName" as="xs:string?" select="$friends => (let $getLength := function ($strings as xs:string*){for-each($strings,function ($a as xs:string) {string-length($a)})}, $getMaxLength := function ($strings as xs:string*, $getLength as function (xs:string*) as xs:integer*){max($getLength($strings))}, $maxLength := $getMaxLength(?, $getLength), $maxString := function ($strings as xs:string*){$strings[. => string-length() eq $maxLength($strings)]} return $maxString(?))() => head()"/> <xsl:sequence select="if ($aLongName => exists()) then ('A longest name:' || $aLongName) => ahf:system.out.println() else ()"/>
[結果]
[xslt] A longest name:Brian
XSLT3.0では、Simple Map Operator "!"、Arrow Operator "=>"、それに高階関数を使うとXSLT2.0では考えも及ばなかったようなコーディングができるようになります.本当に知っていて損はありません.
※ もっぷさんからコメントのありましたようにXPath 3.1ではfold-left()という関数があって、以下のように一発で最も長い文字数の名前を出すことができます.
<xsl:variable as="xs:string?" name="aLongName" select=" $friends => fold-left( '', function($name1, $name2) { if (string-length($name1) ge string-length($name2)) then $name1 else $name2 } )" /> <xsl:sequence select="if ($aLongName => exists()) then ('A longest name: ' || $aLongName) => ahf:system.out.println() else ()"/>
fold-left()関数は以下に仕様があります.