5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

多引数で多返値な関数が利用できるLua言語のお話

Last updated at Posted at 2019-12-30

1. はじめに

某所で、「関数定義で複数の引数が使えるのに返値は1個に限定される言語が多いのはなんで?(意訳)」という議論が起きていたようですね。たしかにC言語など関数の返値が1つな言語は多いようです。

でも例外はありますよね。私の知る限り、Go、Lisp、Ruby、Luaあたりは一度に複数の返値を返す関数が定義できます。

今回はその中でも、(私が大好きな)Lua言語に注目して多値の利用について雑談をしていきましょう。

「エクスキューズ」
Lua言語は、ソフトウェア内の組み込み用途で作られた軽量高速な言語であり、シンプルであることを信条としている言語ですのでご承知ください。変数の型?引数の個数? あーあー聞こえない。

2. 多重代入って素敵

2.1 多引数・多返値な関数

Lua言語では以下のように多引数・多返値な関数が定義できます。

function swap(a,b)
  return b,a
end

print(swap(1,10))

-----
結果
10 1

print()関数は複数引数を受けとる関数ですのでswap()関数が出した2つの返値をそのまま画面表示してくれます。

しかし、二つの返値をいったん別の変数に保持したいこともあるわけで、その場合は次のような「多重代入」が使えます。便利ですね。

local x, y = swap(1,10)
print("1st: ", x)
print("2nd: ", y)
-----
結果
1st: 10
2nd: 1

2.2 Luaはうまくやってくれます

なお以下のように、Lua言語では返値の個数と受け取り手の変数の個数が違っていても、なんとなくうまくやりくりしてくれる仕様になっています。

-- 変数1個 ← 返値2個
local v0 = swap(1,10)
print("v0: ", v0)

-- 変数3個 ← 返値2個
local v1, v2, v3 = swap(1,10)
print("v1: ", v1)
print("v2: ", v2)
print("v3: ", v3)
-----
結果
v0: 10
v1: 10
v2: 1
v3: nil

つまり、多重代入で受け取り変数1個で返値2個の場合、変数には一番目の返値のみを代入し、二番目以降は無視します。逆に、受け取り側の変数の方が多ければ、先頭の変数から返値を詰めていき、余った変数は触らずそのままnilとなります。

とはいえ、返値を無視する(握り潰す)ときは、それを明示的にアピールするために次のような表記("_"、アンダーバー)をするのが一般的です。

local v0, _ = swap(1,10)
print("v0: ", v0)

3. 関数の入れ子もできる

3.1 関数から関数へ直接代入

また、以下のように関数が返す複数の返値をそのまま他の関数の引数として叩き込むことも可能です。

function swap(a,b)
  return b, a
end

print(swap(swap(1,10)))
-----
1 10

swap()は2引数2返値な関数です。一番内側のswap()で処理された返値"10 1"をさらに外側のswap()で処理することで、もとの並び順に戻ったことになります。

複数のバラバラな変数をバラバラなまま処理できるのって便利ですよね。2引数2返値な異なる関数を順番に適用していくことで、Luaでも関数型のプログラミングが可能になります。

え? 「他言語なら構造体使う?」「LuaならTableを使え?」 あーあー、聞こえない。

3.2 Luaならうまくやって・・・くれます?

Luaは変数と返値の数があっていなくてもうまくやってくれることは前述しましたが、それでは受け取り側関数の引数と送り側関数の返値の数があっていないときはどういう挙動になるのでしょうか。
以下のような例を考えてみます。

function plusichi(x)
  return x+1
end

-- 引数1個の関数 ← 返値2個の関数
print(plusichi(swap(1,10)))
-----
結果
11

これ、型厳密な言語を普段から扱っている人にとってはとても気持ち悪い処理だと思います。

具体的な処理の流れは以下のとおりです。

  1. swap()で "10 1" の2つの返値。
  2. plusishi()は1引数なので2つの返値のうち先頭"10"のみを受け取って処理。
  3. plusichi()により"11"を返す。
  4. 画面に"11"を表示する。

ええ、Luaってこういう仕様なんです。仕様だから仕方ない。

なお、次のようなプログラムも何の問題もなく動きます。

function swap(a,b)
  return b,a
end

print(1+swap(1,10))
-----
結果
11

うん、さすがにこれはドン引きです。

でもLuaって次のようなプログラムでも問題なく動くのですから、そういうものだと思いましょう。

function multi(a,b,c)
  print(a)
  print(b)
  print(c)
end
multi(1)
-----
結果
1
nil
nil

4. おわりに

Lua言語の関数定義では、C言語とは異なり、複数の返値を持つことができます。

C言語における関数定義時に、「ただ2つの値を返したいだけなのになんで構造体使わないといけないの!めんどくさい!」という不満を持っていた人にとってはうれしい機能ですね。

ただし紹介した通り、Luaは変数・引数の個数が異なっていてもなんとか処理してしまいますので、複雑なプログラムを組む時は、はまらないよう気をつけましょう。特に、2つ程度ならともかく、それ以上大量の変数を一気に扱うならば、LuaのTable(ハッシュテーブル)の方が楽だと思います。

追記

Go言語も多返値をもつ関数が定義できますが、Goの関数は定義時に返値の型や個数を記述しなくてはなりません。そのため、Luaのように「うまく」やってくれないので気をつけましょう。
多重代入時には、変数の数(型)と返値の数が同じでなくてはコンパイルエラーです。

このあたり、Lua言語との比較をしながら、まとめてくれる識者はいらっしゃいませんか?

5
3
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?