Scala - Operators with Examples

• Scala operators are methods with symbolic names that support both infix and prefix notation, enabling expressive mathematical and logical operations while maintaining type safety

Key Insights

• Scala operators are methods with symbolic names that support both infix and prefix notation, enabling expressive mathematical and logical operations while maintaining type safety • Understanding operator precedence and associativity rules prevents subtle bugs, especially when mixing arithmetic, bitwise, and comparison operators in complex expressions • Custom operators can be defined on any class, but following Scala’s conventions for operator naming and behavior ensures code remains maintainable and predictable

Arithmetic Operators

Scala provides standard arithmetic operators that work as methods on numeric types. Every arithmetic operation is a method call with infix notation.

object ArithmeticOperators extends App {
  val a = 10
  val b = 3
  
  println(s"Addition: $a + $b = ${a + b}")           // 13
  println(s"Subtraction: $a - $b = ${a - b}")        // 7
  println(s"Multiplication: $a * $b = ${a * b}")     // 30
  println(s"Division: $a / $b = ${a / b}")           // 3
  println(s"Modulus: $a % $b = ${a % b}")            // 1
  
  // Method call equivalents
  println(s"Addition (method): ${a.+(b)}")           // 13
  println(s"Multiplication (method): ${a.*(b)}")     // 30
  
  // Floating point division
  val x = 10.0
  val y = 3.0
  println(s"Float division: $x / $y = ${x / y}")     // 3.3333333333333335
}

Relational and Logical Operators

Relational operators compare values and return Boolean results. Logical operators combine Boolean expressions.

object RelationalLogicalOperators extends App {
  val x = 5
  val y = 10
  
  // Relational operators
  println(s"$x == $y: ${x == y}")    // false
  println(s"$x != $y: ${x != y}")    // true
  println(s"$x > $y: ${x > y}")      // false
  println(s"$x < $y: ${x < y}")      // true
  println(s"$x >= $y: ${x >= y}")    // false
  println(s"$x <= $y: ${x <= y}")    // true
  
  // Logical operators
  val a = true
  val b = false
  
  println(s"$a && $b: ${a && b}")    // false (AND)
  println(s"$a || $b: ${a || b}")    // true (OR)
  println(s"!$a: ${!a}")             // false (NOT)
  
  // Short-circuit evaluation
  def expensive(): Boolean = {
    println("Expensive computation")
    true
  }
  
  println(false && expensive())      // false, expensive() not called
  println(true || expensive())       // true, expensive() not called
}

Bitwise Operators

Bitwise operators perform bit-level operations on integer types. These are essential for low-level programming and performance-critical code.

object BitwiseOperators extends App {
  val a = 60  // 0011 1100 in binary
  val b = 13  // 0000 1101 in binary
  
  println(s"a & b = ${a & b}")       // 12 (0000 1100) - AND
  println(s"a | b = ${a | b}")       // 61 (0011 1101) - OR
  println(s"a ^ b = ${a ^ b}")       // 49 (0011 0001) - XOR
  println(s"~a = ${~a}")             // -61 (1100 0011) - NOT
  println(s"a << 2 = ${a << 2}")     // 240 (1111 0000) - Left shift
  println(s"a >> 2 = ${a >> 2}")     // 15 (0000 1111) - Right shift
  println(s"a >>> 2 = ${a >>> 2}")   // 15 (unsigned right shift)
  
  // Practical example: checking flags
  val READ = 1 << 0    // 0001
  val WRITE = 1 << 1   // 0010
  val EXECUTE = 1 << 2 // 0100
  
  var permissions = READ | WRITE
  println(s"Has read: ${(permissions & READ) != 0}")      // true
  println(s"Has execute: ${(permissions & EXECUTE) != 0}") // false
  
  permissions |= EXECUTE  // Add execute permission
  println(s"Has execute now: ${(permissions & EXECUTE) != 0}") // true
}

Assignment Operators

Assignment operators modify variables in place. Scala supports compound assignment operators that combine arithmetic operations with assignment.

object AssignmentOperators extends App {
  var x = 10
  
  x += 5   // x = x + 5
  println(s"After +=: $x")  // 15
  
  x -= 3   // x = x - 3
  println(s"After -=: $x")  // 12
  
  x *= 2   // x = x * 2
  println(s"After *=: $x")  // 24
  
  x /= 4   // x = x / 4
  println(s"After /=: $x")  // 6
  
  x %= 4   // x = x % 4
  println(s"After %=: $x")  // 2
  
  // Bitwise compound assignments
  var flags = 5
  flags &= 3
  println(s"After &=: $flags")  // 1
  
  flags |= 4
  println(s"After |=: $flags")  // 5
  
  flags ^= 3
  println(s"After ^=: $flags")  // 6
}

Operator Precedence and Associativity

Understanding operator precedence prevents bugs in complex expressions. Scala follows specific rules based on the first character of the operator.

object OperatorPrecedence extends App {
  // Precedence example
  val result1 = 2 + 3 * 4
  println(s"2 + 3 * 4 = $result1")  // 14, not 20
  
  val result2 = (2 + 3) * 4
  println(s"(2 + 3) * 4 = $result2")  // 20
  
  // Precedence order (highest to lowest):
  // 1. Postfix operators
  // 2. * / %
  // 3. + -
  // 4. :
  // 5. < >
  // 6. = !
  // 7. &
  // 8. ^
  // 9. |
  // 10. All letters
  // 11. All assignment operators
  
  // Associativity: operators ending with ':' are right-associative
  val list = 1 :: 2 :: 3 :: Nil
  println(s"List: $list")  // List(1, 2, 3)
  
