この初版は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. 無断転載禁止。 | ![]() |