Lua/関数

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Lua
An illustration of a stack and of the operations that can be performed on it.
An illustration of a stack and of the operations that can be performed on it.

スタック(stack) とは、アイテムを追加 (プッシュ) または削除 (ポップ) できるアイテムのリストで、最後に追加されたアイテムが最初に削除されることを意味する、後入れ先出しの原則で動作するものです。このため、このようなリストはスタックと呼ばれます。スタックでは、ある項目を削除するには、まずその項目の上にある項目を削除する必要があります。したがって、すべての操作はスタックの最上位で行われます。ある項目が、その項目の後に追加された場合は、その項目の上にあり、その項目の前に追加された場合は、その項目の下にある。

関数(function)(サブルーチン、プロシージャ、ルーチン、サブプログラムとも呼ばれる)は、特定のタスクを実行する命令のシーケンスで、その命令のシーケンスが実行されるべきときはいつでもプログラム内の他の場所から呼び出すことができます。また、関数は入力として値を受け取り、その入力を操作したり、入力に基づいたタスクを実行した後に出力を返すことができます。関数は、他の関数の内部を含め、プログラムのどこからでも定義でき、また、関数にアクセスできるプログラムのどの部分からも呼び出すことができます。関数は、数値や文字列と同様に、値であり、したがって変数に格納でき、変数に共通するすべての特性を備えています。このような特性により、関数は非常に便利なものとなっています。

関数は他の関数から呼び出すことができるため、Luaインタープリター(Luaコードを読み込み実行するプログラム)は、現在実行中の関数がどの関数から呼び出されているかを把握し、関数が終了した時(実行すべきコードがなくなった時)に正しい関数の実行に戻れるようにする必要があります。コールスタックの各項目は、スタック内の直上の関数を呼び出した関数で、スタック内の最後の項目までは、現在実行中の関数である。関数が終了すると、インタプリタはスタックのポップ操作でリストの最後の関数を削除し、前の関数に戻る。

関数には、組込み関数とユーザー定義関数の2種類があります。組込み関数(Built-in functions)はLuaで提供される関数で、すでに知っているprint関数などの関数が含まれています。print関数のように直接アクセスできるものもありますが、乱数を返すmath.random関数のようにライブラリーを介してアクセスする必要があるものもあります。ユーザー定義関数(User-defined functions)は、ユーザーが定義した関数です。ユーザー定義関数は、関数コンストラクターを使用して定義されます。

local func = function(first_parameter, second_parameter, third_parameter)
	-- 関数本体(関数の本体は、関数に含まれるコードです)
end

上のコードは3つのパラメータを持つ関数を作成し、変数 func に格納しています。次のコードは上のコードと全く同じですが、関数の定義に構文糖を使用しています。

local function func(first_parameter, second_parameter, third_parameter)
	-- 関数本体
end

注意すべきは、第二の形式を使うと、関数自身の内部から関数を参照することができるが、第一の形式を使うとそれができない点です。これは、local function foo() endlocal foo; foo = function() endではなく、local foo; foo = function() endと変換されるからであす。これは、fooが第一の形式のではなく第二の形式では関数の環境の一部であることを意味し、第二の形式が関数自身を参照することを可能にする理由を説明しています

どちらの場合も、キーワード local を省略することで、関数をグローバル変数に格納することができます。パラメータは変数のようなもので、関数が値を受け取ることができるようにします。関数が呼び出されたとき、引数を与えることができます。関数が呼び出されると、引数はパラメータとして受け取られます。パラメータは関数の先頭で定義されるローカル変数のようなもので、関数呼び出しの際に与えられた引数の順番にしたがって代入されます。引数がない場合、パラメータは値nilを持ちます。次の例では、2つの数値を加算し、その結果を表示しています。したがって、このコードを実行すると、5が表示されます。

local function add(first_number, second_number)
	print(first_number + second_number)
end

add(2, 3)

関数呼出しはほとんどの場合、name(arguments)という形式の下にあります。しかし、引数が1つだけで、それがテーブルか文字列であり、変数にない場合(関数呼出しの中で直接構成され、リテラルとして表現されるという意味)、括弧を省くことができます。

print "Hello, world!"
print {4, 5}

前の例の2行目のコードでは、テーブルのメモリアドレスが表示されます。print関数が自動的に行う値かた文字列の変換の際、複合型(関数、テーブル、ユーザーデータ-、スレッド)はそのメモリアドレスに変換されます。しかし、論理値、数値、nil 値は、対応する文字列に変換されます。

パラメーター(parameter)とアーギュメント(argument)という用語は、実務ではしばしば同じ意味で使われる。本書では、パラメーターアーギュメントという用語は、それぞれ対応する引数の値が割り当てられる名前と、パラメーターに代入され関数に渡される値を意味します。

