11. Operators - RobertMakyla/scalaWiki GitHub Wiki
KEY POINTS:
Identifiers contain either alphanumeric or operator characters.
Unary and binary operators are method calls.
Operator precedence depends on the first character, associativity on the last.
The 'apply' and 'update' methods are called when evaluating expr(args).
Extractors extract tuples or sequences of values from an input.
Identifiers
As in Java, only Unicode are allowed (José - not allowed)
Additionally to java ( a-z 0-9 _ ) I can use any of !#%&*+-/:<=>?@\^|~. but no parentheses [{( or delimiters ,.;
I can also use unicode mathematical symbols
val √ = scala.math.sqrt _ // √(2) will give me 1.41
In backquotes I can include anything:
val `val` = 42 // val: Int = 42
Backquotes can be used to access java methods which are also scala keywords:
Thread.`yield`()
Infix Operators
1 to 10
1.to(10)
1 -> 10
1.->(10)
Each method taking 1 argument, may be transformed to infix syntax (to make it more natural):
class Ulamek(val x:Int, val y:Int){
def +(that:Ulamek) = new Ulamek(this.x * that.y + that.x * this.y , this.y * that.y)
override def toString = x + "/" + y
}
val a = new Ulamek(1,2)
val b = new Ulamek(2,3)
val result = a + b // instead of a.add(b)
result.toString // --> 7/6
If we defined methods *() -() methods, and then try: a+b * c-d
it will calculate it correctly even if all methods are user defined: (a+b) * (c-d)
That is because in Scala a chain of methods is executed with method name priority :
1. (all other special characters)
2. * /
3. + -
4. : ! = < >
5. (all letters)
Defining my own Operators
class Fraction(val num: Int,val den: Int) {
def *(other: Fraction) = new Fraction(num * other.num, den * other.den)
}
val f1 = new Fraction(1,2)
val f2 = new Fraction(2,3)
f1 * f2
Unary Operators
1 toString
1.toString()
The four operators + - ! ~ are allowed as prefix operators, appearing before their arguments
-a
a.unary_- // the same as -a
Assignment Operators ( += -=)
a = a + 1
a += 1
Precedence
1 + 2 * 3 * is evaluated first cause it's more important than +
Precedence priority: 1.* / 2. + - 3. < > 4. ! =
Associativity
17 – 2 – 9 is like (17 – 2) – 9 because -'' is left-associative
In Scala all operators are left-associative except for :: and assignment operators (e.g. =)
The 'apply' and 'update' Methods
obj(arg1, arg2, ...) if 'obj' is not method or function, it's equivalent to obj.apply(arg1, arg2, ...)
"0123"(2)
"0123".apply(2)
obj(arg1, arg2, ...) = value it's equivalent to obj.update(arg1, arg2, ...)
val scores = new scala.collection.mutable.HashMap[String, Int]
scores("Bob") = 100 // Calls scores.update("Bob", 100)
The 'apply' method is commonly used in companion objects to construct object without calling new
class Fraction(n: Int, d: Int) {
}
object Fraction {
def apply(n: Int, d: Int) = new Fraction(n, d)
}
Thanks to 'apply' method, we can construct 'Fraction(3, 4)' instead of 'new Fraction(3, 4)'
val result = Fraction(3, 4) * Fraction(2, 5)
Extractors = Objects with 'unapply' method
An apply method takes construction parameters and turns them into an object.
An unapply method takes an object and extracts values from it.
Usually (but not necessarily) the values from which the object was constructed.
NOTE: update() method should be in class
NOTE: apply() method should be in companion object
NOTE: unapply() method should be in companion object
{
class Fraction(val den: Int, val num: Int) {}
object Fraction {
def apply(den: Int, num: Int) = new Fraction(den,num)
def unapply(input: Fraction) =
if (input.den == 0) None else Some((input.num, input.den))
}
val f = Fraction(1,2) // Calls Fraction.apply(1,2)
val Fraction(myNum, myDen) = f // Calls Fraction.unapply(f)
println(myNum)
println(myDen)
}
other example:
{
class MyLib() {
val map = scala.collection.mutable.HashMap(1 -> "Rob", 2 -> "Monia")
def update(key:Int, value:String) { map += (key->value) }
}
object MyLib {
def apply() = new MyLib()
def unapply(m: MyLib) = if (m.map != null) Some(m.map) else None
}
val m = MyLib() // apply
println("applied: "+m.map)
m(1) = "Robert" // update(key:Int, value:String)
println("updated: "+m.map)
val MyLib(tmpMap) = m // unapply
println("from unapply: "+tmpMap)
}
other example (no need of class - apply and unapply can be in object)
{
object Twice {
def apply(x: Int): Int = x * 2 // Int -> Int
def unapply(z: Int): Option[Int] = if (z%2 == 0) Some(z/2) else None // Int -> Int
}
val x = Twice(21) // x = 42
x match { case Twice(n) => println("n that we got from x:Int "+x+" is = "+n) } // prints n=21
}
Extractors with One or No Arguments
If the unapply method extracts a single value, it should just return an 'Option' of the target type.
object Number {
def unapply(input: String): Option[Int] =
try {
Some(Integer.parseInt(input.trim))
} catch {
case ex: NumberFormatException => None
}
}
val Number(n) = "123" // --> n = 123
Extractors as Testers of Value
object Name {
def unapply(input: String) = {
val pos = input.indexOf(" ")
if (pos == -1) None
else Some((input.substring(0, pos), input.substring(pos + 1)))
}
}
object IsFullName {
def unapply(input: String) = input.contains(" ")
}
"Peter van der Linden" match {
case Name(first, last @IsFullName()) => println("full name") // checks additionally if last name contains " "
case Name(first, last) => println("simple name")
}
"Peter Linden" match {
case Name(first, last @IsFullName()) => println("full name") // checks additionally if last name contains " "
case Name(first, last) => println("simple name")
}
The unapplySeq Method - for extracting a sequence of values
object Name {
def unapplySeq(input: String): Option[Seq[String]] =
if (input.trim == "") None else Some(input.trim.split("\\s+"))
}
Now you can match for any number of variables:
"Peter van der Linden" match {
case Name(first, last) => println("just first and last")
case Name(first, middle, last) => println("all 3: first, middle, last")
case Name(first, "van", "der", last) => println("FULL name")
}
Unattended Test NOTE: url can be split("/") into a sequence and the last 2 or 3 items should be recognised by cases which use unapplySeq()
'A multiply by B' could be split by split(" ") and recognised by unapply()
but it also can be split by RegExp - it's another solution ( a bit simpler ? )