Scala-return文を色々に呼び出してみた


目次

あけましておめでとうございます。

フリュー ソフトウェア開発部 国平です。

今年もよろしくお願いいたします。


前回の記事で、自身のプリクラと逆変換した画像を載せたところ、

「キレイなジャイアンみたい」とか、

「逆変換してもまだ本物よりイケメン」とか散々言われまくってます。

なので、今日は画像なしで、Scalaのreturn文の挙動について文章だけで勝負します。

発端

皆様、リーダブルコードという本はご存知でしょうか。

タイトルそのまま、読みやすいソースコードを書くためのテクニックが書かれた本です。 非常に読みやすく、しかもエッセンスを抽出してコンパクトにまとめられた良書です。

この本の中で、return文に関するTipsとして「関数からはやく返す」という項目があります。 これは「早い段階で、結果を返せるのであれば、さっさとreturnしましょう」ということで、 私はそれに従ってコードを書いていたのですが、隣の席の人にコードを見せると、

「Scalaっぽくない」という話になって、確かにその通りなのでコード自体はreturn文を使わないように変更しました。

その時に書いたreturn文ありのコードはこんなイメージです↓

def someFunction(canReturn:Boolean) =  {
  if (canReturn) {
    return true
  }
  
  // ここから処理が続く
}

この時ふと、「Scalaって引数に関数を渡せるけど、じゃあreturn文を書いた時に、どの関数の結果になるんだ」という話になっていろいろ試すことになりました。

というわけで、ScalaのREPLで色々なreturn文を試してみました。

ケース1

とりあえず、裸のreturnを書いてみました。

scala> return 2
<console>:8: error: return outside method definition
              return 2
              ^

return文をメソッド定義の外に書くなって怒られました(´Д⊂グスン

こういうの↓も同様です

scala> class Foo {
     | val foo = return 3
     | }
<console>:8: error: return outside method definition
       val foo = return 3
                 ^

これならどうだ

scala> def foo() = {
     | if (true) return 3
     | 4
     | }
<console>:8: error: method foo has return statement; needs result type
       if (true) return 3
                 ^

戻り値型を定義しろって、また怒られる(´・ω・`)

というわけで、これだっ

scala> def foo():Int = {
     |   if (true) return 3 
     |   4
     | }
foo: ()Int

scala> foo
res1: Int = 3

はい、当然のように3を返す関数が出来ました。

ここまでのまとめ

  • 関数定義の中からしか呼び出せない
  • 関数の戻り値型の定義が必要

ケース2 高階関数で利用

さて、ここからが本題

関数型言語らしく高階関数の中でreturn文を呼び出してみたらどうなるか実験します。

まずは、Int型を返す関数を引数にとる関数barを定義します。 関数barは、第1引数boolがtrueであれば、第2引数のblock関数を実行した結果に+1して返します。

scala> def bar(bool:Boolean, block: =>Int):Int = if (bool) block + 1 else return 4
bar: (bool: Boolean, block: => Int)Int

scala> bar(true, 1)
res2: Int = 2

試しに呼び出すと、引数を1を返す関数として認識して、+1して処理してくれてます。 なので、結果は2になりますね。

この関数引数にreturn文を書いてみます。

scala> bar(true, return 1)
<console>:3: error: return outside method definition
              bar(true, return 1)
                        ^

あれ?また関数定義の外でreturnしちゃだめって怒られました。 ぬぬぬ、barは関数を引数に取ることを明記してるけど、どうやらダメらしい。

というわけで、これでどうよ?

scala> def bazz(bool:Boolean):Int = {
     |   bar(true, return 1)
     | }
bazz: ()Int

関数bazzの定義の中で、関数barを呼び出して引数にreturn 1を渡しました。 さて、どうなるのか…

scala> bazz(true)
res3: Int = 1

関数bazzの戻り値が1になっとるッッ

結局、高階関数の引数にreturnを書いても、そのreturn文がどのメソッド定義の中に書かれているかが重要なようです。 まぁ、エラーメッセージ通りってことですね。

では関数bazzの引数をfalseにしたらどうなるのか?

scala> bazz(false)
res3: Int = 4

お!4が帰ってきた!

ここまでのまとめ

  • 高階関数でもベタにreturn文を書くことはできない→明示的な関数定義が必要
  • return文が扱う制御は、return文が書かれた箇所で明示的に定義されている関数
  • ただし、return文の処理タイミングは高階関数で制御できる

総括

というわけで、Scalaのreturnは明示的に宣言された関数の制御文として動作することがわかりました。 しかし、高階関数と組み合わせて使いはじめると、一体どのタイミングでreturn文が実行されるのか、ワケガワカラナイヨなので、わざわざ使わないほうが良さそうです。

実際にreturn禁止のスタイルガイドもあるみたいです。というか、Scalaの作者がそう言ってるみたいです。 stackove – Scala coding styles and conventions?

関数内の見通しが悪くなるからreturnで処理を中断したいと思うような場合は、メソッド分割などを駆使して、そもそも関数が長くなりすぎたり、インデントが深くなり過ぎないようにしましょう。