戻値[編集]

関数は入力を受け取り、それを操作して出力を返すことができます。関数がどのように入力(パラメーター)を受け取り、入力(関数本体)を操作するかは、すでにご存知でしょう。また、return文を使って、任意の型の値を1つまたは複数返すことによって、出力を与えることもできます。このように、関数呼び出しは文であり式でもあります。

関数は入力を受取り、それを操作し、出力を返すことができます。入力(パラメーター)を受け取り、それを操作する方法(関数本体)は既に知っています。また、return文を使用し、任意のタイプの1つまたは複数の値を返すことによって出力を提供することもできます。これが、関数呼び出しが文と式の両方である理由です。それらは実行すること(be executed)ができますが、評価すること(be evaluated)もできます。

local function add(first_number, second_number)
	return first_number + second_number
end

print(add(5, 6))

上記の関数のコードは、最初に関数addを定義します。次に、値として5と6を使用して呼び出します。関数はそれらを追加して結果を返し、それがprintされます。これが、上記のコードが11を出力する理由です。これらの値を評価する式をコンマで区切ることにより、関数が多くの値を返すことも可能です。

エラー[編集]

エラーには、構文エラー、静的意味エラー、セマンティックエラーの3種類があります。構文エラーは、コードが明らかに無効である場合に発生します。例えば次のようなコードは、Luaでは無効なものとして検出されます。

print(5 ++ 4 return)

上のコードは意味を持ちません。同様に、英語では、「cat dog tree」は意味がないので、構文的に有効ではありません。これは、文章を作るためのルールに従っていないのです。

静的意味エラーは、コードに意味があるにもかかわらず、意味を成さない場合に起こります。例えば、文字列を数字で足そうとすると、静的意味エラーになります。なぜなら、文字列を数字で足すことはできないからです。

print("hello" + 5)

上のコードはLuaの構文規則に従っていますが、それでも意味がありません。なぜなら、文字列を数字で足すことは不可能だからです(文字列が数字を表す場合は例外で、その場合は強制的に数字に変換されます)。これを英語で例えると、「I are big」という文章になります。これは英語で文章を作るときのルールに従っていますが、「I」は単数形で「are」は複数形なので、やはり意味が通じないのです。

最後に、セマンティックエラーとは、コードの一部が作成者の考えている意味と異なる場合に起こるエラーです。このようなエラーは、見つけるのが非常に難しいため、最悪のエラーと言えます。Luaは、構文エラーや静的意味エラーの場合は必ず教えてくれますが(これをエラーを投げると言います)、意味エラーの場合は、あなたがコードの意味をどう考えているのか分からないので、教えてくれません。このようなエラーは多くの人が思っている以上に頻繁に起こるので、それを見つけて修正することは多くのプログラマーが時間をかけて行っていることです。

エラーを発見し、修正するプロセスをデバッグと呼びます。ほとんどの場合、プログラマは実際にエラーを修正するよりも、エラーを見つけることに多くの時間を費やします。これは、あらゆる種類のエラーに当てはまります。何が問題なのかがわかれば、通常は簡単に修正できますが、時には、プログラマーが何時間もコードの一部を見ていても、その中の何が問題なのかがわからないということもあるのです。

保護呼出し[編集]

エラーを投げる(Throwing an error)とは、インタープリター(コードを読込んで実行するプログラム)が手動または自動で、コードに何か問題があることを示す動作のことです。Luaでは、与えられたコードが無効な場合に自動的に行われますが、error関数を使用して手動で行うことも可能です。

local variable = 500
if variable % 5 ~= 0 then
	error("変数の値を5で割っても小数点以下の数字にならないこと。")
end

error関数にも2番目の引数があり、エラーがスローされるスタックレベルを示しますが、これについては本書では取り上げません。 assert関数はerror関数と同じことを行いますが、最初の引数がnilまたはfalseと評価され、引数がない場合にのみエラーをスローします。これは、エラーがスローされるスタックレベルを指定するために使用できます。 assert関数は、スクリプトの開始時に役立ちます。たとえば、スクリプトが機能するために必要なライブラリが使用可能かどうかを確認する場合に役立ちます。

エラーがスローされるたびにプログラム内のコードの実行が停止するため、なぜ自発的にエラーをスローしたいのか理解するのは難しいかもしれませんが、多くの場合、関数が正しく使用されていないときやプログラムがで実行されていないときにエラーをスローします適切な環境は、コードをデバッグしなければならない人が、何が悪いのかを気付かずにコードを長時間見つめることなく、すぐにコードを見つけるのに役立ちます。

