Zenn
💡

Luauにおける三項演算子風処理の実装方法について

2025/02/22に公開
2

1. 導入

Luauには、条件によって異なる値を返す三項演算子(例: 条件 ? 真の場合の値 : 偽の場合の値)が組み込まれていません(バージョン0.663時点)。
その代わりに、 and/or イディオム や if-then-else 式 を用いて同様の機能を実現することができます。
本記事では、それぞれの方法の利点・注意点を整理しながら、Luauで三項演算子のような処理を実装する方法を解説します。

2. 論理演算子の基礎 (Luauのブール評価)

Luauの論理演算では、falsenilのみが偽とみなされ、それ以外の値はすべて真(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" を返す

上記のように、andorは条件そのものではなくオペランドそのものを返すことに注意してください。
この性質を利用すると、条件式から値を選択する処理(まさに三項演算子的な処理)を表現できます。

3. and/orイディオムによる条件値の選択

Lua由来の and/orイディオム を使うと、簡潔に「条件が真ならA、偽ならB」を表現できます。一般形は次のとおりです。

result = 条件 and 真の場合の値 or 偽の場合の値

これは他の言語の条件 ? 真値 : 偽値に相当し、Luaでは古くから使われてきたテクニックです 。
例えば、2つの値xyのうち大きい方を選ぶには以下のように書けます。

--!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が返ってしまいました。
このように trueValuefalsenilが入り得る場合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に代入しています。
こうすることで、trueValuefalsenilであってもその値自体が正しく返されます(関数の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を含む)でも意図した結果を返します。
また、ジェネリックな型引数 TU を用い、返り値の型を T | U(ユニオン型)とすることで、例えば、 trueValuenilfalseValue のように、異なる型を渡すことができます。

5. 式としての if-then-else を用いる方法

Luauでは、言語拡張として**if-then-else式**が導入されており、条件に応じた値を直接生成できるため、最もシンプルで推奨される方法となっています。
通常のif文と似た構文ですが、if-then-else式は値を返す式として利用できるため、その結果を変数に代入したり、他の式の一部として組み込んだりすることが可能です。

基本的な書き方は次のとおりです。

local result = if 条件 then 真の場合の値 else 偽の場合の値

例えば、xyの大小比較を行い、大きい方の値を選ぶ例は以下のように記述できます。

--!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. 参考資料

Discussion

ログインするとコメントできます