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