LoginSignup
0
3

More than 5 years have passed since last update.

Lua で eval 関数を作る際に、なぜ assert を使うのか(もしくは、load 関数の戻り値について)

Posted at

概要

Lua で eval 関数を作る場合、load() を使用することはもちろんですが、assert() を併用している例を多く見かけます。ここでは、eval の基本的な説明と、なぜ assert を併用するのかについて解説を行います。

Lua での eval 基本編

文字列をプログラムとして評価する関数は、一般的に eval と呼ばれています(ちなみに evaluate という英単語は「評価する」という意味で、evaluation は「評価」という意味です)。Lua でも load 関数にて、この機能は提供されています。古い Lua では loadstring という関数名で提供されていたようです。

実際には、load に文字列を渡すだけでは実行してくれないので、次のようなコードを書く必要があります。

ex_load.txt
> a
nil
> load("a=1")()
> a
1

load の戻り値は関数であり、文字列として記述したプログラムを実行するためには、戻り値に () を付けて実行させる必要があるわけです。

ex_type_of_return_value_of_load.txt
> type(load("a=1"))
function

ネットで見かける eval の実装例

Lua での eval の実装例として、以下のようなコードをよく見かけます。

eval.lua
function eval(inStr)
    return assert(load(inStr))()
end

これは、単に load 関数を用いるだけでは、与えられた文字列にエラーが含まれている場合などにうまく対処できないからです。

例えば、"a=" という不完全な文字列を評価する場合、いつもの "unexpected symbol near..." というエラーメッセージが出力されて欲しいものです。しかし、load("a=")() を実行すると、"stdin:1: attempt to call a nil value" というメッセージが出力されます。

これは、"a=" というプログラム片だから、nil value 云々というメッセージが出力されたわけではありません。どのようなエラーを含む文字列でも、全てこの nil value 云々というメッセージが出力されます。これは eval 関数の挙動として、あまり良いものではありません。可能であれば、適切なエラーメッセージが出力されて欲しいものです。

load 関数の戻り値

load 関数の仕様は、公式サイトの https://www.lua.org/manual/5.3/manual.html#pdf-load で確認できます。それによると、引数として与えられた文字列にエラーがない場合は関数としてコンパイルされたチャンクが返され、そうでない場合(つまりエラーがある場合)は nil とエラーメッセージが返される、とあります。実際に、引数にエラーが含まれない場合、およびエラーが含まれる場合の挙動は以下のようにして簡単に確かめられます。

ex-load-without-with-error.txt
> select("#",load("a=1")) -- num of return values in case of no error
1
> select("#",load("a=")) -- num of return values in case of errors
2

> r,s=load("a=") -- in case of the argument with errors
> r
nil
> s
[string "a="]:1: unexpected symbol near <eof>

引数として与えられた文字列(=評価される文字列)にエラーが含まれる場合、load 関数は常に nil を第 1 戻り値として返します。そのため、load("a=")() のように、単に load 関数の呼び出し直後に () を付けて実行する実装では、エラーが含まれる文字列の場合、常に "stdin:1: attempt to call a nil value" というエラーメッセージが表示されることとなります。

assert の活用

上で見たように、load 関数は評価する文字列にエラーがある場合に nil を第 1 戻り値として返します。であれば、素直に eval を実装すると、例えば、以下のようなコードが考えられます。

rustic-eval.lua
function eval(inCodeStr)
    local r,s=load(inCodeStr)
    if r~=nil then
        return r()
    else
        error(s)
    end
end

もちろん、これでも動くわけですが、assert を使うと、より簡潔に記述できます。Lua のマニュアル https://www.lua.org/manual/5.3/manual.html#pdf-assert によると、assert は 2 つの引数をとり、第 1 引数が nil でなければ引数全てを返し、そうでない場合は第 2 引数をエラーオブジェクトとして error 関数に引き渡します(つまり、第 2 引数がエラーメッセージとして表示されます)。

これは、load 関数にとっては非常に都合の良い仕様となっています。load 関数で評価すべき文字列にエラーが含まれる場合は、nil とエラーメッセージが assert 関数に引き渡され、結果として、error 関数にエラーメッセージが引き渡されて実行されます。もし、load 関数に渡される文字列にエラーが含まれない場合には、第 1 引数は nil ではなく関数となります。そのため、assert 関数の戻り値は load 関数の戻り値と等しくなり、assert 関数の結果を関数として実行することは、与えられた文字列をプログラムとして実行することとなります。つまり、上で示した素朴な実装と同等の処理は、先に上げた次のコード(再掲)のように書けます。

eval.lua(再掲)
function eval(inStr)
    return assert(load(inStr))()
end

まとめ

この文書では、eval 関数について簡単な説明を行い、Lua でも load 関数にて同等の処理ができることを示しました。ネットなどでは load と assert を組み合わせた例が多くみられるため、load 関数の仕様と assert 関数の仕様を説明し、assert 関数を活用することにより、簡潔に eval 関数を記述できることを示しました。

以下、個人的な感想ですが、Lua のライブラリはよく考えられているなぁ、と再認識しました。

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3