NScripterの深淵

NScripter/NSLua/Lua5.1/関連プログラム

▼メニュー

ゴブリンでもわかる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側で追加されたもの)をつまみ食いしてゆく。