operator overloading in scala
DESCRIPTION
I gave this presentation on January 14, 2010 to the Atlanta Scala user group. It covers Scala's implementation of operator overloading, as well as touching on implicit conversions.TRANSCRIPT
OPERATOR OVERLOADING
IN SCALA
Joey Gibson
First Things First
Working with Java since 1996
Playing with Scala for not-quite-a-year
Originally a blog post on joeygibson.com
See the original at http://bit.ly/wNvAl
Based on example from Programming Scala, by
Venkat Subramaniam
What Is Operator Overloading?
A language facility that allows developers to define
behavior for symbols like +, -, / for their own
classes, mimicking built-in operators
C++, Smalltalk, Ruby, lots of others have it
Java does not have it
Except + for String concatenation
BigDecimal, BigInteger, etc. would be nicer to use with
operators
The Basics
Technically, Scala does not have operator
overloading
Operators are just methods
Any method taking 0/1 argument can be called as an
operator
foo doSomething “x” is the same as foo.doSomething(“x”)
foo + 23 is the same as foo.+(23)
You can define both binary and unary operators
Only +, -, ~ and ! can be used as unary operators
As with binary operators, it’s just a method and
-foo is the same as foo.unary_-
What About Precedence?
Scala looks at the first character of the operator
name
All other special chars
* / %
+ -
:
= !
< >
&
^
|
All letters
All assignment operators
Operator Naming
Typically, you would use arithmetic operator
symbols, but you don’t have to
Since operators are just methods, you can use non-
standard characters
Δ, λ, γ, Ω, etc.
Neat/useful, but use sparingly, unless it makes your
code easier to read and/or maintain
If the last character in an operator name is a colon,
that method associates to the right
Complex.scala
package com.joeygibson.oopres
class Complex(val real: Int, val imaginary: Int) {
def +(operand: Complex): Complex = {
new Complex(real + operand.real, imaginary + operand.imaginary)
}
def *(operand: Complex): Complex = {
new Complex(real * operand.real - imaginary * operand.imaginary,
real * operand.imaginary + imaginary * operand.real)
}
override def toString() = {
real + (if (imaginary < 0) "" else "+") + imaginary + "i"
}
}
ComplexTest.scala
package com.joeygibson.oopres
import org.junit.runner.RunWith
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class ComplexTest extends FlatSpec with ShouldMatchers {
"A Complex" should "sum up to 4-9i" in {
val c1 = new Complex(1, 2)
val c2 = new Complex(2, -3)
val c3 = c1 + c2
val res = c1 + c2 * c3
res.toString should equal ("4-9i")
}
}
Interesting Naming Examples
package com.joeygibson.oopres
import org.apache.commons.lang.StringUtils
class Fragment(val text: String) {
override def toString = text
def Δ(other: Fragment): Fragment = {
val diff = StringUtils.difference(text, other.text)
new Fragment(diff)
}
def ::(other: Fragment): Fragment = {
new Fragment(text + " || " + other.text)
}
}
Testing Interesting Names
package com.joeygibson.oopres
@RunWith(classOf[JUnitRunner])
class FragmentTest extends FlatSpec with ShouldMatchers {
it should "return proper differences" in {
val f0 = new Fragment("Scala is groovy")
val f1 = new Fragment("Scala is cool")
val diff = f0 Δ f1
diff.toString should equal("cool")
}
it should "concatenate properly" in {
val f0 = new Fragment("First Fragment")
val f1 = new Fragment("Second Fragment")
val f2 = f0 :: f1
f2.toString should equal ("Second Fragment || First Fragment")
}
}
You Can Even Get Silly…
package com.joeygibson.oopres
class Absurdity(val text: String) {
def \/\/(other: Absurdity) = {
new Absurdity(text + ", " + other.text)
}
def /\/\(other: Absurdity) = {
new Absurdity(other.text + ", " + text)
}
override def toString = text
}
Testing the Silliness
package com.joeygibson.oopres
@RunWith(classOf[JUnitRunner])
class AbsurdityTest extends FlatSpec with ShouldMatchers {
it should "do something absurd" in {
val a = new Absurdity("foo")
val b = new Absurdity("bar")
val c = a \/\/ b
c.toString should equal ("foo, bar")
}
it should "do something equally absurd" in {
val a = new Absurdity("foo")
val b = new Absurdity("bar")
val c = a /\/\ b
c.toString should equal ("bar, foo")
}
}
Arguments of Different Types
Define multiple versions of method, taking different
types
Use Implicit conversions
Multiple Defs of Operator
package com.joeygibson.oopres
class Complex2(val real: Int, val imaginary: Int) {
def +(operand: Complex2): Complex2 = {
new Complex2(real + operand.real, imaginary + operand.imaginary)
}
def +(operand: Int): Complex2 = this + new Complex2(operand, 0)
def *(operand: Complex2): Complex2 = {
new Complex2(real * operand.real - imaginary * operand.imaginary,
real * operand.imaginary + imaginary * operand.real)
}
def *(operand: Int): Complex2 = this * new Complex2(operand, 1)
def unary_- = new Complex2(-real, imaginary)
override def toString() = {
real + (if (imaginary < 0) "" else "+") + imaginary + "i"
}
}
Testing Multiple Defs
package com.joeygibson.oopres
@RunWith(classOf[JUnitRunner])
class Complex2Test extends FlatSpec with ShouldMatchers {
it should "convert int to Complex2" in {
val c = new Complex2(1, 2)
val d = c + 23
d.toString should equal ("24+2i")
}
// it should "convert int to Complex2, reversed" in {
// val c = new Complex2(1, 2)
// val d: Complex2 = 23 + c
//
// d.toString should equal ("24+2i")
// }
}
Implicit Conversions
The implicit function must be in scope
It can live in the companion object of either class
under consideration
Be careful with implicits, especially when using
common types
Implicit Conversions
package com.joeygibson.oopres
object Complex3 {
implicit def intToComplex3(anInt: Int): Complex3 = new Complex3(anInt, 0)
}
class Complex3(val real: Int, val imaginary: Int) {
def +(operand: Complex3): Complex3 = {
new Complex3(real + operand.real, imaginary + operand.imaginary)
}
def *(operand: Complex3): Complex3 = {
new Complex3(real * operand.real - imaginary * operand.imaginary,
real * operand.imaginary + imaginary * operand.real)
}
def unary_- = new Complex3(-real, imaginary)
override def toString() = {
real + (if (imaginary < 0) "" else "+") + imaginary + "i"
}
}
Testing Implicits
package com.joeygibson.oopres
@RunWith(classOf[JUnitRunner])
class Complex3Test extends FlatSpec with ShouldMatchers {
it should "convert int to Complex3" in {
val c = new Complex3(1, 2)
val d = c + 23
d.toString should equal ("24+2i")
}
it should "convert int to Complex3, reversed" in {
import com.joeygibson.oopres.Complex3.intToComplex3
val c = new Complex3(1, 2)
val d: Complex3 = 23 + c
d.toString should equal ("24+2i")
}
}
Using Both
package com.joeygibson.oopres
object Complex4 {
implicit def intToComplex4(anInt: Int): Complex4 = {
printf("Implicitly converting to Complex4\n")
new Complex4(anInt, 0)
}
}
class Complex4(val real: Int, val imaginary: Int) {
def +(operand: Complex4): Complex4 = {
new Complex4(real + operand.real, imaginary + operand.imaginary)
}
def +(operand: Int): Complex4 = this + new Complex4(operand, 0)
def *(operand: Complex4): Complex4 = {
new Complex4(real * operand.real - imaginary * operand.imaginary,
real * operand.imaginary + imaginary * operand.real)
}
def *(operand: Int): Complex4 = this * new Complex4(operand, 0)
def unary_- = new Complex4(-real, imaginary)
override def toString() = {
real + (if (imaginary < 0) "" else "+") + imaginary + "i"
}
}
You Can’t Use ++ as Prefix
package com.joeygibson.oopres
class MyNumber(private var num: Int) {
def number: Int = num
override def toString = num.toString
def ++ = {
val n = num
num += 1
new MyNumber(n)
}
def unary_++ = {
num += 1
this
}
}
The First One Works, The Second…
package com.joeygibson.oopres
@RunWith(classOf[JUnitRunner])
class MyNumberTest extends FlatSpec with ShouldMatchers {
it should "post increment" in {
val x = new MyNumber(23)
val z = x++
z.number should equal (23)
x.number should equal (24)
}
// it should "pre increment" in {
// val x = new MyNumber(23)
// val z = ++x
//
// z.number should equal (24)
// x.number should equal (24)
// }
}
ἐγώ ἐιμι
Email: [email protected]
Blog: http://joeygibson.com
Original post: http://bit.ly/wNvAl
Twitter: @joeygibson
Facebook: http://facebook.com/joeygibson