1. 概要

このチュートリアルでは、Scalaのreturnキーワードについて説明します。

Javaとよく似たScalaプログラミング言語には、 return キーワードがありますが、プログラムの意味を簡単に変更したり、コードを推論しにくくしたりする可能性があるため、この使用はお勧めしません。

2. 序章

Javaでは、 return キーワードは必須であり、メソッドの実行を完了するために使用されます。

public boolean isEven(int number){
   return number % 2 == 0;
}

Scalaではreturnキーワードを使用することもできます。

private def isEven(number : Int) : Boolean = {
    return number % 2 == 0
}

しかし、Scalaでは、returnキーワードはオプションです。 Scalaコンパイラーは、関数内の最後の実行の値を戻り値として扱い、関数を書き直すことができるためです。

private def isEven(number : Int) : Boolean = {
   number % 2 == 0
}

では、Javaと同じように動作し、同じセマンティクスを持つ場合、Scalaで return キーワードを使用しないのはなぜですか?

3. 参照透過性

プログラムの意味や結果を変更せずに、コードの一部が使用されている場所であればどこでも、そのコードを計算した値に安全に置き換えることができれば、コードの一部は参照透過性として定義されます。 

return キーワードが参照透過性である場合は、returnキーワードを使用する式をその値に置き換える必要があります。

private def isPositiveNumber1(number : Int ) : Boolean = {
   val isTrue = return true
   val isFalse = return false

   if(number > 0) isTrue else isFalse
}

private def isPositiveNumber2(number : Int ) : Boolean = {
  if(number > 0) return true else return false
}
  

println(isPositiveNumber1(-1)) // true 
println(isPositiveNumber2(-1)) // false

この例では、値を return キーワードを使用した計算に置き換えると、いくつかの不整合が発生することがわかります。 プログラム全体が変更され、参照透過性が失われます。

ここで、returnキーワードを使用しない場合に何が起こるかを見てみましょう。

private def isPositiveNumber1(number : Int ) : Boolean = {
   val isTrue = true
   val isFalse = false

   if(number > 0) isTrue else isFalse
}

private def isPositiveNumber2(number : Int ) : Boolean = {
  if(number > 0) true else false
}

println(isPositiveNumber1(-1)) // false
println(isPositiveNumber2(-1)) // false

プログラムは変更されていないことがわかります。つまり、参照透過性が維持されています。

return キーワードは、 returnキーワードまたは式が評価されると、現在の計算を破棄し、returnが表示されるメソッドの呼び出し元に戻るため、参照透過性がありません。

4. インライン化

インライン化は、関数呼び出しをその本体または定義に置き換えることを指します。

return キーワードを使用すると、関数をインライン化するときにどのように不整合が発生するかを見てみましょう。 まず、 return キーワードを使用する関数を定義し、目的の結果が得られるかどうかを確認します。

def multiplier(a : Int, b: Int) : Int = return a * b
val randomNumbers = List(1,2,3,4)
def multiple(numbers : List[Int]) : Int = {
    numbers.foldLeft(1)(multiplier)
}
println(multiple(randomNumbers)) // 24

これは期待どおりに機能します。 ここで、 multiplier 関数をそのコンテンツに置き換えてインライン化すると、どうなりますか。

val randomNumbers = List(1,2,3,4)
def multiple(numbers : List[Int]) : Int = {
  numbers.foldLeft(1){(a,b) => return a * b}
}
println(multiple(randomNumbers)) // 1 ??

間違った結果が得られます。 これは、 return キーワードが現在の計算を放棄し、評価時に渡されたものを返すためです。foldLeftの場合は1です。

5. 早期返品

return キーワードを使用することになっていない場合、どうすれば早期返品を実装できるのか疑問に思われるかもしれません。 ここで、再帰などの他のメカニズムを使用することをお勧めします。

String クラスのメソッドでない場合、JavaでindexOfのバージョンを作成する方法を見てみましょう。

int indexOf(String string, char character){
    if(string.isEmpty()) {
        return -1;
    } else {
        int index = -1;
        int i;
        char[] stringArray = string.toCharArray();
        for(i= 0; i <= string.length() - 1; i++){
            if (stringArray[i] == character)
                return i;
        }
        return index;
    }
}

インデックスが見つかったら、早く戻ります。 return キーワードを使用してループから抜け出すことが想定されていない場合、Scalaでこれを行うにはどうすればよいですか?

ここで、再帰などの関数型プログラミングメソッドを使用できます。これにより、コードが理解しやすくなります。

def indexOf(string : String , char : Char) : Int = {
   def runner(stringList : List[Char], index : Int) : Int = {
     stringList match {
       case Nil => -1
       case h :: _ if h == char => index + 1
       case _ :: t => runner(t , index + 1)
     }
   }
   if(string.isEmpty) -1 else runner(string.toList, -1)
}
println(indexOf("hello",'h') // 0
println(indexOf("hello",'z') // -1

return キーワードの洞察はなく、私たちのプログラムは問題なく機能します。

6. 結論

この記事では、Scalaの return キーワードがJavaの対応するキーワードとどのように類似しているか、および参照透過性を損なうためにキーワードを使用することが推奨されない理由を説明しました。 Scalaでreturnキーワードを使用するユースケースがあると思うかもしれませんが、実際にはありません。

いつものように、ソースコードはGitHubにあります。