テンプレートでクロージャ

MT-column > Tips
| | コメント(0) | トラックバック(0)

Tips というよりは変態的テンプレートの紹介という趣きのコラムが続きますが、今日はもう一つ、テンプレートでクロージャを生成する例を紹介します。今日の例はプログラミングに馴染みがある人でないと理解が難しいと思いますので、よく分からなそうな場合はまるごとスルーしてください。

クロージャとは

「クロージャ」という単語自体、プログラミングに馴染みがない人はおそらく聞いたこともないだろうと思いますが、プログラミングの世界で使われる概念で、ざっくりと一言でまとめると、「処理のまとまりと、その処理で使われる情報のセット」というようなものです。

この「セット」を作成する能力があることで何が便利かというと、「カウント中の値を覚えているカウンター」の作成や「ほとんど同じではあるけれどもちょっとだけ動作の違う処理」の複製といったことを、より簡単で間違えが起きにくい方法でできるようになります。はい、難しそうですが、でも便利そうです。

クロージャについての詳しい説明は、本コラムの主旨と異なるというところと、筆者のスキル不足のため、ここでは省きますが、Web 上にはよい資料がたくさん公開されていますので、そちらを参照していただくとよいと思います。

以降このコラムの中では、ざっくりとではありますが、「クロージャ」とは「処理のまとまりと、その処理で使われる情報のセット」である、というところで進めたいと思います。

コンセプト

では早速、クロージャの具体的な実現方法について検討していきます。

「処理のまとまりと、その処理で使われる情報のセット」ということですので、ここから、以下の2つの機能を実現できればよいことが分かります。

  • 処理をまとめて後で利用する機能
  • 使われる情報を処理のまとまりと結びつける機能

前者に関しては、MTのテンプレート機構の中では以下の機能が利用できそうです。

  • テンプレートモジュール
  • SetVarTemplate
  • mtevel

課題は後者が実現できるか、あるいはどう実現するかというところになりますが、この点について、上の3つそれぞれについて考えていきたいと思います。

テンプレートモジュール

テンプレートモジュールはMTの機能の中で最も一般的なモジュール化の機能ですが、以下のような特徴があるため、クロージャの実現を実現するための機能としてはちょっと弱そうです。

  • 「使われる情報を処理のまとまりと結びつける機能」の実現がきれいにできない
  • テンプレート処理の中で動的に生成することができない

SetVarTemplate

SetVarTemplate の特徴は以下の通りです。

  • 「使われる情報を処理のまとまりと結びつける機能」の実現がきれいにできない
  • テンプレート処理の中で動的に生成することができる

処理のまとまりを動的に生成することができ、これはとても魅力的な機能なのですが、テンプレートモジュールの場合と同様の理由でどうしてもきれいに実現でないところがあるので、やはりちょっと残念なところがあります。

mteval

MT では 4.2 から mtevalというモディファイアが利用できるようになりました。このモディファイアは指定されたタグが展開されるタイミングで、その中に含まれるMTタグが処理されます。

また mteval を使った場合、MTタグを含むテンプレートを文字列として保存(MTの内部形式ではない)させるようになるため、上の2つではできなかった、使われる情報を結びつける機能がわりと簡単に実現できるのです。

  • 「使われる情報を処理のまとまりと結びつける機能」の実現ができる
  • テンプレート処理の中で動的に生成することができる

一方では、MTで一般的に使われている方法ではないため、処理の書き方がやや冗長になってしまうという部分があります。

検討結果

一般的に使われていない故、少々書きにくいという部分はありますが、クロージャを最もクロージャらしく実現できそうだというところで、ここでは mteval を採用したいと思います。

mteval を処理のまとまりとして使うための準備

今回使うことになった mteval ですが、処理のまとまりとして利用するために用意された機能ではないため、利用するためには少々工夫が必要になります。ここではその工夫について、いくつか説明をしていきます。

apply

mteval の利用方法はMTタグに mteval="1" として指定をするだけなのですが、このままだと処理のまとまりにパラメーターを渡したい場合に、mt:SetVar などで別途変数に値を代入してから利用することになり、どうもきれいではありません。そこで今回は、 apply というテンプレートを作成し、これを介して処理のまとまりを利用することにしました。

