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
     Operatorsare 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 Complex4n")

        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: joey@joeygibson.com
   Blog: http://joeygibson.com
     Original   post: http://bit.ly/wNvAl
   Twitter: @joeygibson
   Facebook: http://facebook.com/joeygibson

Operator Overloading In Scala

  • 1.
  • 2.
    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
  • 3.
    What Is OperatorOverloading?  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
  • 4.
    The Basics  Technically, Scala does not have operator overloading  Operatorsare 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_-
  • 5.
    What About Precedence?  Scala looks at the first character of the operator name  All other special chars  */%  +-  :  =!  <>  &  ^  |  All letters  All assignment operators
  • 6.
    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
  • 7.
    Complex.scala package com.joeygibson.oopres class Complex(valreal: 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" } }
  • 8.
    ComplexTest.scala package com.joeygibson.oopres import org.junit.runner.RunWith importorg.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") } }
  • 9.
    Interesting Naming Examples packagecom.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) } }
  • 10.
    Testing Interesting Names packagecom.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") } }
  • 11.
    You Can EvenGet 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 }
  • 12.
    Testing the Silliness packagecom.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") } }
  • 13.
    Arguments of DifferentTypes  Define multiple versions of method, taking different types  Use Implicit conversions
  • 14.
    Multiple Defs ofOperator 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" } }
  • 15.
    Testing Multiple Defs packagecom.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") // } }
  • 16.
    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
  • 17.
    Implicit Conversions package com.joeygibson.oopres objectComplex3 { 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" } }
  • 18.
    Testing Implicits package com.joeygibson.oopres @RunWith(classOf[JUnitRunner]) classComplex3Test 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") } }
  • 19.
    Using Both package com.joeygibson.oopres objectComplex4 { implicit def intToComplex4(anInt: Int): Complex4 = { printf("Implicitly converting to Complex4n") 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" } }
  • 20.
    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 } }
  • 21.
    The First OneWorks, 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) // } }
  • 22.
    ἐγώ ἐιμι  Email: joey@joeygibson.com  Blog: http://joeygibson.com  Original post: http://bit.ly/wNvAl  Twitter: @joeygibson  Facebook: http://facebook.com/joeygibson