FreeMarker Template Language 入門(5)

パッケージJava製品開発担当の大です。こんにちは。

前回に引き続き、FreeMarker Template Language(以下 FTL)の紹介をします。

今回は、関数のお話です。

関数の定義

FTLで関数を定義するには、FTLタグのfunctionディレクティブを使用します。
サンプルを見たほうがわかりやすいでしょう。以下はおなじみの階乗を計算する関数です。

<#function fact n>
  <#if n == 0>
    <#return 1 />
  <#else>
    <#return fact(n - 1) * n />
  </#if>
</#function>

returnディレクティブで値を返します。他のプログラミング言語とそう変わりはないですね。利用するときはカッコをつけて呼び出します。

<#list 0..10 as i>
  ${i}! => ${fact(i)}
</#list>

実行結果:

  0! => 1
  1! => 1
  2! => 2
  3! => 6
  4! => 24
  5! => 120
  6! => 720
  7! => 5,040
  8! => 40,320
  9! => 362,880
  10! => 3,628,800

ローカル変数

これまで、変数の宣言ではassignディレクティブを使用してきました。関数の中では、関数内だけで有効な変数を宣言するlocalディレクティブを使用できます。(変数の宣言は、他にglobalディレクティブというのもあります。これらの違いはまた別に機会に説明します。)

以下の例は、整数nと配列lstを引数にとり、lstの要素のうちn以上の要素だけをもつ配列を返す関数です。結果の配列をローカル変数ansとして空で作成し、要素を順に評価しながら条件にあったものを非破壊的に追加しています。

<#function partg n lst>
  <#local ans = []>
  <#list lst as x>
    <#if (x >= n)>
      <#local ans = ans + [x]>
    </#if>
  </#list>
  <#return ans>
</#function>

実行してみましょう。配列のうち4以上の数を列挙してみます。

<#assign ls = [10, 2, 4, 5, 8, 1, 3]>
<#list partg(4, ls) as x>${x} </#list>

実行結果:

10 4 5 8 

第一級オブジェクトとしての関数

FTLでは、関数は第一級オブジェクト(first-class object)として扱われています。つまり、変数に代入したり、別の関数の引数として渡したりできます。

先程のpartg関数を少し改造して、比較関数predを引数として受け取る高階関数removeとして作り変えてみます。

<#function remove n lst pred>
  <#local ans = []>
  <#list lst as x>
    <#if !pred(x, n)>
      <#local ans = ans + [x]>
    </#if>
  </#list>
  <#return ans>
</#function>

このように定義しておけば、比較関数を変更するだけで違うリストを得ることができます。(残念ながら、匿名関数/ラムダ式を扱う仕組みはいまのところFTLでは用意されていないようですので、比較関数はいちいち名前をつけて定義する必要があります)。

<#function myge a b>
  <#return (a >= b)>
</#function>

<#function mylt a b>
  <#return (a < b)>
</#function>

<#assign ls = [10, 2, 4, 5, 8, 1, 3]>
<#list remove(4, ls, myge) as x>${x} </#list>
<#list remove(4, ls, mylt) as x>${x} </#list>

実行結果:

2 1 3 
10 4 5 8 

クイックソート

上記の比較関数とremove、それに配列の先頭要素以外を取得する関数restを使って、クイックソートアルゴリズムを実装してみます。

<#function rest lst>
  <#if lst?size <= 1>
    <#return []>
  <#else>
    <#return lst?reverse?chunk(lst?size-1)[0]?reverse>
  </#if>
</#function>

<#function qsort lst>
  <#if lst?size <= 1>
    <#return lst>
  <#else>
    <#local car = lst?first>
    <#local cdr = rest(lst)>
    <#return
      qsort(remove(car, cdr, myge)) +
      [car] +
      qsort(remove(car, cdr, mylt))>
  </#if>
</#function>

<#assign ls = [1, 10, 23, 3, 54, 2, 4, 6]>
<#list qsort(ls) as x>${x} </#list>

実行結果:

1 2 3 4 6 10 23 54 

このクイックソートは「こんなこともできるよ」というだけで、実用性はないので念のため。。。実際にソートしたければ、配列のビルトインsortを使ったほうがずっといいでしょう。

<#list ls?sort as x>${x} </#list>