  // This is equivalent to:
  val list2 = 1 :: (2 :: (3 :: Nil))
  println(s"List2: $list2")  // List(1, 2, 3)
  
  // Left-associative example
  val calc = 10 - 5 - 2
  println(s"10 - 5 - 2 = $calc")  // 3, evaluated as (10 - 5) - 2
}

Custom Operators

Scala allows defining custom operators as methods. Any method with symbolic names acts as an operator.

case class Vector2D(x: Double, y: Double) {
  def +(other: Vector2D): Vector2D = 
    Vector2D(x + other.x, y + other.y)
  
  def -(other: Vector2D): Vector2D = 
    Vector2D(x - other.x, y - other.y)
  
  def *(scalar: Double): Vector2D = 
    Vector2D(x * scalar, y * scalar)
  
  def dot(other: Vector2D): Double = 
    x * other.x + y * other.y
  
  def magnitude: Double = 
    Math.sqrt(x * x + y * y)
  
  override def toString: String = s"Vector2D($x, $y)"
}

object CustomOperators extends App {
  val v1 = Vector2D(3, 4)
  val v2 = Vector2D(1, 2)
  
  println(s"v1 + v2 = ${v1 + v2}")      // Vector2D(4.0, 6.0)
  println(s"v1 - v2 = ${v1 - v2}")      // Vector2D(2.0, 2.0)
  println(s"v1 * 2 = ${v1 * 2}")        // Vector2D(6.0, 8.0)
  println(s"v1 dot v2 = ${v1 dot v2}")  // 11.0
  println(s"|v1| = ${v1.magnitude}")    // 5.0
  
  // Custom symbolic operators
  case class Matrix(values: Array[Array[Int]]) {
    def ⊕(other: Matrix): Matrix = {
      // Element-wise addition
      val result = values.zip(other.values).map { case (row1, row2) =>
        row1.zip(row2).map { case (a, b) => a + b }
      }
      Matrix(result)
    }
  }
  
  val m1 = Matrix(Array(Array(1, 2), Array(3, 4)))
  val m2 = Matrix(Array(Array(5, 6), Array(7, 8)))
  val sum = m1  m2
  
  println("Matrix sum:")
  sum.values.foreach(row => println(row.mkString(", ")))
}

Unary Operators

Unary operators work with a single operand. Scala supports prefix unary operators: +, -, !, and ~.

object UnaryOperators extends App {
  val x = 5
  val y = -10
  
  println(s"Unary +: ${+x}")  // 5
  println(s"Unary -: ${-x}")  // -5
  println(s"Unary - on negative: ${-y}")  // 10
  
  val flag = true
  println(s"Unary !: ${!flag}")  // false
  
  val bits = 5
  println(s"Unary ~: ${~bits}")  // -6
  
  // Custom unary operators
  case class Counter(value: Int) {
    def unary_- : Counter = Counter(-value)
    def unary_! : Counter = Counter(value + 1)
  }
  
  val counter = Counter(5)
  println(s"Original: ${counter.value}")        // 5
  println(s"Negated: ${(-counter).value}")      // -5
  println(s"Incremented: ${(!counter).value}")  // 6
  
  // Method name must be unary_<operator>
  // Only +, -, !, and ~ are allowed as unary operators
}

Operator Overloading Best Practices

When defining custom operators, follow conventions to maintain code clarity and prevent confusion.

object OperatorBestPractices extends App {
  // Good: Operators match mathematical intuition
  case class Rational(numerator: Int, denominator: Int) {
    require(denominator != 0, "Denominator cannot be zero")
    
    private def gcd(a: Int, b: Int): Int = 
      if (b == 0) a else gcd(b, a % b)
    
    private val g = gcd(numerator.abs, denominator.abs)
    val n = numerator / g
    val d = denominator / g
    
    def +(that: Rational): Rational = 
      Rational(n * that.d + that.n * d, d * that.d)
    
    def -(that: Rational): Rational = 
      Rational(n * that.d - that.n * d, d * that.d)
    
    def *(that: Rational): Rational = 
      Rational(n * that.n, d * that.d)
    
    def /(that: Rational): Rational = 
      Rational(n * that.d, d * that.n)
    
    override def toString: String = s"$n/$d"
  }
  
  val half = Rational(1, 2)
  val third = Rational(1, 3)
  
  println(s"$half + $third = ${half + third}")  // 5/6
  println(s"$half * $third = ${half * third}")  // 1/6
  println(s"$half / $third = ${half / third}")  // 3/2
  
  // Avoid: Confusing or non-intuitive operators
  // Bad example (don't do this):
  // def @@(x: Int): String = x.toString  // Unclear meaning
  // def ><(x: Int): Int = x * 2          // Not intuitive
  
  // Prefer named methods for complex operations
  case class Dataset(data: List[Int]) {
    def union(other: Dataset): Dataset = 
      Dataset((data ++ other.data).distinct)
    
    def intersect(other: Dataset): Dataset = 
      Dataset(data.intersect(other.data))
    
    // Operators for common set operations are acceptable
    def ++(other: Dataset): Dataset = union(other)
    def ∩(other: Dataset): Dataset = intersect(other)
  }
  
  val set1 = Dataset(List(1, 2, 3))
  val set2 = Dataset(List(2, 3, 4))
  
  println(s"Union: ${set1 ++ set2}")           // Dataset(List(1, 2, 3, 4))
  println(s"Intersection: ${set1  set2}")     // Dataset(List(2, 3))
}

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.