エラーがコードを停止するのを防ぎ、代わりにユーザーにエラーメッセージを表示して、開発者にバグを報告できるようにするなどの方法が役立つ場合があります。これは例外処理(exception handling)(またはエラー処理(error handling))と呼ばれ、エラーをキャッチして伝播を防ぎ、例外ハンドラーを実行して処理します。さまざまなプログラミング言語で行われる方法は大きく異なります。 Luaでは、保護された呼び出しを使用して行われます。[1]。保護モードで呼び出された関数はエラーが発生してもプログラムを停止しないため、これらは保護呼出し(Protected calls)と呼ばれます。プロテクトモードで関数を呼出すために使用できる関数は2つあります。

プロテクトモードで関数を呼出すために使用できる関数
関数 概要
pcall(function, ...) プロテクトモードで関数を呼び出し、ステータスコード(エラーがスローされたかどうかによって値が異なるブール値)と関数によって返される値、または関数がエラーによって停止された場合はエラーメッセージを返します。 引数は、プロテクトモードで呼び出す必要のある関数である最初の引数の後に pcall 関数に渡すことで、関数に指定できます。
xpcall(function, handler, ...) pcallと同じことを行いますが、関数エラーが発生すると、pcallが返すのと同じ値を返す代わりに、それらをパラメーターとしてハンドラー関数を呼び出します。 次に、ハンドラ関数を使用して、たとえば、エラーメッセージを表示できます。 pcall 関数の場合、 xpcall 関数に引数を渡すことで、引数を関数に渡すことができます。

スタックオーバーフロー[編集]

コールスタックとは、呼び出された関数が順番に格納されているスタックのことで、前述した。Luaを含むほとんどの言語におけるそのコールスタックには、最大サイズが設定されている。この最大サイズは非常に大きいので、ほとんどの場合心配する必要はないが、自分自身を呼び出す関数(これを再帰性といい、そのような関数を再帰関数と呼ぶ)は、無限に何度も呼び出すことを防ぐものがなければ、この限界に達する可能性がある。これをスタックオーバーフローと呼びます。スタックがオーバーフローすると、コードの実行が停止し、エラーがスローされます。

可変引数関数[編集]

可変引数関数(Variadic functions)は、vararg関数とも呼ばれ、可変数の引数を受け入れる関数です。 可変個引数関数は、パラメーターリストの最後にある3つのドット( "...")で示されます。 パラメータリストのパラメータに収まらない引数は、破棄されるのではなく、vararg式を介して関数で使用できるようになります。これも3つのドットで示されます。 vararg式の値は、値のリスト(テーブルではない)であり、次の式を使用してより簡単に操作できるようにテーブルに配置できます: {...}。 Lua 5.0では、vararg式を介して使用できる代わりに、「arg」と呼ばれる特別なパラメーターで追加の引数を使用できました。 次の関数は、受け取ったすべての引数に最初の引数を追加し、次にそれらすべてを合計して結果を出力する関数の例です。

function add_one(increment, ...)
	local result = 0
	for _, number in next, {...} do
		result = result + number + increment
	end
end

上記のコードは可変引数関数のデモンストレーションにすぎないため、理解する必要はありません。

select関数は、テーブルを使用せずに引数リストを操作するのに役立ちます。引数の数が不定であるため、それ自体が可変引数関数です。最初の引数として指定された数値を使用して、引数の後のすべての引数を返します(指定された数値が負の場合、最後からインデックスを付けます。つまり、-1が最後の引数です)。また、最初の引数が文字列 "#"の場合、最初の引数を除いて、受け取った引数の数を返します。引数リスト内の特定の数値の前のすべての引数を破棄すると、より元々、引数として送信されるnil値と、引数として送信されるものがないことを区別するのに役立ちます。実際、selectは、最初の引数として "#" が指定されている場合、nil値とno valueを区別します。引数リスト(および戻りリストも)はタプルのインスタンスであり、テーブルに関する章で説明します。 select関数はすべてのタプルで機能します。

print((function(...) return select('#', ...) == 1 and "nil" or "no value" end)()) --> no value
print((function(...) return select('#', ...) == 1 and "nil" or "no value" end)(nil)) --> nil
print((function(...) return select('#', ...) == 1 and "nil" or "no value" end)(variable_that_is_not_defined)) --> nil

-- このコードからわかるように、この関数は引数としてnilという値が渡されたのか、それとも単に値が渡されなかっただけなのかを検出することができる。
-- 通常の場合、どちらもnilとみなされるため、このように区別しています。


脚註[編集]

  1. ^ 詳細については、次を参照してください。 Ierusalimschy, Roberto. Lua.org,『Programming in Lua』, first, (2003).