読者です 読者をやめる 読者になる 読者になる

Scala で条件演算子を作ってみる

コップ本を大体読み終わったので、とりあえず Scala の環境を入れてみた。
バージョンは 2.7.7 らしい。


で、Scala三項演算子。ここでは、他の言語でよくある条件演算子をまねて作ってみた。
条件演算子なら if 式使えよ、って話だけど、まぁ練習ということで。


まず、?: で作ろうとしたんだけど、: は Scala ではいろいろとアレなので却下。
次に考えたのが、?? の組み合わせ。でもこれも入れ子がアレなので却下*1
で、最終的に落ち着いたのが ?! の組み合わせ。! 以降は条件が成り立たなかった場合、つまり否定された場合に選択されるし、結構いいんじゃないかな。
実装は、単純に this を返さない流れるようなインターフェイスの発想で、

class CondOpA[T](val cond: Boolean, a: => T) {
  def !(b: => T) = if (cond) a else b
}

class CondOp(val cond: Boolean) {
  def ?[T](a: => T) = new CondOpA(cond, a)
}

implicit def boolWrapper(cond: Boolean) = new CondOp(cond)

こんな感じ。
暗黙の型変換を使って Boolean から CondOp に変換し、CondOp の ? メソッドを呼び出すと CondOpA が返され、CondOpA の ! メソッドを呼び出すと中で if 式が走る、と。
で、名前渡しパラメータを使用しているので、if 式と同じく実際に必要な項しか評価されないようになっている。


たとえば、

def hoge(): String = {
  println("hoge, world!")
  return "hoge"
}

def piyo(): String = {
  println("piyo, world!")
  return "piyo"
}

な関数を定義して ?! を使ってみると、

scala> true ? hoge() ! piyo()
hoge, world!
res0: String = hoge

scala> false ? hoge() ! piyo()
piyo, world!
res1: String = piyo

こんな感じ。
ジェネリクス周りはまだ完全に理解してないから両方 T で固定しちゃってるけど、より抽象的な方の型を返す、とかは普通にできそう。


で、ここまではいいんだけど・・・

scala> true ? hoge() ! false ? hoge() ! piyo()
<console>:10: error: type mismatch;
 found   : CondOpA[String]
 required: String
       true ? hoge() ! false ? hoge() ! piyo()

入れ子にできない!
つまり、(true ? hoge() ! false) をまず実行しようとして、a が String、b が Boolean なため、怒られる、と。
結局、括弧を補ってやる必要がある。

scala> true ? hoge() ! (false ? hoge() ! piyo())
hoge, world!
res2: String = hoge

さらに・・・

scala> true ? hoge()
res3: CondOpA[String] = CondOpA@15724a0

セミコロンが勝手に補われる!
ので、ここでもやっぱり括弧を使って、

scala> (true ? hoge()
     |       ! piyo())
hoge, world!
res4: String = hoge

と記述する必要がある。


さらには、最初

class CondOpA[T](val cond: Boolean, a: => T) {
  def !(b: => T) = if (cond) a else b
}

class CondOpB[T](val cond: Boolean, b: => T) {
  def ?(a: => T) = if (cond) a else b
}

class CondOp(val cond: Boolean) {
  def ?[T](a: => T) = new CondOpA(cond, a)
  def ![T](b: => T) = new CondOpB(cond, b)
}

implicit def boolWrapper(cond: Boolean) = new CondOp(cond)

なんてものを考えていたんだけど・・・

scala> true ! hoge() ? piyo()
<console>:15: error: value ? is not a member of String
       true ! hoge() ? piyo()
                     ^

演算子の優先順位が邪魔をする!
のでやっぱり括弧 (ry

scala> (true ! hoge()) ? piyo()
piyo, world!
res6: String = piyo

結論

if 式使え。

*1:実は後で他のものを選んでも関係なかったということが判明。後述