Chapter 5 Functional Programming in Scala

1) Object-oriented programming

Solve the problem, decompose the object, behavior, attribute, and then solve the problem through the relationship of the object and the call of the behavior.

Object: User

Behavior: login, connect to JDBC, read database

Attributes: username, password

Scala language is a completely object-oriented programming language. everything is an object

The essence of objects: an encapsulation of data and behavior

2) Functional programming

When solving a problem, decompose the problem into steps one by one, encapsulate each step (function), and solve the problem by calling these encapsulated steps.

For example: request->username, password->connect JDBC->read database

The Scala language is a fully functional programming language. Everything is a function.

The essence of functions: functions can be passed as a value

3) Functional programming and object-oriented programming are perfectly integrated in Scala.

5.1 Function basics

5.1.1 Function Basic Syntax

    • basic grammar

2) Case Practice

Requirement: Define a function to print out the name passed in.

object TestFunction {
  def main(args: Array[String]): Unit = {
   // (1) Function definition
     def f(arg: String): Unit = {
       println(arg)
     }
     // (2) Function call
     // function name (parameter)
      f("hello world")
     }
}

5.1.2 Difference between functions and methods

1) Core concepts

(1) A collection of program statements that accomplish a certain function is called a function.

(2) The functions in a class are called methods.

2) Case Practice

(1) Scala language can declare any syntax in any syntax structure

(2) Functions do not have the concept of overloading and rewriting; methods can be overloaded and rewritten

(3) Functions in Scala can be defined nestedly

object TestFunction {
 // (2) The method can be overloaded and rewritten, and the program can be executed
 def main(): Unit = {
 }
 def main(args: Array[String]): Unit = {
 // (1) Scala language can declare any syntax in any syntax structure
 import java.util.Date
 new Date()
 // (2) The function does not have the concept of overloading and rewriting, and the program reports an error
 def test(): Unit ={
 println("No parameters, no return value")
 }
 test()
 def test(name:String):Unit={
 println()
 }
 //(3) Functions in Scala can be defined nestedly
 def test2(): Unit ={
 def test3(name:String):Unit={
 println("Functions can be defined nested")
 }
 }
 }
}

5.1.3 Function definition

1) Function definition

(1) Function 1: no parameters, no return value

(2) Function 2: no parameters, return value

(3) Function 3: There are parameters, but no return value

(4) Function 4: with parameters and return value

(5) Function 5: multiple parameters, no return value

(6) Function 6: Multiple parameters, return value

2) Case Practice

package com.learn.day05

/**
 * @author Lucaslee
 * @create 2023-02-02 15:56
 */
object Test02_FunctionDefine {
  def main(args: Array[String]): Unit = {
    //no parameters and no return value
    def f1(): Unit = {
      println("no parameters and no return value")
    }
//    f1()
    println(f1())

    println("=========================")

    //    (2) Function 2: no parameters, return value
    def f2(): Int = {
      println("2. No parameters, return value")
      return 12
    }
    println(f2())

    println("=========================")

    //    (3) Function 3: with parameters, no return value
    def f3(name: String): Unit = {
      println("3: With parameters, no return value " + name)
    }

    println(f3("alice"))

    println("=========================")

    //    (4) Function 4: There are parameters and return values
    def f4(name: String): String = {
      println("4: There are parameters and return values " + name)
      return "hi, " + name
    }

    println(f4("alice"))

    println("=========================")

    //    (5) Function 5: Multiple parameters, no return value
    def f5(name1: String, name2: String): Unit = {
      println("5: Multiple parameters, no return value")
      println(s"${name1}and ${name2}all my good friends")
    }

    f5("alice","bob")

    println("=========================")

    //    (6) Function 6: Multiple parameters, return value
    def f6(a: Int, b: Int): Int = {
      println("6: Multi-parameter, with return value")
      return a + b
    }

    println(f6(12, 37))
  }

}

5.1.4 Function parameters

1) Case Practice

(1) variable parameters

(2) If there are multiple parameters in the parameter list, the variable parameters are generally placed at the end

(3) The default value of the parameter, generally the parameter with the default value is placed behind the parameter list

(4) Named parameters

