Blog
RubyistがScalaを勉強した話 (演算子オーバーロード)
RubyistがScalaを勉強してみたシリーズ:
RubyistがScalaを勉強してみた | Scala Tech Blog
RubyistがScalaを勉強した話(Duck Typing) | Scala Tech Blog
RubyistがScalaを勉強した話 (演算子オーバーロード)
前回の記事で、Duck Typingについてブログを書きました。
今回はC++プログラマー、Rubyプログラマーもよく使っている演算子オーバーロードについて書きます。
Scalaの演算子オーバーロードについて
1. 基本の動作
Rubyでは、演算子オーバーロードがよく使われています。
演算子オーバーロードは既存の演算子、例えば:+, -, *, /などを全部書き直せることにより、より分かりやすいコード、またDSLを簡単に実装することができます。
例えばベクトルのクラスを実現したいとして:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Vec3 attr_accessor :x,:y,:z def initialize(value1, value2, value3) @x = value1 @y = value2 @z = value3 end def *(a) if a.is_a?(Numeric) #multiply by scalar return Vec3.new(@x*a, @y*a, @z*a) elsif a.is_a?(Vec3) #dot product return @x*a.x + @y*a.y + @z*a.z end end end v = Vec3.new(1, 1, 1) v2 = v*5 |
例を見て頂くとすぐ分かると思いますが、演算子オーバーロードにより、表現したい機能をDSLとして実装することができます。
なぜRubyで簡単に演算子オーバーロードが実現できるかと言うと、演算子が全てメソッドだからです。
Scalaも同じく、演算子が全てメソッドなので、簡単にオーバーロードすることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Vector3(val x: Int, val y: Int, val z: Int) { def *(m: Int): Vector3 = { new Vector3(x*m, y*m, z*m) } def *(m: Vector3): Vector3 = { new Vector3(m.x*x, m.y*y, m.z*z) } override def toString(): String = { s"${x}:${y}:${z}" } } object Blog1 { def main(args: Array[String]): Unit = { val x = new Vector3(1,2,3) val y = new Vector3(2,3,4) println(x*3) println(x*y) } } |
簡単ですね!
2.unaryオペレーター
a+bのように一つのオペレーターに対して、変数が二つある場合と違って、一つの変数しか存在しない場合もあります。
例えば
1 2 |
!a --b |
そのオペーレータはunaryオペレーターと呼びます。scalaではunaryオペレーターをオーバーロードすることもできます:
1 2 3 4 5 6 7 8 9 10 |
class Complex(val real : Double, val imag : Double) { def unary_~ = Math.sqrt(real * real + imag * imag) } object Complex { def main(args : Array[String]) : Unit = { var b = new Complex(2.0,3.0) println(~b) // 3.60555 } } |
オペレーターオーバーロードにより、DSLを実現することができ、それによりソースコードの表現力を高めることもできます。
面白い演算子オーバーロード
演算子オーバーロードは既存の +, -, /, * をオーバーロードするだけでなく、特別な表記として定義することもできます。Scala 界隈だと、例えば Akka Streams や Scalaz で積極的に実装されていて、高い表現力で実装することができます。その一部を簡単に紹介します。
Akka streams
Akka streams では ~> の演算子を「フロー」として使われます:
1 2 3 4 5 |
val g = FlowGraph { implicit builder => import akka.stream.scaladsl.FlowGraphImplicits._ source ~> bcast ~> step1 ~> merge ~> sink bcast ~> step2 ~> merge } |
(コードは 前のAkka streamsを紹介記事 から参照します)
~> でフローを表すというのは直感的でわかりやすく、とても良い定義だと思います。
Scalaz
ScalazはScalaにおける関数型プログラミングのみを追求するライブラリです。Scalazではunicodeのシンボルを幅広く使い、Haskellのようにピュアな関数型言語として使えます。それらのシンボルはほとんど関数型言語のためのもの(Applicative, Bind, kleisli)なので、この記事のスコープではちょっと外れていると思いますが、もっとも分かりやすいシンボルとして:
1 2 3 4 5 6 7 8 9 |
集合の演算子 ∋ contains List(1,2,3) ∋ 3 ∈ contains 50 ∈: Stream.range(0, 100) Booleanロジック演算子 ∧ AND true ∧ false ∨ OR true ∨ true ⊽ NOR false ⊽ true ⊼ NAND false ⊼ false |
などが存在します、とても分かりやすいですね〜
まとめ
全3回の連載で、Rubyistから見るScalaの紹介シリーズを書いてきました。LL系の言語の代表である Ruby から、コンパイル言語であり関数型言語である Scala に考え方をシフトさせるのは難しかったですが、勉強していくとどんどんScalaの素晴らしさを感じられて最強の言語かなと感じています。
Rubyistシリーズは終わりますが、今後もScalaエンジニアとしてどんどん記事を書いていきますので、お楽しみに!
Copyright about logo
Copyright (C) 2008 合同会社Rubyアソシエーション
Copyright (c) 2002-2015 EPFL
Author