Operator Overloading In Scala

22
OPERATOR OVERLOADING IN SCALA Joey Gibson

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 of Operator Overloading In Scala

Page 1: Operator Overloading In Scala

OPERATOR OVERLOADING

IN SCALA

Joey Gibson

Page 2: Operator Overloading In Scala

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

Page 3: Operator Overloading In Scala

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

Page 4: Operator Overloading In Scala

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_-

Page 5: Operator Overloading In Scala

What About Precedence?

Scala looks at the first character of the operator

name

All other special chars

* / %

+ -

:

= !

< >

&

^

|

All letters

All assignment operators

Page 6: Operator Overloading In Scala

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

Page 7: Operator Overloading In Scala

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"

}

}

Page 8: Operator Overloading In Scala

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")

}

}

Page 9: Operator Overloading In Scala

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)

}

}

Page 10: Operator Overloading In Scala

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")

}

}

Page 11: Operator Overloading In Scala

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

}

Page 12: Operator Overloading In Scala

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")

}

}

Page 13: Operator Overloading In Scala

Arguments of Different Types

Define multiple versions of method, taking different

types

Use Implicit conversions

Page 14: Operator Overloading In Scala

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"

}

}

Page 15: Operator Overloading In Scala

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")

// }

}

Page 16: Operator Overloading In Scala

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

Page 17: Operator Overloading In Scala

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"

}

}

Page 18: Operator Overloading In Scala

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")

}

}

Page 19: Operator Overloading In Scala

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"

}

}

Page 20: Operator Overloading In Scala

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

}

}

Page 21: Operator Overloading In Scala

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)

// }

}

Page 22: Operator Overloading In Scala

ἐγώ ἐιμι

Email: [email protected]

Blog: http://joeygibson.com

Original post: http://bit.ly/wNvAl

Twitter: @joeygibson

Facebook: http://facebook.com/joeygibson