package com.learn.day05

/**
 * @author Lucaslee
 * @create 2023-02-02 16:07
 */
object Test03_FunctionParameter {
  def main(args: Array[String]): Unit = {
    //  (1) variable parameters
    def f1(str: String*): Unit = {
      println(str)
    }

    f1("alice")
    f1("aaa", "bbb", "ccc")

    //  (2) If there are multiple **parameters in the parameter list, the variable parameters are generally placed at the end**
    def f2(str1: String, str2: String*): Unit = {
      println("str1: " + str1 + " str2: " + str2)
    }
    f2("alice")
    f2("aaa", "bbb", "ccc")

    //  (3) The default value of the parameter, generally the parameter with the default value is placed behind the parameter list
    def f3(name: String = "atguigu"): Unit = {
      println("My school is " + name)
    }

    f3("school")
    f3()

    //  (4) Named parameters
    def f4(name: String = "atguigu", age: Int): Unit = {
      println(s"${age}Year-old ${name}Study in Silicon Valley")
    }

    f4("alice", 20)
    //You can pass parameters out of order, as long as you specify the parameter name
    f4(age = 23, name = "bob")
    f4(age = 21)
  }
}

5.1.5 The principle of simplicity of functions (emphasis)

The principle of simplicity of function: save what you can

1) The details of the principle of simplicity

  • (1) return can be omitted, and Scala will use the last line of code in the function body as the return value

  • (2) If the function body has only one line of code, the curly braces can be omitted

  • (3) If the return value type can be inferred, it can be omitted (: and the return value type are omitted together)

  • (4) If there is a return, the return value type cannot be omitted and must be specified

  • (5) If the function explicitly declares unit, then even if the return keyword is used in the function body, it will not work

  • (6) Scala can omit the equal sign if it is expected to be a non-return type

  • (7) If the function has no parameters, but the parameter list is declared, then when calling, parentheses can be added or not

  • (8) If the function has no parameter list, the parentheses can be omitted, and the parentheses must be omitted when calling

  • (9) If you don’t care about the name and only care about logic processing, then the function name (def) can be omitted

2) Case Practice

package com.learn.day05

/**
 * @author Lucaslee
 * @create 2023-02-02 16:19
 */
//The principle of function to simplicity
object Test04_Simplify {
  def main(args: Array[String]): Unit = {
    // (0) Function standard writing
    def f0(name: String): String = {
      return name
    }
    println(f0("atguigu"))
    println("==========================")

    //  (1) return can be omitted, and Scala will use the last line of code in the function body as the return value
    def f1(name: String): String = {
      name
    }
    println(f1("atguigu"))

    println("==========================")
    //  (2) If the function body has only one line of code, the curly braces can be omitted
    def f2(name: String): String = name
    println(f2("atguigu"))

    println("==========================")
    //  (3) If the return value type can be inferred, it can be omitted (: and the return value type are omitted together)
    def f3(name: String) = name
    println(f3("atguigu"))

    println("==========================")
    //  (4) If there is a return, the return value type cannot be omitted and must be specified
    //    def f4(name: String) = {
    //      return name
    //    }
    //
    //    println(f4("atguigu"))

    println("==========================")
    //  (5) If the function explicitly declares unit, then even if the return keyword is used in the function body, it will not work
    def f5(name: String): Unit = {
      return name
    }

    println(f5("atguigu"))
    //  (6) Scala can omit the equal sign if it is expected to be a non-return type
    def f6(name: String) {
      println(name)
    }

    println(f6("atguigu"))

    println("==========================")
    //  (7) If the function has no parameters, but the parameter list is declared, then when calling, parentheses can be added or not
    def f7(): Unit = {
      println("atguigu")
    }

    f7()
    f7

    println("==========================")
    //  (8) If the function has no parameter list, the parentheses can be omitted, and the parentheses must be omitted when calling
    def f8: Unit = {
      println("atguigu")
    }

    //    f8()
    f8

    println("==========================")
    //  (9) If you don’t care about the name and only care about logic processing, then the function name (def) can be omitted
    //normally
    def f9(name: String): Unit = {
      println(name)
    }
    
    //After omitting def, it becomes like this
    // Anonymous functions, lambda expressions
    (name: String) => { println(name) }

      println("==========================")
  }

}

