この初版はLua 5.0向けに書かれています。後のバージョンにもほぼ当てはまりますが、いくつかの違いがあります。
第4版はLua 5.3を対象としており、Amazonや他の書店で入手できます。
本書を購入することで、Luaプロジェクトの支援にもなります。
![]() |
Luaプログラミング | ![]() |
第一部 言語 第6章 関数詳解 |
関数が別の関数の内部に記述されている場合、その関数は外側の関数のローカル変数に完全にアクセスできます。この機能はレキシカルスコープと呼ばれます。当たり前に聞こえるかもしれませんが、そうではありません。レキシカルスコープとファーストクラス関数は、プログラミング言語において強力な概念ですが、この概念をサポートする言語はわずかです。
簡単な例から始めましょう。学生の名前のリストと、名前を成績に関連付けるテーブルがあるとします。成績順(成績の高い順)に名前のリストをソートしたいとします。このタスクは次のように実行できます。
names = {"Peter", "Paul", "Mary"} grades = {Mary = 10, Paul = 7, Peter = 8} table.sort(names, function (n1, n2) return grades[n1] > grades[n2] -- compare the grades end)さて、このタスクを実行する関数を作成したいとします。
function sortbygrade (names, grades) table.sort(names, function (n1, n2) return grades[n1] > grades[n2] -- compare the grades end) endこの例で興味深い点は、
sort
に渡される無名関数が、外側の関数sortbygrade
のローカル変数であるパラメータgrades
にアクセスしていることです。この無名関数内では、grades
はグローバル変数でもローカル変数でもありません。これを外部ローカル変数、またはアップバリューと呼びます。("アップバリュー"という用語は、grades
は値ではなく変数であるため、少し誤解を招く可能性があります。ただし、この用語はLuaの歴史的なルーツを持ち、"外部ローカル変数"よりも短いです。)なぜこれがそんなに興味深いのでしょうか?関数はファーストクラスの値だからです。次のコードを考えてみましょう。
function newCounter () local i = 0 return function () -- anonymous function i = i + 1 return i end end c1 = newCounter() print(c1()) --> 1 print(c1()) --> 2ここで、無名関数はアップバリューである
i
を使用してカウンターを保持しています。しかし、無名関数を呼び出す時点では、変数を作成した関数(newCounter
)が既にリターンしているため、i
は既にスコープ外になっています。それでも、Luaはクロージャの概念を使用して、この状況を正しく処理します。簡単に言うと、クロージャは関数とそのアップバリューに正しくアクセスするために必要なすべてのものです。newCounter
をもう一度呼び出すと、新しいローカル変数i
が作成されるため、新しいクロージャが取得され、その新しい変数に対して動作します。c2 = newCounter() print(c2()) --> 1 print(c1()) --> 3 print(c2()) --> 2そのため、
c1
とc2
は同じ関数に対する異なるクロージャであり、それぞれローカル変数i
の独立したインスタンスに対して動作します。技術的に言えば、Luaの値はクロージャであり、関数ではありません。関数は単にクロージャのプロトタイプです。それでも、混乱の可能性がない場合は、クロージャを指す用語として「関数」を使用し続けます。クロージャは、多くのコンテキストで貴重なツールを提供します。見てきたように、sort
などの高階関数への引数として役立ちます。クロージャは、newCounter
の例のように、他の関数を構築する関数にも役立ちます。このメカニズムにより、Luaプログラムは関数型の世界からの高度なプログラミング手法を取り入れることができます。クロージャは、コールバック関数にも役立ちます。典型的な例は、一般的なGUIツールキットでボタンを作成する場合に発生します。各ボタンには、ユーザーがボタンを押したときに呼び出されるコールバック関数があります。押されたときに、異なるボタンにわずかに異なる動作をさせたいとします。たとえば、電卓には、数字ごとに1つずつ、10個の同様のボタンが必要です。次の関数のような関数を使用して、それぞれを作成できます。
function digitButton (digit) return Button{ label = digit, action = function () add_to_display(digit) end } endこの例では、
Button
は新しいボタンを作成するツールキット関数であると仮定します。label
はボタンのラベルです。action
は、ボタンが押されたときに呼び出されるコールバック関数です。(アップバリューdigit
にアクセスするため、実際にはクロージャです。)コールバック関数は、digitButton
がタスクを実行し、ローカル変数digit
がスコープ外になった後、ずっと後に呼び出すことができますが、それでもその変数にアクセスできます。クロージャは、まったく異なるコンテキストでも役立ちます。関数は通常の変数に格納されるため、Luaでは関数を簡単に再定義できます。定義済みの関数でさえもです。この機能は、Luaが非常に柔軟な理由の1つです。ただし、関数を再定義する場合、新しい実装で元の関数が必要になることがよくあります。たとえば、関数sin
をラジアンではなく度で動作するように再定義したいとします。この新しい関数は引数を変換してから、元のsin
関数を呼び出して実際の作業を行う必要があります。コードは次のようになります。
oldSin = math.sin math.sin = function (x) return oldSin(x*math.pi/180) endよりクリーンな方法は次のとおりです。
do local oldSin = math.sin local k = math.pi/180 math.sin = function (x) return oldSin(x*k) end endこれで、古いバージョンはプライベート変数に保持されます。それにアクセスする唯一の方法は、新しいバージョンを使用することです。
この同じ機能を使用して、サンドボックスとも呼ばれる安全な環境を作成できます。安全な環境は、サーバーがインターネット経由で受信したコードなど、信頼できないコードを実行する場合に不可欠です。たとえば、プログラムがアクセスできるファイルを制限するには、クロージャを使用してopen
関数(io
ライブラリから)を再定義できます。
do local oldOpen = io.open io.open = function (filename, mode) if access_OK(filename, mode) then return oldOpen(filename, mode) else return nil, "access denied" end end endこの例の良い点は、再定義後、プログラムが新しい制限付きバージョンを介する場合を除き、制限のない
open
を呼び出す方法がないことです。安全でないバージョンは、クロージャ内のプライベート変数として保持され、外部からはアクセスできません。この機能により、LuaサンドボックスをLua自体で構築できます。通常の利点である柔軟性があります。万能型のソリューションではなく、Luaはメタメカニズムを提供するため、特定のセキュリティニーズに合わせて環境を調整できます。Copyright © 2003–2004 Roberto Ierusalimschy. All rights reserved. 著作権 © 2003–2004 Roberto Ierusalimschy. 無断転載禁止。 | ![]() |