ちょっとしたJavaコードをScalaに書き直してみた

元コードはJava: The Good Partsの118ページ目

変数多いぅぉ・・・try catchやifが値を返せばretValとかいらないのに・・・
という「Javaコードを見るとscalaで書き直したい病」が発病したので(・ω・`)

元のJavaコード↓

  @Override
  public int compare(Player o1,Player o2) {
    float o1A, o2A;
    int retVal;

    if(o1.getId() == o2.getId())
      return 0;
    if(o1.hasRole(Player.Roles.Batter)){
      try{
        o1A = o1.asBatter().getAverage();
      }catch(NotEnoughAtBatsException e) {
        o1A = 0.0f;
      }
    } else
      o1A = 0.0f;

    if(o2.hasRole(Player.Roles.Batter)) {
      try {
        o2A = o2.asBatter().getAverage();
      } catch (NotEnoughAtBatsException e) {
        o2A = 0.0f;
      }
    } else
      o2A = 0.0f;

    if(o1A < o2A)
      retVal = -1;
    else if(
      retVal = 1;
    else if(o1.getId() < o2.getId())
      retVal = -1;
    else retVal = 1;

    return retVal;
  }

書き直したscalaコード*1

  override def compare(o1:Player,o2:Player):Int = {
    
    //処理重複してたから、ローカル関数にしてみた
    //try catch や、if自体が値を返すから、変数がいらなくなる
    def getAverage(p:Player):Float = {
      if(p.hasRole(Player.Roles.Batter)){
        try{
          p.asBatter.getAverage
        }catch{
          case _:NotEnoughAtBatsException => 0.0f
        }
      } else
        0.0f
    }

    if(o1.getId == o2.getId){
      0
    }else{
      // matchも式であり、値を返すから、ここでも一時変数がいらなくなる
      ( getAverage(o1) compare getAverage(o2) ) match {
        case 0 => o1.getId compare o2.getId
        case x => x 
      }
    }
  }

こんな感じで、scalaにちょっと慣れれば、「ifやtry catchそれ自体が式であり、値を返す」*2ので、変数がいらなくなります。
変数がないと

  • 初期化の忘れとか、そういうこと自体をそもそも考えなくてよくなる
  • どこで使われて、スコープどこまでなのか?を考えなくてよくなるので、コードを読むときに読みやすい

っていうところが利点ですかね、個人的な感覚としては。
matzがよく「名前重要」っていいますが、

たいして意味のないものに名前をつける

って結構めんどくさいというか、変数に名前をつける必要がないと、

大事なものにだけ名前をつけられる

ので、よりわかりやすくなる(?)というのがポイントです。


ローカル関数についてですが、これも別にprivate(でstatic)な関数を定義すればJavaでも同じ事ができます。が、その場合

スコープがclassの中全体になる

のにたいして、ローカル関数だと

スコープがそのメソッドの中だけ

になります。
class内のprivateな関数にしたとしても、やってることは同じになりますが、ローカル関数を使うことによって、

この関数はこのメソッドからしか使用しない
という意図を表現することができます。

はぁ何言ってんの?そんな細かいことどうでもいいよ(・д・´)

と言われればまぁそれまでなんですが、個人的には、scalaのこういう細かいところが気に入ってます。

そして、

getAverage(o1) compare getAverage(o2)

o1.getId compare o2.getId

の部分ですが、IntやFloatがそれぞれ、implicitにRichIntRichFloatというものに変換されて、そのcompareというメソッドが呼ばれてます。
このIntからRichIntに変換される関数が、Predefに定義されていて、デフォルトで使えるので、こんな感じでちょっと短く書けます。

*1:よく見ると、if(o1.getId == o2.getId) で、0を返す部分もそもそもいらなくなる・・・けど、動作的に余計なことするから、このままでいいのかなぁ・・・?0返してるのは、同じオブジェクトの場合を考慮してるんだろうけど、その場合って結局getIdが同じ値返すことになるから、o1.getId compare o2.getIdの部分で0が返る。ん〜すごく微妙なところ・・・

*2: 言葉の使い方おかしいのを指摘された(?)のでちょっと修正しました。ありがとうございます