ゴブリンでもわかるNSLua/Lua5.1 第6回:nil型と真偽値型
前回、新たな2種類の型「真偽値(ブーリアン)型」と「nil型」が出てきた。これまでに登場した「数値・文字列・関数・テーブル」に続く(最後の)主要な型である。
それぞれの型について知っておくべきだろう。
nil型
nil(ニル)は形式上型の一種だが、型というよりも「何も代入されていない変数の初期値」という特殊な値に近い。他の言語ではNULL(ヌル)と呼ばれる場合もある。
NScripter本体の変数は初期値が0または""(空文字列)だが、変数の型が決まっていない(動的型付け言語である)Luaにおいては常にnilとなる。いくつかの例を見ながらその性質を掴もう。
--【1】ローカル変数を定義時に何も代入しなかった場合、 -- 中身は常にnilとなっている。 local test -->test==nil local test2=123 local test3="abc" local test4,test5={} -->test4==(空のテーブル)、test5==nil --【2】関数に渡す引数の数が不足していた場合、 -- 不足分の値は全てnilとなる。 function test(a,b,c,d) alert(c,d) end test("a","b") -->c,dはnil --【3】未定義のグローバル変数を参照すると、nilが返る。 alert(aaaaaaaa)-->nil local hoge=aaaaaaaaaa -->hoge=nil --【4】テーブル型はnilを「キーが存在しない状態」として扱う。 -- 裏を返せばテーブルはキーや値としてnilを持てない。 local tab={["a"]=123,["b"]=456,["c"]=789} --値が代入されていないキーを参照するとnilが返る local test=tab["hogehoge"]-->test=nil --nilを代入するとキーが未定義に戻る tab["a"]=nil -->キー"a"はテーブルから消える for k,v in pairs(tab)do alert(k,v)-->"b"=456,"c"=789 end tab[nil]=123 -->エラー!
やや特殊な扱いを受けている型/値であることが見て取れるだろう。【4】の性質は特に重要で、一部言語が抱える「『未定義であるという値』が連想配列内に定義されている」といったような頭おかしいシチュエーションをあらかじめ回避している。
(「引数を受け取る変数」はLuaの内部でローカル変数として定義されるため、【1】と【2】はほとんど同じ内容を説明している。同様に「グローバル変数」はLuaの内部で1つの大きなテーブル(_G)内部に格納されているため、【3】と【4】もほとんど同じ内容を説明している。別に気にしなくてもいいよ。)
真偽値(ブーリアン)型
真偽値(boolean)と呼ばれる型は、true(真)またはfalse(偽)のうちどちらか一方の値を持つ。
--真 local bool=true --偽 local bool=false
見覚えがないだろうか?
NScripter本体では「"0または1"のみの値を用いてフラグの判定を行う数値変数」、RPGツクールなら「スイッチ(と呼ばれる機能)」が同等の用途で用いられており、「真偽値型」という意識がなくとも機能としては絶対に既知であるはずだ。
on/offを判定するだけであれば別に専用の型を用意する必要など無く、実のところ内部でtrue=1,false=0に変換してしまう言語すらある(Luaは違う)。
では、いかなる理由からわざわざ専用の型が与えられているのか? どこで何に使うのか? 次節以降を参照。
真偽値型とif
前項で確認した通り、真偽値型はしばしばフラグの判定に用いられる。最も頻繁に活躍するのは「真なら条件を満たす、偽なら満たさない」という 1 or 0 のフラグ分岐、すなわちifの条件式内部であろう。
local test=true --条件式が真ならifの内部が読まれる if(test==true)then --実行される end --条件式が偽ならifの内部は読まれない if(test==false)then --実行されない end
しかし、上記例ではtest==0,test==1で判定するのと何も変わらない。では、以下の例はどうだろうか?
--【1】比較演算子『== ~=(不等号) < > <= >=』は、 -- 判定結果として真偽値型を返す if(1==1)then -->if(true)then --実行される end if(1>=0)then -->if(true)then --実行される end --【2】and/orは、処理結果として真偽値型を返す if(1==1 and 2==5)then -->if(true and false)then -->if(false)then --実行されない end
真偽値型と「真偽」の定義
ifについて解説した第2回 では、「条件式が判定を満たす場合に節の内部を実行する」と説明した。この説明は間違っていないが言葉足らずであり、正確なifの挙動は「条件式が真である場合に節の内部を実行する」と説明される。
そしてLuaにおいては、falseとnilだけが「偽」、それ以外の値はすべて「真」という大原則(ルール)が定められている。
if(0)then -->if(true)then --実行される end local str="" if(str)then -->if(true)then --実行される end local tab={} if(tab)then -->if(true)then --実行される end local h,t=string.find("abc","c",1,1==1) -->local h,t=string.find("abc","c",1,true)
実のところ、条件式には比較演算子すら必須ではない。
(応用)真偽値型と三項演算
local a,b=false,"abc" if(a or b)then -->if(false or "abc")then -->if("abc")then -->if(true)then --実行される end if(a and b)then -->if(false and "abc")then -->if(false)then --実行されない end
"A or B"の正確な挙動は「Aが真ならAを、そうでなければBを返す」
"A and B"の正確な挙動は「Aが偽ならAを、真ならBを返す」
となっている。この挙動を利用したテクニックに、「三項演算」と呼ばれる分岐付き代入が存在する。
以下のコードを見てほしい。
local a=123 local b=(a==123 and "真" or "偽") -->local b=(true and "真" or "偽") -->local b=("真" or "偽") -->local b=("真") -->local b="真" --「おおよそ」等しい処理 local b if(a==123)then b="真" else b="偽" end
条件分岐によって代入する値を変えたい場合、三項演算を利用するとifを用いるよりスマートに記述を実現できる。
便利に見えるでしょ?
「三項演算の多用は可読性を悪化させる」という無視できない問題点が存在する。
local a,b=123,456 --入れ子にすると意味不明になりがち local c=(a==123 and (b==456 and "真真" or "真偽") or "偽") --改行を挟めば多少改善する local c=(a==123 and (b==456 and "真真" or "真偽") or "偽" )
Lua開発者公式の解説本にも、「三項演算を使うときはなるべく注釈を書き加えておけ」といった旨の警告が存在する。
三項演算は他にも多少のややこしい問題を伴うが、際限なく話が逸れてゆくためこの辺にしておく。
とりあえず「and/orや比較演算子はif以外でも利用できる」とだけ覚えておいてほしい。
まとめ
やや詰め込み気味になったが、無事だろうか?
①nilは『値なし』を表す変数の初期値である
②フラグたるtrue/falseは条件式の内部処理で頻出する
この二点さえ理解できていればさしあたって大丈夫だろう。
いずれにせよ、今回でLuaの主要な型がすべて揃った。次回以降では、数度に分けて標準ライブラリ(デフォルトで用意されているLua関数群)やNSLua関数群(NScripter側で追加されたもの)をつまみ食いしてゆく。