Simplification of lambda expressions:

object Test05_Lambda {
  def main(args: Array[String]): Unit = {
    val fun = (name: String) => {
      println(name)
    }
    fun("atguigu")

    println("========================")

    // Define a function that takes the function as a parameter input
    def f(func: String => Unit): Unit = {
      func("atguigu")
    }

    f(fun)
    f((name: String) => {
      println(name)
    })

    println("========================")

    // Simplification Principles for Anonymous Functions
    //    (1) The type of the parameter can be omitted, and it will be automatically deduced according to the formal parameter
    f((name) => {
      println(name)
    })

    //    (2) After the type is omitted, if it is found that there is only one parameter, the parentheses can be omitted; in other cases: parentheses can never be omitted if there are no parameters and parameters exceed 1.
    f( name => {
      println(name)
    })

    //    (3) If the anonymous function has only one line, the braces can also be omitted
    f( name => println(name) )

    //    (4) If the parameter appears only once, the parameter is omitted and the following parameter can be replaced by _
    f( println(_) )

    //     (5) If it can be inferred that the currently passed println is a function body, not a call statement, you can directly omit the underscore
    f( println )

    println("=========================")

    // Practical example, define a "binary operation" function, which only operates two numbers 1 and 2, but the specific operation is passed in through parameters
    def dualFunctionOneAndTwo(fun: (Int, Int)=>Int): Int = {
      fun(1, 2)
    }

    val add = (a: Int, b: Int) => a + b
    val minus = (a: Int, b: Int) => a - b

    println(dualFunctionOneAndTwo(add))
    println(dualFunctionOneAndTwo(minus))

    // Anonymous function simplification
    println(dualFunctionOneAndTwo((a: Int, b: Int) => a + b))
    println(dualFunctionOneAndTwo((a: Int, b: Int) => a - b))

    println(dualFunctionOneAndTwo((a, b) => a + b))
    println(dualFunctionOneAndTwo( _ + _))
    println(dualFunctionOneAndTwo( _ - _))

    println(dualFunctionOneAndTwo((a, b) => b - a))
    println(dualFunctionOneAndTwo( -_ + _))
  }
}

5.2 Advanced functions

5.2.1 Higher-order functions

In Scala, functions are first-class citizens. How did it manifest?

For a function we can **: define function, call function**

object TestFunction {
  def main(args: Array[String]): Unit = {
    // Call functions
    foo()
  }
  // define function
  def foo():Unit = {
    println("foo...")
  }
}

But in fact, there are higher-order usages of functions

1) Functions can be passed as values

object TestFunction {
  def main(args: Array[String]): Unit = {
    //(1) Call the foo function and give the return value to the variable f
    //val f = foo()
    val f = foo
    println(f)
    **//(2) Adding _ after the called function foo is equivalent to treating the function foo as a whole and passing it to the variable f1**
    val f1 = foo _
    foo()
    f1()
    **//(3) If the variable type is clear, the function can be passed to the variable as a whole without using an underscore**
    var f2:()=>Int = foo
  }
  def foo():Int = {
    println("foo...")
    1
  }
}

2) Functions can be passed as parameters

object Test05_Lambda {
  def main(args: Array[String]): Unit = {

   ** // (1) Define a function, the function parameter is still a function signature; f represents the function name; (Int,Int) represents the input of two
     Int parameter; Int Represents the return value of the function**
    def f1(f: (Int, Int) => Int): Int = {
      f(2, 4)
    }

    // (2) Define a function whose parameters and return value types are consistent with the input parameters of f1
    def add(a: Int, b: Int): Int = a + b

    **// (3) Pass the add function as a parameter to the f1 function, if it can be deduced that it is not a call, _
    can be omitted**
    println(f1(add))
    println(f1(add _))
    **//Anonymous functions can be passed**
  }

}

3) Functions can be returned as function return values

object Test05_Lambda {
  def main(args: Array[String]): Unit = {
    def f1() = {
      def f2() = {
      }

      f2 _
    }

    val f = f1()
    // Because the return value of the f1 function is still a function, the variable f can continue to be called as a function
    f()
    // The code above can be simplified to
    f1()()
  }

}