<mt:If name="debug">
  <mt:Var name="$name" encode_html="1" nl2br="1" replace=" ","&nbsp;"/>
</mt:If>
<mt:Var name="$name" mteval="1" />
以下のように利用します。
<mt:Include module="apply" name="function_name" param1=".." param2=".." />

処理の結果は文字列として出力されることになりますが、例えばここに setvar="return" のようなモディファイアをつけることで、return に結果を保存することができます。

<mt:Include module="apply" name="function_name" param1=".." param2=".." setvar="return" />

これはもう立派な関数ですね。

mteval と apply を使った階乗計算

mteval による処理のまとまりの定義と、apply テンプレートを使ったまとまりの利用の例として、階乗を計算して表示するテンプレートを紹介します。

<mt:SetVar name="recursion_method" value="low level" />

<mt:SetVarBlock name="fact">
<mt:Unless replace="fun:","mt:">
  <fun:Unless name="fact_tmp">
    <fun:SetVar name="fact_tmp" value="1" />
  </fun:Unless>

  <fun:If name="i" lt="0">
    Negative value is not supported.
  <fun:ElseIf name="i" eq="0">
    <fun:Var name="fact_tmp" />
    <fun:SetVar name="fact_tmp" value="" />
  <fun:Else>
    <fun:SetVar name="fact_tmp" value="$i" op="*" />
    <fun:SetVar name="i" value="1" op="-" />

<mt:If name="recursion_method" eq="apply">
    <mt:Include module="apply" name="fact" i="$i" />
</mt:If>

<mt:If name="recursion_method" eq="low level">
    <fun:Var name="fact" mteval="1" />
</mt:If>

  </fun:If>
</mt:Unless>
</mt:SetVarBlock>

<ol>
<mt:For var="i" from="1" to="10">
  <li><mt:Include module="apply" name="fact" i="$i" /></li>
</mt:For>
</ol>

結果は以下のようになります。

  1. 1
  2. 2
  3. 6
  4. 24
  5. 120
  6. 720
  7. 5040
  8. 40320
  9. 362880
  10. 3628800

本当は再帰的な処理も apply で行いたいのですが、 MTではテンプレートモジュールを再帰的に読み込むことができないため、 常に recursion_method を low level として、mteval で再帰することになります。

加算器

さて、いよいよここからがクロージャ生成の例になります。まず最も簡単な例として、渡された数にある決まった数を足すクロージャを生成し、それを利用して加算を行います。

<mt:SetVarBlock name="add-n">
  <mt:Unless replace="fun:","mt:">
    <fun:SetVarBlock name="$define">
      <fun:Unless replace="fun2:","mt:">
        <fun2:Var name="value" setvar="add-n_tmp" />
        <fun2:SetVar name="add-n_tmp" value="<fun:Var name="add" />" op="+" />
        <fun2:Var name="add-n_tmp" />
      </fun:Unless>
    </fun:SetVarBlock>
  </mt:Unless>
</mt:SetVarBlock>


<mt:Include module="apply" name="add-n" define="add2" add="2" />
<mt:Include module="apply" name="add-n" define="add3" add="3" />
<mt:Include module="apply" name="add-n" define="add4" add="4" />


<h1>add2</h1>
<ol>
<mt:For var="i" from="1" to="5">
  <li><mt:Include module="apply" name="add2" value="$i" /></li>
</mt:For>
</ol>


<h1>add3</h1>
<ol>
<mt:For var="i" from="1" to="5">
  <li><mt:Include module="apply" name="add3" value="$i" /></li>
</mt:For>
</ol>


<h1>add4</h1>
<ol>
<mt:For var="i" from="1" to="5">
  <li><mt:Include module="apply" name="add4" value="$i" /></li>
</mt:For>
</ol>

結果は以下のようになります。

add2

  1. 3
  2. 4
  3. 5
  4. 6
  5. 7

add3

  1. 4
  2. 5
  3. 6
  4. 7
  5. 8

add4

  1. 5
  2. 6
  3. 7
  4. 8
  5. 9

http://www.google.com/search?q=クロージャ+add-nで検索すると一般的な例がたくさん見つかるので、そちらと比較をしていただくとよいと思います。

