Luauにおける三項演算子風処理の実装方法について
1. 導入
Luauには、条件によって異なる値を返す三項演算子
(例: 条件 ? 真の場合の値 : 偽の場合の値)が組み込まれていません(バージョン0.663時点)。
その代わりに、 and/or
イディオム や if-then-else
式 を用いて同様の機能を実現することができます。
本記事では、それぞれの方法の利点・注意点を整理しながら、Luauで三項演算子のような処理を実装する方法を解説します。
2. 論理演算子の基礎 (Luauのブール評価)
Luauの論理演算では、false
とnil
のみが偽とみなされ、それ以外の値はすべて真(truthy)とみなされます。
例えば、数値の0
や空文字列""
でさえ真と判断される点は注意すべきポイントです。
基本的な論理演算子は次の2つです。
-
and(論理積)
両方の条件が真の場合に真を返します。
Luaにおけるand
は特殊で、左側のオペランドが偽(false
またはnil
)ならその値を返し、真であれば右側のオペランドの値を返します。 -
or(論理和)
少なくとも一方の条件が真であれば真を返します。
Luaのor
も特殊で、左側のオペランドが偽でない(真である)場合は左側のオペランドの値を返し、左側のオペランドが偽(false
またはnil
)の場合に右側のオペランドの値を返します。
これらの演算子は 短絡評価(ショートサーキット) されます。
つまり、and
では最初の値が偽の場合にそれ以上評価せずに即座に結果を返し、or
では最初の値が真の場合に即座に結果を返します。
次のコード例で動作を確認してみましょう。
-- and 演算子の例
print(true and "Hello") --> "Hello" : true は真なので、第二オペランド "Hello" を返す
print(1 and "Hello") --> "Hello" : 数値 1 は真なので "Hello" を返す
print("World" and 0) --> 0 : "World" は真なので、第二オペランド 0 を返す
print(false and "Hello") --> false : false は偽なので、第一オペランド false を返す
print(nil and "Hello") --> nil : nil は偽なので、nil を返す
-- or 演算子の例
print(true or "Hello") --> true : true は真なので、第一オペランド true を返す
print(false or "Hello") --> "Hello" : false は偽なので、第二オペランド "Hello" を返す
print(nil or "Hello") --> "Hello" : nil は偽なので、"Hello" を返す
print("Hi" or "Hello") --> "Hi" : "Hi" は真なので、第一オペランド "Hi" を返す
上記のように、and
とor
は条件そのものではなくオペランドそのものを返すことに注意してください。
この性質を利用すると、条件式から値を選択する処理(まさに三項演算子的な処理)を表現できます。
and/or
イディオムによる条件値の選択
3. Lua由来の and/or
イディオム を使うと、簡潔に「条件が真ならA、偽ならB」を表現できます。一般形は次のとおりです。
result = 条件 and 真の場合の値 or 偽の場合の値
これは他の言語の条件 ? 真値 : 偽値
に相当し、Luaでは古くから使われてきたテクニックです 。
例えば、2つの値x
とy
のうち大きい方を選ぶには以下のように書けます。
--!strict
local x: number, y: number = 5, 10
local max: number = (x > y) and x or y -- x > yが真ならxを、偽ならyを返す
print(max) --> 10
x > y
が真の場合and
は第二オペランドのx
を返し、偽の場合は第一オペランドのfalse
を返します。
偽だった場合はそのままor
演算に移り、or
が第二オペランドのy
を返すため、結果的に条件に応じた値が得られます。
and/or
イディオムの落とし穴
and/or
イディオムは手軽ですが、一つ注意すべき落とし穴があります。
それは、「真の場合の値がfalse
またはnil
になり得る場合」です。
Luaの論理演算の仕様上、and
の結果がfalse
またはnil
だと常にor
の右側のオペランドが採用されてしまいます。
そのため、本来は真の場合として返したい値がfalse
/nil
だと、誤って偽の場合の値が返されてしまうのです。
例えば、以下のコードを見てください。
--!strict
local flag: boolean = true
local trueValue: string? = nil
local falseValue: string? = "NG"
local result: string? = flag and trueValue or falseValue
print(result) --> NG (本当はnilを期待するが"NG"が返る)
flag
が真なので、本来はtrueValue
(ここではnil
)を返したいところですが、trueValue
自体が偽の値であるため、flag and trueValue
の結果はnil
となり、その後のor
によってfalseValue
が返ってしまいました。
このように trueValue
にfalse
やnil
が入り得る場合、and/or
イディオムは意図した結果を返しません。
3. 即時関数(IIFE)による回避方法
and/or
イディオムの問題を回避する一つの方法は、即時実行関数(IIFE: Immediately Invoked Function Expression)を使うことです。
匿名関数を定義してすぐに実行することで、通常のif-then-else
による値選択を式として扱うことができます。具体的には次のように書きます。
--!strict
local condition: boolean = true
local trueValue: number? = nil
local falseValue: number? = 0
local result: number? = (function()
if condition then
return trueValue
else
return falseValue
end
end)()
print(result) -- condition が true なら nil、condition が false なら 0 が出力される
また、下記のように一行で書くこともできます。
local result: number? = (function() if condition then return trueValue else return falseValue end end)()
上記のコードでは、匿名関数内でif
文を使って条件に応じた値をreturn
し、それを即座に呼び出すことでresult
に代入しています。
こうすることで、trueValue
がfalse
やnil
であってもその値自体が正しく返されます(関数のreturn
は論理演算のように値を無視しないため)。
この方法は確実に正しい結果を得られますが、コードが少し長くなる欠点があります。
特に複数回使うには毎回関数を書く必要があるため、次に紹介するような関数化やLuauの新機能も検討すると良いでしょう。
4. 再利用可能な関数による実装
もう一つのアプローチは、三項演算子風の処理を行う関数を自分で定義してしまうことです。
例えば、以下のようなternary
関数を用意します。
--!strict
function ternary<T, U>(condition: boolean, trueValue: T, falseValue: U): T | U
if condition then
return trueValue
else
return falseValue
end
end
print(ternary(true, nil, 0)) -- condition が true の場合は nil が出力される
print(ternary(false, nil, 0)) -- condition が false の場合は 0 が出力される
このternary
関数は、condition
が真なら第二引数を、偽なら第三引数を返すものです。
一度定義しておけばどこでも呼び出せるため、同じパターンを繰り返し書くよりコードの見通しが良くなります。
また、and/or
イディオムと違って内部で明示的にif
文を使っているため、trueValue
がどんな値(false
/nil
を含む)でも意図した結果を返します。
また、ジェネリックな型引数 T
と U
を用い、返り値の型を T | U
(ユニオン型)とすることで、例えば、 trueValue
に nil
、falseValue
のように、異なる型を渡すことができます。
if-then-else
を用いる方法
5. 式としての Luauでは、言語拡張として**if-then-else
式**が導入されており、条件に応じた値を直接生成できるため、最もシンプルで推奨される方法となっています。
通常のif
文と似た構文ですが、if-then-else
式は値を返す式として利用できるため、その結果を変数に代入したり、他の式の一部として組み込んだりすることが可能です。
基本的な書き方は次のとおりです。
local result = if 条件 then 真の場合の値 else 偽の場合の値
例えば、x
とy
の大小比較を行い、大きい方の値を選ぶ例は以下のように記述できます。
--!strict
local x: number, y: number = 5, 10
-- この一行で、条件「 x > y 」が真ならxがmaxValueに代入され、偽ならyがmaxValueに代入されます。
local maxValue: number = if x > y then x else y
print(maxValue) --> 10
なお、if-then-else
式では必ずelse
部分を記述する必要があります(省略はできません)。
6. 参考資料
- Luau公式ドキュメント: https://luau-lang.org/
- Lua公式マニュアル 5.1: https://www.lua.org/manual/5.1/manual.html#3.4.7
Discussion
LuauはLuaと異なり、式としてのif-then-elseもあるので、そちらを使うのもおすすめです。
if-then-else
のご紹介ありがとうございます!記事のリライトに併せて
if-then-else
の紹介も追記させていただきました。