12. High Order Functions (call by name, by value) - RobertMakyla/scalaWiki GitHub Wiki

KEY POINTS: Functions are 'first-class citizens' in Scala, just like numbers. You can create anonymous functions, usually to give them to other functions. A function argument specifies behavior that should be executed later. Many collection methods take function parameters, applying a function to the values of the collection. There are syntax shortcuts that allow you to express function parameters in a way that is short and easy to read. You can create functions that operate on blocks of code and look much like the built-in control statements.

Functions as Values

     val myf = (x:Double) => -x                   // setting function.

     myf(2)                         // example usage --> -2

Giving Function to another function as parameter

     Array(3.14, 1.42, 2.0).map(myf)     // map() accepts functions  -->  Array(-3.14, -1.42, -2.0)

Anonymous Functions

     (x: Int) => 3 * x                     // anonymous function

     val triple = (x: Int) => 3 * x        // anonymous function stored in variable
     val triple: Int => Int = x => 3 * x   // anonymous function stored in variable
     val triple: Int => Int = _ * 3        // anonymous function stored in variable

     val triple = x => 3 * x               // ERROR
     val triple = _ * 3                    // ERROR

     Array(2, 3).map( (x: Int) => 3 * x )        // --> Array(6, 9)
     Array(2, 3).map( x => 3 * x )               //  the same
     Array(2, 3).map( _ * x )                    //  the same
     Array(2, 3).map( triple )                   //  the same


     def triple(x: Double) = 3 * x           // THE SAME but here I provide name to function
                                            //  it's not anonymous

Higher-Order Functions

  1. Functions that take Function as Parameters

      def useFunctionWithOneQuarterArgument(f: (Double) => Double) = f(0.25)
    

    The 'useFunctionWithOneQuarterArgument' takes function as parameter and puts 0.25 argument to this function, and returns what this function returned

      useFunctionWithOneQuarterArgument( (x:Double) => 400 * x )     // --> 100.0
    

    The type of useFunctionWithOneQuarterArgument is ((Double) => Double) => Double

  2. Functions that return Function

      def multiBy(factor: Double) = (x:Double) => factor * x
    
      def multiBy(factor: Double): Double => Double = x => factor * x   // OK cause returned type is specified
    
      def multiBy(factor: Double): Double => Double = _ * factor        // OK cause returned type is specified
    
      multiBy(10)               // returns function
      multiBy(10)(2)            // 20
    
      val tmp = multiBy(10)     // saves to tmp function
      tmp(2)                    // 20
    

    The type of multiBy is (Double) => (Double => Double)

Parameters

     def useFunctionWithOneQuarterArgument(f: (Double) => Double) = f(0.25)

     useFunctionWithOneQuarterArgument( (x:Double) => 400 * x )     // 100

 The function knows that parameter must be of type (Double) => Double, so I don't need to declare type of 'x' :

     useFunctionWithOneQuarterArgument( (x) => 400 * x )            // 100

 If it's only one parameter on the left side, I can skip the () :

     useFunctionWithOneQuarterArgument( x => 400 * x )              // 100

 If the single parameter occurs only once on the right side, I can write:

     useFunctionWithOneQuarterArgument( 400 * _ )                   // 100


 e.g.    1 to 3 map { 2 * _ }                                       // (2, 4, 6)

         val fun = 3 * _                       // Error: Can't establish return type

         val fun = (x: Int) => 3 * x           // OK
         val fun = 3 * (_: Int)               // OK

Useful Higher-Order Functions

  (1 to 10).map("*" * _).foreach(println _)     // foreach(f: (Int) => U)
                                               // foreach applies function to each argument

  (1 to 9).filter(_ % 2 == 0)                 // filter( f: (Int => Boolean) )
  (1 to 9).filter( x => x % 2 == 0)          // the same



  (1 to 3).reduceLeft(_ + _ )             // reduceLeft takes 2 args function and applies it from left:
                                          // ((1 + 2) + 3) = 6

  (1 to 3).reduceRight(_ + _ )            // (1 + (2 + 3)) = 6

  (1 to 9).reduceLeft(_ * _)                // reduceLeft[B >: Int](f: (B,Int) => B)
                                           // reduceLeft takes 2 args function and applies it from left:
                                          // (...((1 * 2) * 3) * ... * 9)


  "ccc a bb eeeee dddd".split(" ").sortWith(_.length < _.length)

                                         // splits with spaces and sorts: Array(a, bb, ccc, dddd, eeeee)