カウンター

「処理のまとまりと、その処理で使われる情報のセット」という意味では、加算器が生成できたところをもってクロージャが生成できたとしてもいいと思うのですが、最初に具体例としてあげた「カウント中の値を覚えているカウンター」を作成するには、使われる情報を更新できる必要があります。これを形にしたものが、以下の例です。

正確なところとしては、この例では使われる情報を更新しているわけではなく、「更新された使われる情報をもつ、新しいクロージャで自分自身を更新する」という方法で、情報の更新をエミュレーションしています。この辺りは、プログラミングの世界でどのように分類されるのか筆者にはよく分かっていません。ご存知の方は教えていただけるとありがたいです。

<mt:SetVar name="debug" value="0" />
<mt:SetVar name="counter_update_method" value="low level" />

<mt:SetVarBlock name="new_counter">
  <mt:Unless replace="fun:","mt:">
    <fun:SetVarBlock name="$define">
      <fun:Unless replace="fun2:","mt:">
        <fun2:SetVar
          name="counter_value"
          value="<fun:Var name="counter_value" />" />
        <fun2:Unless name="counter_value">
          <fun2:SetVar name="counter_value" value="0" />
        </fun2:Unless>
        <fun2:SetVar name="counter_value" value="1" op="+" />
        <fun2:Var name="counter_value" />

<mt:If name="counter_update_method" eq="apply">
        <mt:Ignore>using 'apply' template module</mt:Ignore>
        <fun2:Include
          module="apply"
          name="new_counter"
          define="<fun:Var name="define" />"
          counter_value="$counter_value" />
</mt:If>

<mt:If name="counter_update_method" eq="low level">
        <mt:Ignore>using low level method</mt:Ignore>
        <fun2:SetVar name="define" value="<fun:Var name="define" />" />
        <fun2:Var name="new_counter" mteval="1" />
</mt:If>

        <fun2:SetVar name="counter_value" value="" />
      </fun:Unless>
    </fun:SetVarBlock>
  </mt:Unless>
</mt:SetVarBlock>


<mt:Include module="apply" name="new_counter" define="counter1" />

<h1>counter1</h1>
<ol>
<mt:For var="i" from="1" to="5">
  <li>counter1: <mt:Include module="apply" name="counter1" /></li>
</mt:For>
</ol>


<mt:Include module="apply" name="new_counter" define="counter2" />
<mt:Include module="apply" name="new_counter" define="counter3" />

<h1>counter2, counter3</h1>
<ol>
<mt:For var="i" from="1" to="5">
  <li>counter2: <mt:Include module="apply" name="counter2" /></li>
  <li>counter3: <mt:Include module="apply" name="counter3" /></li>
</mt:For>
</ol>

結果は以下のようになります。

counter1

  1. counter1: 1
  2. counter1: 2
  3. counter1: 3
  4. counter1: 4
  5. counter1: 5

counter2, counter3

  1. counter2: 1
  2. counter3: 1
  3. counter2: 2
  4. counter3: 2
  5. counter2: 3
  6. counter3: 3
  7. counter2: 4
  8. counter3: 4
  9. counter2: 5
  10. counter3: 5

こちらも add-n と同様、http://www.google.com/search?q=クロージャ+カウンタで一般的な例がたくさん見つかりますが、その中でもhttp://ja.wikipedia.org/wiki/クロージャ、ここで紹介されている JavaScript による実装が比較しやすいと思います。

まとめ

「MTテンプレートでクロージャを生成する」というテーマについては、実用性はまったくなく、実験に過ぎません。ここまでお付き合い頂いた方、ありがとうございます。

そして非常に淡いながらも、MTのテンプレートに興味を持った若い人がこのコラムをきっかけに新しいプログラミングのパラダイムと出会うことができ、そこから何かのつながることがあればという気持ちを持ちつつ、今日はここまでとしたいと思います。

次回からはもう少し使える Tips を紹介していく予定です。

トラックバック(0)

このブログ記事を参照しているブログ一覧: テンプレートでクロージャ

このブログ記事に対するトラックバックURL: https://tec.toi-planning.net/mtos/mt-tb.cgi/436

コメントする

Created by ToI企画
Powered by Movable Type 5.2.2