• 控制抽象


    控制抽象

    所有的函数都可以被分成通用部分,以及非通用部分,这将导致代码存在大量的冗余。

    代码1-1

    object FileMatcher {
      private def filesHere = new java.io.File(".").listFiles()
      def fileEnding(query: String) =
        for (file <- filesHere; if file.getName.endsWith(query)) yield file
      def fileContains(query: String) =
        for (file <- filesHere; if file.getName.contains(query)) yield file
    }
    

     代码1-1中,fileEnding和fileContains两个方法,一个是查询以query结尾的,一个是查询包含query的,虽然功能不同,但代码大部分却相同,随着功能的增加,冗余的代码将会越来越多,维护也会越来越困难。为了解决这一问题,可以使用高阶函数,可以将函数当做对象传入另外一个函数,如代码1-2。

    object FileMatcher {
      private def filesHere = new java.io.File(".").listFiles()
      def filesMatching(query: String, matcher: (String, String) => Boolean) =
        for (file <- filesHere; if matcher(file.getName, query)) yield file
      def filedEnding(query: String) = filesMatching(query, _.endsWith(_))
      def filedContaining(query: String) = filesMatching(query, _.contains(_))
    }
    

     filesMatching方法除了接收一个String对象的参数,还接收一个函数值对象,这个函数值对象接收两个String对象,经过一系列计算,最后得出的结果为Boolean类型,将filedEnding和filedContaining两个方法分别将_.endsWith(_)_.contains(_)作为对象传入filesMatching方法中,下划线是占位符,如果有不懂的同学可以去看上一篇函数与闭包,红色下划线代表第一个参数,蓝色下划线代表第二个参数,经过改造,可以发现代码的冗余少了很多。

    实际上,我们的代码还可以更精简一点,如代码1-3。

    代码1-3

    object FileMatcher {
      private def filesHere = new java.io.File(".").listFiles()
      def filesMatching(query: String, matcher: (String) => Boolean) =
        for (file <- filesHere; if matcher(file.getName)) yield file
      def filedEnding(query: String) = filesMatching(query, _.endsWith(query))
      def filedContaining(query: String) = filesMatching(query, _.contains(query))
    }
    

     柯里化

      柯里化的函数被应用于多个参数列表,而不是仅仅一个,代码1-4展示的是未被柯里化的函数,它实现了对两个Int类型参数的加法

    代码1-4

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

     相对的,代码1-5展示了柯里化后的同一个函数

    代码1-5

    scala> def add(x: Int)(y: Int) = x + y
    add: (x: Int)(y: Int)Int
    
    scala> add(1)(2)
    res1: Int = 3
    

     当add方法被调用时,实际上接连调用两个传统函数。第一个函数调用带单个的名为x的Int参数,并返回第二个函数的函数值。第二个函数带Int参数y,如代码1-6

    代码1-6

    scala> def first(x: Int) = (y: Int) => x + y
    first: (x: Int)Int => Int
    
    scala> val second = first(1)
    second: Int => Int = <function1>
    
    scala> second(2)
    res2: Int = 3
    

     first和second函数只是柯里化过程的一个演示,它们并不直接连在add函数上,尽管如此,仍然有一个方式获得实际指向add第二个函数的参考,可以用部分应用表达式方式,把占位符标注用在add里,如代码1-7

    代码1-7

    scala> def add(x: Int)(y: Int) = x + y
    add: (x: Int)(y: Int)Int
    
    scala> add(1)(2)
    res1: Int = 3
    
    scala> val onePlus = add(1)_
    onePlus: Int => Int = <function1>
    
    scala> onePlus(2)
    res3: Int = 3
    
    scala> val twoPlus = add(2)_
    twoPlus: Int => Int = <function1>
    
    scala> twoPlus(3)
    res4: Int = 5
    

     add(1)_里的下划线是第二个参数列表的占位符,结果指向一个函数的参考,这个函数被调用的时候,对它唯一的Int参数加1并返回结果

    双倍控制结构

    能够重复一个操作两次,见代码1-8

    代码1-8

    scala> def twice(op: Double => Double, x: Double) = op(op(x))
    twice: (op: Double => Double, x: Double)Double
    
    scala> twice(_ + 1, 6)
    res6: Double = 8.0
    

    IO流的借贷模式

    见代码1-9

    代码1-9

    def withPrintWriter(file: File, op: PrintWriter => Unit) = {
          val writer = new PrintWriter(file)
          try {
            op(writer)
          } finally {
            writer.close()
          }
        }
        withPrintWriter(new File("date.txt"), writer => writer.println(new Date))
    

     使用这个方法的好处是,由withPrintWriter而并非用户代码,确认文件在结尾被关闭。因此忘记关闭文件是不可能的,之所以被称为借贷模式,因此控制抽象函数,如withPrintWriter,打开了资源并“贷出”给函数

    传名参数

      传值参数在函数调用之前表达式会被求值,例如Int、Long等数值参数类型,而传名参数在函数调用前表达式不会求值,只会当做一个匿名参数传递下去,如代码1-10和代码1-11

    代码1-10

    scala> def strToIntByValue(s: String) = {
        println("call strToIntByValue")
        s.toInt
      }
    strToIntByValue: (s: String)Int
    
    scala> strToIntByValue({ println("hello world"); "10" })
    hello world
    call strToIntByValue
    res0: Int = 10
    

     代码1-10中,先打印了hello world再打印call strToIntByValue,方法strToIntByValue时已经对传入参数的表达式求值

     代码1-11

    scala> def strToIntByName(s: => String) = {
        println("call strToIntByName")
        s.toInt
      }
    strToIntByName: (s: => String)Int
    
    scala> strToIntByName({ println("hello world"); "10" })
    call strToIntByName
    hello world
    res1: Int = 10
    

     代码1-11,先打印了strToIntByName再打印hello world,在调用strToIntByName时并未对传入参数的表达式求值,一直到需要用到传入参数表达式的时候

  • 相关阅读:
    borderInterpolate()函数
    cvtColor(src, src_gray, CV_BGR2GRAY)报错
    用OpenCV读取摄像头
    OpenCV的视频输入和相似度测量
    C++ main函数中参数argc和argv含义及用法
    OpenCV的视频读取
    MySql与Oracle的几个主要区别
    OLTP与OLAP的介绍(理论知识)
    IDEA激活
    short i =1; i=i+1与short i=1; i+=1的区别
  • 原文地址:https://www.cnblogs.com/baoliyan/p/6784537.html
Copyright © 2020-2023  润新知