読者です 読者をやめる 読者になる 読者になる

Lua5.1 getfenv(), setfenv() 解説

Lua5.2 で getfenv(), setfenv() は廃止されたので読む必要はありません

(一応記事の中身は残しておきますが)

Lua5.1 にはgetfenv(), setfenv() という関数が標準で提供されています。 Lua 5.1 リファレンスマニュアル(日本語訳)には、これらの解説は以下のように書かれています。

setfenv (f, table)

指定された関数で使われる環境を変更する。 f はLuaの関数か、関数のスタックレベルを指定する数値である。 レベル1は setfenv を呼び出した関数を示す。 setfenv は与えられた関数を返す。

特殊なケースとして、fに0が与えられると、setfenv は走行中のスレッドの環境を変える。 この場合、setfenv は何も返さない。

getfenv (f)

関数の現在の環境を返す。 f はLuaの関数か、関数のスタックレベルを示す数値である。 レベル1はgetfenvを呼んだ関数である。 もし与えられた関数がLuaの関数でなかったり、fが0の場合は、getfenv はグローバル環境を返す。 f のデフォルト値は1である。" しかし、これだけでは良く分からない人も多いのではないでしょうか。

二年ほど前、僕はLuaを使い始めました。 そこでLua 5.1 リファレンスマニュアル(日本語訳)をパラパラと眺めている時にこれらの関数を知り、説明を読んでも意味が分からなかった経験があります。 今日、軽く検索したところリファレンスマニュアル以外で日本語でこれらの関数を解説したページが見つからなかったので、 同じ経験をした人のために解説を書いてみようと思います。

尚、以下の説明はLuaの基本的な知識、_Gがグローバル環境を表す事、クロージャの知識を前提としています……が、サンプルコードを多く示すので曖昧な知識でも大丈夫かもしれません


これらの関数については頑張って説明するよりもサンプルコードを示した方が圧倒的に理解が早いです。 コードと実行結果を交えつつ説明を書いてみます。

サンプルコード1

function f()
  v = "this is v."
end

tbl = {}

setfenv(f, tbl)
f()

print("v is ", v)
print("tbl.v is ", tbl.v)

実行結果1

v is    nil
tbl.v is        this is v.

setfenv(f, tbl)f()の環境をtblにセットした後にf()を呼び出しているため、 f()内のv = "this is v."はグロバール変数vのセットではなくtblへの変更となります。

サンプルコード2

print(_G == getfenv(0))
print(_G == getfenv(1))

実行結果2

true
true

getfenv(0)は特殊で、必ずグローバル環境を返します。そのため、デフォルトでグローバル環境を表すグローバル変数_Gと返り値が一致します。 getfenv(1)はこのコード自体を読んだ環境を返します。グローバル環境から呼んでいるので、グローバル環境が返り値となり、結果_Gと一致します。

サンプルコード3

function f()
    print("Hello!")
end

setfenv(f, {})
f()

実行結果3

lua: attempt to call global 'print' (a nil value)

エラーが起きてしまいました。 f()にセットされた環境は空のテーブルなため、環境からprintが見つからずnilになり、関数呼び出しが失敗します。

サンプルコード4

local print = print
function f()
    print("Hello!")
end

setfenv(f, {})
f()

実行結果4

hello!

先程とほぼ変わらないコードですが、こちらはエラーが起きません。 これは、Luaは関数定義の際にその時点で定義されているローカル変数をクロージャにキャプチャするためです。 そのため、環境からprintを検索する前にキャプチャされたprintを参照するため、エラーが起きません。 この通り、getfenv()setfenv()で扱う環境とはクロージャとは違うことが分かると思います。

サンプルコード5

local function f()
    print(getfenv(2).value)
end
local function g()
    f()
end
value = "G"
setfenv(g, {value = "I"})
f()
g()

実行結果5

G
I

getfenv(2)は、これを呼び出した関数を呼び出した関数の環境を取得します。 たとえば、下から二行目のf()ではgetfenv(2)を呼び出した関数(f())を呼び出した関数(トップレベル)の環境(_G)が返ってきます。 最終行のg()ではgetfenv(2)を呼び出した関数(f())を呼び出した関数(g())の環境({value = "I"})が返ってきます。 これがgetfenv(3)だとこれを呼び出した関数を呼び出した関数を呼び出した関数の環境を…となるわけです。


いかがでしょうか、多少なりともgetfenv()setfenv()について理解が進んだと思います。 こういった機能は他の言語には見られないと思うのですが、あまり取り上げられる事が無いのが少々残念です。 実に面白い機能だと思います。

どういう機能かは分かったが、一体どんな使い道があるんだ?使う事はあるのか?と思うかもしれません。 それについては、また今度書こうと思います。

尚、現在Lua5.2がリリースされていますが、僕はLua5.2には触れていないのでそちらではどうなっているのか知りません。 悪しからず。