zoukankan      html  css  js  c++  java
  • Scala之偏函数Partial Function

    本文原文出处: http://blog.csdn.net/bluishglc/article/details/50995939 严禁不论什么形式的转载,否则将托付CSDN官方维护权益!

    从使用case语句构造匿名函数谈起

    在Scala里,我们能够使用case语句来创建一个匿名函数(函数字面量)。这有别于一般的匿名函数创建方法。来看个样例:

    scala> List(1,2,3) map {case i:Int=>i+1}
    res1: List[Int] = List(2, 3, 4)

    这很有趣,case i:Int=>i+1构建的匿名函数等同于(i:Int)=>i+1。也就是以下这个样子:

    scala> List(1,2,3) map {(i:Int)=>i+1}
    res2: List[Int] = List(2, 3, 4)

    《Scala In Programming》一书对独立的case语句作为匿名函数(函数字面量)有权威的解释:

    Essentially, a case sequence is a function literal, only more general. Instead of having a single entry point and list of parameters, a case sequence has multiple entry points, each with their own list of parameters. Each case is an entry point to the function, and the parameters are specified with the pattern. 
    

    一个case语句就是一个独立的匿名函数,假设有一组case语句的话。从效果上看,构建出的这个匿名函数会有多种不同的參数列表,每个case相应一种參数列表,參数是case后面的变量声明,其值是通过模式匹配赋予的。

    使用case语句构造匿名函数的“额外”优点

    使用case语句构造匿名函数是有“额外”优点的。这个“优点”在以下这个样例中得到了充分的体现:

    List(1, 3, 5, "seven") map { case i: Int => i + 1 } // won't work
    // scala.MatchError: seven (of class java.lang.String)
    List(1, 3, 5, "seven") collect { case i: Int => i + 1 }
    // verify
    assert(List(2, 4, 6) == (List(1, 3, 5, "seven") collect { case i: Int => i + 1 }))

    在这个样例中:传递给map的case语句构建的是一个普通的匿名函数。在把这个函数适用于”seven”元素时发生了类型匹配错误。而对于collect,它声明接受的是一个偏函数:PartialFunction。传递的case语句能良好的工作说明这个case语句被编译器自己主动编译成了一个PartialFunction!这就是case语句“额外”的优点:case语句(组合)除了能够被编译为匿名函数(类型是FunctionX,在Scala里,全部的函数字面量都是一个对象,这个对象的类型是FunctionX)。还能够很方便的编译为一个偏函数PartialFunction!(注意:PartialFunction同一时候是Function1的子类)编译器会依据调用处的函数类型声明自己主动帮我们判定怎样编译这个case语句(组合)。

    上面我们直接抛出了偏函数的概念,这会让人头晕,我们能够仅仅从collect这个演示样例的效果上去理解偏函数:它仅仅对会作用于指定类型的參数或指定范围值的參数实施计算。超出它的界定范围之外的參数类型和值它会忽略(未必会忽略,这取决于你打算怎样处理)。就像上面样例中一样。case i: Int => i + 1仅仅声明了对Int參数的处理,在遇到”seven”元素时。不在偏函数的适用范围内,所以这个元素被忽略了。

    正式认识偏函数Partial Function

    如同在一開始的样例中那样。我们手动实现了一个与case i:Int=>i+1等价的那个匿名函数(i:Int)=>i+1,那么在上面的collect方法中使用到的case i: Int => i + 1它的等价函数是什么呢?显然。不可能是(i:Int)=>i+1了,由于我们已经解释了,collect接受的參数类型是PartialFunction[Any,Int],而不是(Int)=>Int。 那个case语句相应的偏函数具体是什么样的呢?来看:

    scala> val inc = new PartialFunction[Any, Int] {
         | def apply(any: Any) = any.asInstanceOf[Int]+1
         | def isDefinedAt(any: Any) = if (any.isInstanceOf[Int]) true else false
         | }
    inc: PartialFunction[Any,Int] = <function1>
    
    scala> List(1, 3, 5, "seven") collect inc
    res4: List[Int] = List(2, 4, 6)

    PartialFunction特质规定了两个要实现的方法:apply和isDefinedAt,isDefinedAt用来告知调用方这个偏函数接受參数的范围,能够是类型也能够是值,在我们这个样例中我们要求这个inc函数仅仅处理Int型的数据。

    apply方法用来描写叙述对已接受的值怎样处理,在我们这个样例中,我们仅仅是简单的把值+1,注意。非Int型的值已被isDefinedAt方法过滤掉了,所以不用操心类型转换的问题。

    上面这个样例写起来真得很笨拙,和前面的case语句方式比起来真是差太多了。这个样例从反面展示了:通过case语句组合去是实现一个偏函数是多么简洁。实际上case语句组合与偏函数的用意是高度贴合的,所以使用case语句组合是最简单明智的选择。相同是上面的inc函数。换成case去写例如以下:

    scala> def inc: PartialFunction[Any, Int] =
         | { case i: Int => i + 1 }
    inc: PartialFunction[Any,Int]
    
    scala> List(1, 3, 5, "seven") collect inc
    res5: List[Int] = List(2, 4, 6)

    当然,假设偏函数的逻辑很复杂,可能通过定义一个专门的类并继承PartialFunction是更好选择。

    Case语句是怎样被编译成偏函数的

    关于这个问题在《Programming In Scala》中有较为具体的解释。对于这样一个使用case写在的偏函数:

    val second: PartialFunction[List[Int],Int] = {
        case x :: y :: _ => y
    }

    In fact, such an expression gets ranslated by the Scala compiler to a partial function by translating the patterns twice—once for the implementation of the real function, and once to test whether the function is defined or not. For instance, the function literal { case x :: y :: _ => y }above gets translated to the following partialfunction value:

    new PartialFunction[List[Int], Int] {
        def apply(xs: List[Int]) = xs match {
            case x :: y :: _ => y
        }
        def isDefinedAt(xs: List[Int]) = xs match {
            case x :: y :: _ => true
            case _ => false
        }
    }

    为什么偏函数须要抽象成一个专门的Trait

    首先。在Scala里。一切皆对象。函数字面量(匿名函数)也不例外!这也是为什么我们能够把函数字面量赋给一个变量的原因, 是对象就有相应的类型,那么一个函数字面量的真实类型是什么呢?看以下这个样例:

    scala> var inc = (x: Int) => x + 1
    inc: Int => Int = <function1>
    
    scala> inc.isInstanceOf[Function1[Int,Int]]
    res0: Boolean = true

    在Scala的scala包里,有一系列Function trait,它们实际上就是函数字面量作为“对象”存在时相应的类型。

    Function类型有多个版本号,Function0表示无參数函数。Function1表示仅仅有一个參数的函数。以此类推。

    至此我们解释的是一个普遍性问题:是函数就是对象。是对象就有类型。

    那么,接下来我们看一下偏函数又应该是什么样的一种“类型”?

    从语义上讲,偏函数差别于普通函数的唯一特征就是:偏函数会自主地告诉调用方它的处理參数的范围,范围既但是值也能够是类型。

    针对这种场景,我们须要给函数安插一种明白的“标识”,告诉编译器:这个函数具有这种特征。所以特质PartialFunction就被创建出来用于“标记”这类函数的,这个特质最基本的方法就是isDefinedAt。同一时候你也记得PartialFunction还是Function1的子类。所以它也要有apply方法,这是很自然的。偏函数本身首先是一个函数嘛。

    从还有一个角度思考,偏函数的逻辑是能够通过普通函数去实现的。仅仅是偏函数是更为优雅的一种方式。同一时候偏函数特质PartialFunction的存在对调用方和实现方都是一种语义更加丰富的约定,比方collect方法声明使用一个偏函数就暗含着它不太可能对每个元素进行操作,它的返回结果仅仅是针对偏函数“感兴趣”的元素计算出来的

    为什么偏函数仅仅能有一个參数?

    为什么仅仅有针对单一參数的偏函数。而不是像Function特质那样,拥有多个版本号的PartialFunction呢?在刚刚接触偏函数时,这也让我感到费解,但看透了偏函数的实质之后就会认为很合理了。

    我们说所谓的偏函数本质上是由多个case语句组成的针对每一种可能的參数分别进行处理的一种“结构较为特殊”的函数,那特殊在什么地方呢?对,就是case语句,前面我们提到,case语句声明的变量就是偏函数的參数,既然case语句仅仅能声明一个变量,那么偏函数受限于此,也仅仅能有一个參数!

    说究竟,类型PartialFunction无非是为由一组case语句描写叙述的函数字面量提供一个类型描写叙述而已,case语句仅仅接受一个參数。则偏函数的类型声明自然就仅仅有一个參数。

    但是,上这并不会对编程造成什么阻碍,假设你想给一个偏函数传递多个參数。全然能够把这些參数封装成一个Tuple传递过去!

  • 相关阅读:
    luoguP3181 [HAOI2016]找相同字符
    luoguP4248 [AHOI2013]差异
    luoguP2852 [USACO06DEC]Milk Patterns
    后缀自动机
    luoguP3975 [TJOI2015]弦论
    luoguP2824 [HEOI2016/TJOI2016]排序(线段树分裂做法)
    组合数学学习笔记
    「题解」:[BZOJ2938]病毒 (AC自动机+dfs)
    Linux新人报到
    指针学习笔记
  • 原文地址:https://www.cnblogs.com/yutingliuyl/p/7148906.html
Copyright © 2011-2022 走看看