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