5.2.2 Anonymous functions

1) Description

A function without a name is an anonymous function.

(x:Int)=>{function body}

  • x: Indicates the input parameter type;

  • Int: Indicates the input parameter type;

  • Function body: represents specific code logic

2) Case Practice

Requirement 1: The passed function has a parameter

Passing anonymous functions to simple principles:

(1) The type of the parameter can be omitted, and it will be automatically deduced according to the formal parameter

(2) After the type is omitted, if it is found that there is only one parameter, the parentheses can be omitted; in other cases: the parentheses can never be omitted if there are no parameters and the number of parameters exceeds 1.

(3) If the anonymous function has only one line, the braces can also be omitted

(4) If the parameter appears only once, the parameter is omitted and the following parameter can be replaced by _

package com.learn.day05

/**
 * @author Lucaslee
 * @create 2023-02-02 16:45
 */
object Test05_Lambda {
  def main(args: Array[String]): Unit = {
    // (1) Define a function: parameters include data and logic functions
    def operation(arr: Array[Int], op: Int => Int) = {
      for (elem <- arr) yield op(elem)
    }

    // (2) Define the logic function
    def op(ele: Int): Int = {
      ele + 1
    }

    // (3) Standard function call
    val arr = operation(Array(1, 2, 3, 4), op)
    println(arr.mkString(","))
    // (4) Using anonymous functions
    val arr1 = operation(Array(1, 2, 3, 4), (ele: Int) => {ele + 1})
    println(arr1.mkString(","))
    // (4.1) The type of the parameter can be omitted, and it will be automatically deduced according to the formal parameter;
    val arr2 = operation(Array(1, 2, 3, 4), (ele) => {ele + 1})
    println(arr2.mkString(","))
    // (4.2) After the type is omitted, if it is found that there is only one parameter, the parentheses can be omitted; in other cases: if there are no parameters and the number of parameters exceeds 1, the parentheses can never be omitted.
    val arr3 = operation(Array(1, 2, 3, 4), ele => {ele + 1})
    println(arr3.mkString(","))
    // (4.3) If the anonymous function has only one line, the braces can also be omitted
    val arr4 = operation(Array(1, 2, 3, 4), ele => ele + 1)
    println(arr4.mkString(","))
    //(4.4) If the parameter appears only once, the parameter is omitted and the subsequent parameter can be replaced by _
    val arr5 = operation(Array(1, 2, 3, 4), _ + 1)
    println(arr5.mkString(","))
  }
}

Requirement 2: The passed function has two parameters

object TestFunction {
  def main(args: Array[String]): Unit = {
    def calculator(a: Int, b: Int, op: (Int, Int) => Int): Int
    = {
      op(a, b)
    }
    // (1) Standard Edition
    println(calculator(2, 3, (x: Int, y: Int) => {x + y}))
    // (2) If there is only one line, the braces can also be omitted
    println(calculator(2, 3, (x: Int, y: Int) => x + y))
    // (3) The type of the parameter can be omitted, and it will be automatically deduced according to the formal parameter;
    println(calculator(2, 3, (x , y) => x + y))
    // (4) If the parameter appears only once, the parameter is omitted and the following parameter can be replaced by _
    println(calculator(2, 3, _ + _))
  }
}

extended exercise

Exercise 1: Define an anonymous function and assign it as a value to the variable fun. The function has three parameters, the types are Int, String, and Char, and the return value type is Boolean.

It is required to call the function fun(0, “”, ‘0’) to return false, and return true in other cases.

 // 1. Exercise 1
   //common writing
   def f(num: Int,str: String,cha: Char): Boolean ={
     if(num == 0 && str == "" && cha == '0'){false}
     else {true}
   }
    //Anonymous function writing
    val fun = (num: Int,str: String,cha: Char) => { if(num == 0 && str == "" && cha == '0')false else true }

    println(fun(0, "", '0'))
    println(fun(0, "", '1'))
    println(fun(23, "", '0'))
    println(fun(0, "hello", '0'))
    
    
    output:
    false
    true
    true
    true