Closures

 A function can be in any scope: package/class/function/method

     def getMultiFunctionByFactor(factor : Double)  =  (x : Double) => factor * x

     val tripleFunction  = getMultiFunctionByFactor(3)
     val halfFunction    = getMultiFunctionByFactor(0.5)

 Each of functions has its own factor, set through argument. Such function is called a CLOSURE.

Single Abstract Methods (SAM) Conversions

 In Scala we can pass functions as arguments. In Java we pass implementations of classes with SAM.

Currying

Turning a function that takes two arguments into a function that takes one argument and returns curried function which takes also one argument and returns value.

def mul(x: Int, y: Int) = x * y              // takes 2 args

def mulOneAtATime(x: Int) = (y: Int) => x*y  // takes 1 arg, returns curried function
                                             // curried function also takes 1 arg

mulOneAtATime(6)(6)                          // 36

def mulOneAtATime(x: Int)(y: Int) = x * y    // shortcut for curried function

def mulBySix(x:Int) = mulOneAtATime(6)(x)

Call by-value vs. by-name

    def predicate: Boolean = {
      println("calling predicate") // side effect
      100 > 1  // return value
    }

    def callByValue(x: Boolean) = { // `x` -evaluated before entering callByValue
      println("x1 = " + x)
      println("x2 = " + x) 
    }

    def callByName(x: => Boolean) = { 
      println("x1 = " + x)       // 'x' function is evaluated twice inside
      println("x2 = " + x)       // the callByName()
    }

x function is evaluated twice insidecallByName(), because println() takes parameters by-value. In other case, x would not be evaluated but just passed deeper.


    scala> callByValue(predicate)
    calling predicate
    x1 = true
    x2 = true

    scala> callByName(predicate)
    calling predicate
    x1 = true
    calling predicate
    x2 = true

In call by-value the value is always evaluated during the call of function

In call by-name the value is evaluated inside function, once it's called by-value at any level

In call by-name the value may never be evaluated. Eg:

    val assertingIsEnabled = false
    
    def assertByName(predicate: => Boolean){
        if(assertingIsEnabled){
            assert(predicate)
        }
    }

    assertByName( (1/0) > 1 )  // no error cause asserting is disabled
                               // predicate is never evaluated

    assertByValue( (1/0) > 1 )  // ERROR : Division by zero

() => Boolean vs. => Boolean

If we had a definition def callByName(x: () => Boolean) that's just taking function as parameter

Then we would need to call it:

    callByName( () => predicate )
    callByName( () => 100 > 1   )

With def callByName(x: => Boolean) that's proper call by-name

We can call it like that and not worry that it's evaluated immediately:

    callByName( 100 > 1 )  // evaluated inside the function
    callByName(predicate)  // i.e. not immediately

Control Abstractions

Providing 1 function as parameter

    def runInThread(block: () => Unit) {   // parameter is a function'()=>Unit'
        new Thread {
           override def run() { block() } // block is evaluated in new thread
        }.start()                        //  not before
    }

E.g:

    runInThread { 
        () => { println("Hi");
                Thread.sleep(1000); 
                println("Bye") } 
    }

Changing it to Call-by-name

  • If (block: () => Unit) is changed to call by-name (block: => Unit)

  • Then () => { println("Hi")} can be replaced with println("Hi")

  • But also block() must be replaced with block because in 'call by-name' block it treated as a value ( evaluated at first usage of call by-value)

    def runInThread(block: => Unit) {     // call by-name
        new Thread {
            override def run() { block } // block is evaluated in new thread
        }.start()                       //  not before
    }
    runInThread { println("Hi"); Thread.sleep(1000); println("Bye") }

Two parameters called by-name

    def keepDoingWhen(condition: => Boolean)(block: => Unit) {
        if (condition) {
             block
             keepDoingWhen(condition)(block)
        }
    }

Usage of call by-name

    var x = 10
    keepDoingWhen (x > 0) {  // The 'x > 0' is not valuated here
         x -= 1
         println(x)
    }

or the original way (without call by-name) the same but ugly cause we are passing functions:

    def keepDoingWhen(condition: () => Boolean)(block: () => Unit) {
        if (condition()) {
            block()
            keepDoingWhen(condition)(block)
        }
    }
    
    var x = 10
    keepDoingWhen( () => x > 0) { // the same but ugly
         () => x -= 1
         println(x)
    }