zoukankan      html  css  js  c++  java
  • Beginning Scala study note(4) Functional Programming in Scala

    1. Functional programming treats computation as the evaluation of mathematical and avoids state and mutable data. Scala encourages an expression-oriented programming(EOP)
      1) In expression-oriented programming every statement is an expression. A statement executes code, but does not return any value. An expression returns value. Note that an expression-oriented programming language is a programming language where every construct is an expression, and thus evaluates to a value.

    scala> val test = if(3>2) "true" else "false"
    test: String = true

      The relating java code:

    boolean test = 3 > 2 ? true : false;

       Scala has unified the concept of ?: with its blocks and so Scala has no ?: syntax.

      2) An expression is referentially transparent(引用透明) if it can be substituted(替代) by its resulting value, without changing the behavior of the program, regardless of where the expression is used in the program. The keystones of functional programming are: referential transparency, higher-order function, and immutable value. A pure function does not mutate the input parameters and always returns the same value for the same input.
      The syntax for a function literal(函数字面量) with a parenthesized comma-separated list of arguments followed by an arrow and the body of the function. It's also called an anonymous function.
      A function value is a function object and you can invoke the function object in the same manner as you invoke any other function. The function object extends one of the FunctionN traits, FunctionN depends on the number of arguments.

    scala> val add = (x: Int, y: Int) => x + y
    add: (Int, Int) => Int = <function2>
    scala> add(1,2)
    res0: Int = 3

       The invocation of this function is converted to a call to the apply method of the Function class instance.

    scala> val areaOfRectangle:(Int, Int) => Int = ( Int, height: Int) => {width*height}
    areaOfRectangle: (Int, Int) => Int = <function2>
    scala> areaOfRectangle(5,3)
    res1: Int = 15

       Trait scala.Function2 in the Scala package:

      trait Function2[-T1, -T2, +R] extends AnyRef { ... abstract def apply( v1 :T1, v2 :T2 ) : R ... }

    scala> val areaOfRectangle: Function2[Int,Int,Int] = ( Int, height: Int) => width*height
    areaOfRectangle: (Int, Int) => Int = <function2>

       You can explicitly call the apply method:

    scala> areaOfRectangle.apply(5,6)
    res4: Int = 30

       You can even define a function by implementing an appropriate Function Trait and define its required apply method.

    scala> val areaOfRectangle: (Int, Int) => Int = new Function2[Int, Int, Int]{ 
    | def apply( Int, height: Int):Int = { 
    | width*height 
    | } 
    | }
    areaOfRectangle: (Int, Int) => Int = <function2>

     2. A first-class function is a function that can be: 1) Assigned to variables, 2) Passed as an argument to the other function, and 3) Returned as values from the other function. And such functions, which take functions as arguments or return a function, are called higher-order(高阶) functions.

      1) Function as Variable

    scala> val doubler = (i: Int) => {i*2}
    doubler: Int => Int = <function1>
    scala> doubler(10)
    res0: Int = 20

       The variable doubler is an instance of a function, known as a function value.

      2) Function as Parameter

    scala> def operation(functionparam: (Int, Int) => Int){ 
    | println(functionparam(4,4))}
    operation: (functionparam: (Int, Int) => Int)Unit
    scala> val add = (x: Int, y: Int) => {x+y}
    add: (Int, Int) => Int = <function2>
    # Any function that matches this signature can be passed into the operation method
    scala> operation(add)
    8
    scala> val subtract = (x: Int, y: Int) => {x-y}
    subtract: (Int, Int) => Int = <function2>
    scala> operation(subtract)
    0
    scala> val multiply = (x: Int, y: Int) => {x*y}
    multiply: (Int, Int) => Int = <function2>
    scala> operation(multiply)
    16

       3) Returning a Function

      You can return a function from a function or method. In order to do this, first define an anonymous function.

    # define an anonymous function
    scala> def greeting = (name: String) => {"hello "+name}
    greeting: String => String
    # now you can assign greeting() to a variable
    scala> val greet = greeting
    greet: String => String = <function1>
    scala> greet("World")
    res4: String = hello World

       4) Closure

      A closure is a function, whose return value depends on the value of one or more variables declared outside this function.

    scala> var y = 3
    y: Int = 3
    scala> val multiplier = (x: Int) => x * y
    multiplier: Int => Int = <function1>
    scala> multiplier(3)
    res0: Int = 9
    scala> y = 10
    y: Int = 10
    scala> multiplier(6)
    res2: Int = 60

      The multiplier function references y and reads its current value each time. The Scala compiler creates a closure that encompass the variable in the enclosing scope.

      5) Partially Applied Function
      When all parameters are passed to the function you have fully applied the function to all the parameters.

    scala> val add = (x: Int, y: Int) => x + y
    add: (Int, Int) => Int = <function2>
    scala> add(1,2)
    res3: Int = 3

       When you give only a subset of the parameters to the function, the result of the expression is a partially applied function. It shows partiallyAdd is a fuction that implements the Function1 trait.

    scala> val partiallyAdd = add(1, _:Int)
    partiallyAdd: Int => Int = <function1>
    scala> partiallyAdd(10)
    res4: Int = 11

       The first argument 1 was passed int the original add function and the new function named partiallyAdd was created, which is a partially applied function; then, the second argument 10 was passed into partiallyAdd. When you provide all the parameters, the original function is executed, yielding the result.

      6) Curried Function
      Currying converts a function with multiple parameters creating a chain of function, each expecting a single parameter.

    scala> val add = (x: Int, y: Int) => x + y
    add: (Int, Int) => Int = <function2>
    # curried functions
    scala> def add(x: Int)(y: Int) = x + y
    add: (x: Int)(y: Int)Int
    scala> add(4)(5)
    res10: Int = 9
    # another form
    scala> def add(x: Int) = (y: Int) => x + y
    add: (x: Int)Int => Int
    scala> add(4)(3)
    res11: Int = 7

       7) Function Composition

    An implication of composability is that functions can be treated as values.

    sealed trait Expr # sealed仅能被同文件中的类继承
    case class Add(left: Expr, right: Expr) extends Expr
    case class Mul(left: Expr, right: Expr) extends Expr
    case class Val(value: Int) extends Expr
    case class Var(name: String) extends Expr

       We can build expressions like:

    1+1 => Add(Val(1), Val(1)) 
    3 * (1 + 1) => Mul(Val(3),Add(Val(1), Val(1))  
    a * 11 => Mul(Var("a"), Val(11))
    scala> def calc(expr: Expr, vars: Map[String, Int]):Int = expr match{ 
    | case Add(left,right) => calc(left, vars) + calc(right, vars) 
    | case Mul(left,right) => calc(left, vars) + calc(right, vars) 
    | case Val(v) => v 
    | case Var(name) => vars(name) 
    | }

       If expr is an Add, we extract the left and right parameters, which are themselves Exprs. We call calc to calculate the value of the left and right parameters and add the results. 

      8) Tail Calls and Tail Call Optimization
      A recursive function is one that may invoke itself.

    scala> def factorial(number: Int): Int = {
    | if(number == 1)
    | return 1
    | number * factorial(number-1)
    | }
    factorial: (number: Int)Int
    scala> println(factorial(4))
    24

       The Scala compiler can optimize recursive functions with tail recursion so that recursive calls do not use all the stack space, therefore not running into stack-overflow error. Only functions whose last statement is the recursive invocation can be optimized for tail-recursion by the Scala compiler.

      Scala provides an annotation available to mark a function to be optimized for tail-recursion. A function marked with the annotation causes an error at compilation time if it cannot be optimized for tail-recursion. Add @annotation.tailrec before the function definition.

    # The recursive call is not the last statement 
    scala> @annotation.tailrec
    | def factorial(number: Int): Int = {
    | if(number == 1)
    | return 1
    | number * factorial(number - 1)
    | }
    <console>:11: error: could not optimize @tailrec annotated method factorial: it contains a recursive call not in tail position
    number * factorial(number - 1)
    ^
    scala> @annotation.tailrec
    | def factorial(accumulator: Int, number: Int): Int = {
    | if(number == 1)
    | return accumulator
    | factorial(number * accumulator, number - 1)
    | }
    factorial: (accumulator: Int, number: Int)Int

       A successful compile guarantees that the function will be optimized with tail recursion, so that each successive call will not add new stack frames.

      9) Call-by-Name, Call-by-Value, and General Laziness
      In java programs, when you call a method with parameters, the value of the parameters are all calculated before the method is called. There are some cases when you want parameters to be optionally evaluated or repeatedly evaluated. In this case, Scala provides the call-by-name mechanism.

    # java code of log messages
    if(logger.level().intValue() >= INFO.intValue()){
        logger.log(INFO,"The value is "+value)
    }
    # Call-by-name has the ability to delay the evaluation of the String to log only  if that String will actually be logged
    def log(level: Level, msg: => String) = 
        if(logger.level().intValue() >= INFO.intValue()) 
            logger.log(level, msg)
    # Call this code
    log(INFO, "The value is "+value)        

       The log method will access '"The value is "+value' only if the log message is going to be printed. In order to make something call-by-name, just put => before the type.

      The first use of call-by-name is passing an expression that takes a long time to evaluate that may not be evaluated. The second use for call-by-name is the situation where we want to evaluate the expression many times in the target method.

    # we could collect all the Strings returned from an expression until we encounter a null
    def allString(expr: => String): List[String] = expr match {
    case null => Nil
    case s => s :: allStrings(expr)
    }
    scala> import java.io._
    import java.io._
    scala> val br = new BufferedReader(new FileReader("nohup.out"))
    br: java.io.BufferedReader = java.io.BufferedReader@2d33ee43
    scala> allString(br.readLine)
    res5: List[String] = List(1,2,3,4)
    scala> for(str <- res5) println(str)
    1
    2
    3
    4
    
    # scala method
    import scala.io.Source
    if(args.length > 0){
        for(line <- Source.fromFile(args(0)).getLines())
            println(line.length+" "+line)
    }else
        Console.err.println("Please enter filename")
  • 相关阅读:
    django rest framework 去掉url尾部的斜杠
    django url 参数
    windows系统C盘显示100G已用完,全选所有的文件夹查看却只有50G?
    关于RESTful名字的含义
    tensorflow 之 tf.reshape 之 -1
    关于python cv2-1.0(ImportError: No module named cv2)
    spark元组的列表转化为字典
    python 中的list 转 array 以及 array 转 list 以及array.array numpy.array
    web 版processing显示图片
    网页版的processing
  • 原文地址:https://www.cnblogs.com/mengrennwpu/p/6106502.html
Copyright © 2011-2022 走看看