Exercise 2: Define a function func that takes an argument of type Int and returns a function (let it be f1).

It returns a function f1 that takes a parameter of type String and returns a function (denoted as f2) as well. The function f2 receives a parameter of type Char and returns a value of Boolean.

It is required to call the function func(0) ("") ('0') to get the return value false, and return true in other cases.

    // 2. Exercise 2
    def func(i: Int): String=>(Char=>Boolean) = {
      def f1(s: String): Char=>Boolean = {
        def f2(c: Char): Boolean = {
          if (i == 0 && s == "" && c == '0') false else true
        }
        f2
      }
      f1
    }

    println(func(0)("")('0'))
    println(func(0)("")('1'))
    println(func(23)("")('0'))
    println(func(0)("hello")('0'))

    // Anonymous function shorthand
    def func1(i: Int): String=>(Char=>Boolean) = {
      s => c => if (i == 0 && s == "" && c == '0') false else true
    }

    println(func1(0)("")('0'))
    println(func1(0)("")('1'))
    println(func1(23)("")('0'))
    println(func1(0)("hello")('0'))

    // Currying
    def func2(i: Int)(s: String)(c: Char): Boolean = {
      if (i == 0 && s == "" && c == '0') false else true
    }
    println(func2(0)("")('0'))
    println(func2(0)("")('1'))
    println(func2(23)("")('0'))
    println(func2(0)("hello")('0'))

5.2.3 Higher-order function case

Requirements: simulate Map mapping, Filter filtering, Reduce aggregation

object TestFunction {
  def main(args: Array[String]): Unit = {
    // (1) map mapping
    def map(arr: Array[Int], op: Int => Int) = {
      for (elem <- arr) yield op(elem)
    }
    val arr = map(Array(1, 2, 3, 4), (x: Int) => {
      x * x
    })
    println(arr.mkString(","))
    // (2) filter filter. If there are parameters, and the parameters are only used once, then the parameters are omitted and the following parameters are represented by _
    def filter(arr:Array[Int],op:Int =>Boolean) ={
      var arr1:ArrayBuffer[Int] = ArrayBuffer[Int]()
      for(elem <- arr if op(elem)){
        arr1.append(elem)
      }
      arr1.toArray
    }
    var arr1 = filter(Array(1, 2, 3, 4), _ % 2 == 1)
    println(arr1.mkString(","))
    // (3) reduce aggregation. There are multiple parameters, and each parameter is used only once, then the parameter
    The number is omitted and the following parameters are used_said that the first n indivual_On behalf of n parameters
    def reduce(arr: Array[Int], op: (Int, Int) => Int) = {
      var init: Int = arr(0)
      for (elem <- 1 until arr.length) {
        init = op(init, elem)
      }
      init
    }
    //val arr2 = reduce(Array(1, 2, 3, 4), (x, y) => x * y)
    val arr2 = reduce(Array(1, 2, 3, 4), _ * _)
    println(arr2)
  }
}

5.2.4 Function currying & closure

Closures: a standard feature of functional programming

1) Description

Closure: If a function accesses the value of its external (local) variables, then this function and its environment are called closures

Function Currying: Turn multiple parameters of a parameter list into multiple parameter lists.

2) Case Practice

(1) closure

object TestFunction {
  def main(args: Array[String]): Unit = {
    def f1()={
      var a:Int = 10
      def f2(b:Int)={
        a + b
      }
      f2 _
    }
    // When calling, after the execution of the f1 function is completed, the local variable a should be released along with the stack space
    val f = f1()
    // But here, the variable a is actually not released, but contained inside the f2 function, forming a closed effect
    println(f(3))

    println(f1()(3))
    // Function currying, in fact, is to simplify the complex parameter logic, function currying must have a closure
    def f3()(b:Int)={
      a + b
    }
    println(f3()(3))
  }
}

5.2.5 Recursion

1) Description

A function/method calls itself in the body of the function/method, we call it a recursive call

2) Case Practice

object TestFunction {
  def main(args: Array[String]): Unit = {
    // factorial
    // recursive algorithm
    // 1) The method calls itself
    // 2) The method must have the logic of jumping out
    // 3) When the method calls itself, the passed parameters should be regular
    // 4) Recursion in scala must declare the return value type of the function
    println(test(5))
  }

  def test(i: Int): Int = {
    if (i == 1) {
      1
    } else {
      i * test(I - 1)
    }
  }
}
import scala.annotation.tailrec

object Test10_Recursion {
  def main(args: Array[String]): Unit = {
    println(fact(5))
    println(tailFact(5))
  }

  // Recursive implementation of calculating factorial
  def fact(n: Int): Int = {
    if (n == 0) return 1
    fact(n - 1) * n
  }

  // Tail recursive implementation
  def tailFact(n: Int): Int = {
    @tailrec
    def loop(n: Int, currRes: Int): Int = {
      if (n == 0) return currRes
      loop(n - 1, currRes * n)
    }
    loop(n, 1)
  }
}

5.2.6 Control abstraction

1) Value call: pass the calculated value to the past

object TestControl {
  def main(args: Array[String]): Unit = {
    def f = ()=>{
      println("f...")
      10
    }
    foo(f())
  }
  def foo(a: Int):Unit = {
    println(a)
    println(a)
  }
}

2) Name call: pass the code over

object TestControl {
  def main(args: Array[String]): Unit = {
    def f = ()=>{
      println("f...")
      10
    }
    foo(f())
  }
  //def foo(a: Int):Unit = {
  def foo(a: =>Int):Unit = {//Note that the variable a has no parentheses here
    println(a)
    println(a)
  }
}
Output result:
f...
10
f...
10 


Note: Java only has call by value; Scala has both call by value and call by name.

3) Case Practice 1

object TestFunction {
  def main(args: Array[String]): Unit = {
    // (1) Pass code block
    foo({
      println("aaa")
    })
    // (2) Parentheses can be omitted
    foo{
      println("aaa")
    }
  }
  def foo(a: =>Unit):Unit = {
    println(a)
    println(a)
  }
}

4) Case Practice 2

object Test12_MyWhile {
  def main(args: Array[String]): Unit = {
    var n = 10

    // 1. Regular while loop
    while (n >= 1){
      println(n)
      n -= 1
    }

    // 2. Implement a function with a closure, pass in the code block as a parameter, and call it recursively
    def myWhile(condition: =>Boolean): (=>Unit)=>Unit = {
      // The inner function needs to be called recursively, and the parameter is the loop body
      def doLoop(op: =>Unit): Unit = {
        if (condition){
          op
          myWhile(condition)(op)
        }
      }
      doLoop _
    }

    println("=================")
    n = 10
    myWhile(n >= 1){
      println(n)
      n -= 1
    }

    // 3. Implementation with anonymous function
    def myWhile2(condition: =>Boolean): (=>Unit)=>Unit = {
      // The inner function needs to be called recursively, and the parameter is the loop body
      op => {
        if (condition){
          op
          myWhile2(condition)(op)
        }
      }
    }
    println("=================")
    n = 10
    myWhile2(n >= 1){
      println(n)
      n -= 1
    }

    // 3. Implemented with currying
    def myWhile3(condition: =>Boolean)(op: =>Unit): Unit = {
      if (condition){
        op  //Parameters by name ** Note: Java only has call-by-value; Scala has both call-by-value and call-by-name. **
        myWhile3(condition)(op)
      }
    }

    println("=================")
    n = 10
    myWhile3(n >= 1){
      println(n)
      n -= 1
    }
  }
}

5.2.7 Lazy loading

1) Description

When a function return value is declared lazy, the execution of the function will be postponed until we first evaluate it, and the function will not execute. We call this kind of function a lazy function.

2) Case Practice

def main(args: Array[String]): Unit = {
  lazy val res = sum(10, 30)
  println("----------------")
  println("res=" + res)
  }
  def sum(n1: Int, n2: Int): Int = {
  println("sum be executed. . .")
  return n1 + n2
  }
  }
  Output result:
  ----------------
  sum be executed. . .
  res=40

Note: lazy cannot modify variables of type var

Tags: Java Back-end Big Data Scala programming language

Posted by jemrys on Sun, 05 Feb 2023 00:51:39 +1030