每天半小时掌握Scala(day 03)

Posted 爱上终身学习

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每天半小时掌握Scala(day 03)相关的知识,希望对你有一定的参考价值。

===函数()===

 

函数与操作符

    从技术层面上来说,scala没有操作符重载因为它根本没有传统意义上的操作符诸如“+”、“-”、“*”、“/”这样的操作符其实调用的是方法方法被当作操作符使用时根据使用方式的不同可以分为中缀标注操作符)、前缀标注后缀标注

    中缀标注中缀操作符左右分别有一个操作数方法若只有一个参数实际上是两个参数因为有一个隐式的this),调用的时候就可以省略点及括号实际上如果方法有多个显式参数也可以这样做只不过你需要把参数用小括号全部括起来如果方法被当作中缀操作符来使用也即省略了点及括号),那么左操作数是方法的调用者除非方法名以冒号“:”结尾此时方法被右操作数调用)。另外,scala的中缀标注不仅可以在操作符中存在也可以在模式匹配类型声明中存在参见相应部分

    前缀标注前缀操作符只有右边一个操作数但是对应的方法名应该在操作符字符上加上前缀“unary_”。标识符中能作为前缀操作符用的只有+、-、!~。

    后缀标注后缀操作符只有左边一个操作数任何不带显式参数的方法都可以作为后缀操作符 

    scala函数的定义方式除了作为对象成员函数的方法之外还有内嵌在函数中的函数函数字面量和函数值

 

嵌套定义的函数

    嵌套定义的函数也叫本地函数本地函数仅在包含它的代码块中可见

 

函数字面量

scala你不仅可以定义和调用函数还可以把它们写成匿名的字面量也即函数字面量并把它们作为值传递函数字面量被编译进类并在运行期间实例化为函数值任何函数值都是某个扩展了scala包的若干FunctionN特质之一的类的实例Function0是没有参数的函数,Function1是有一个参数的函数等等每一个FunctionN特质有一个apply方法用来调用函数)。因此函数字面量和值的区别在于函数字面量存在于源代码中而函数值作为对象存在于运行期这个区别很像类源代码和对象运行期之间的关系

以下是对给定数执行加一操作的函数字面量

(x: Int) => x + 1

    其中,=>指出这个函数把左边的东西转变为右边的东西=>右边你也可以使用{}来包含代码块

    函数值是对象因此你可以将其存入变量中这些变量也是函数你可以使用通常的括号函数调用写法调用它们

val fun = (x: Int) => x + 1

val a = fun(5)

    有时,scala编译器可以推断出函数字面量的参数类型因此你可以省略参数类型然后你也可以省略参数外边的括号

(x) => x + 1

x => x + 1

    如果想让函数字面量更简洁可以把通配符“_”当作单个参数的占位符如果遇见编译器无法识别参数类型时“_”之后加上参数类型声明即可

List(1,2,3,4,5).filter(_ > 3)

val fun = (_: Int) + (_: Int)

 

部分应用函数

    你还可以使用单个“_”替换整个参数列表例如可以写成

                List(1,2,3,4,5).foreach(println(_))

    或者更好的方法是你还可以写成

List(1,2,3,4,5).foreach(println _)

    以这种方式使用下划线时你就正在写一个部分应用函数部分应用函数是一种表达式你不需要提供函数需要的所有参数代之以仅提供部分或不提供所需参数如下先定义一个函数然后创建一个部分应用函数并保存于变量然后该变量就可以作为函数使用

def sum(a: Int, b: Int, c: Int) = a + b + c

val a = sum _

println(a(1,2,3))

    实际发生的事情是这样的名为a的变量指向一个函数值对象这个函数值是由scala编译器依照部分应用函数表达式sum _,自动产生的类的一个实例编译器产生的类有一个apply方法带有3个参数之所以带3个参数是因为sum _表达式缺少的参数数量为3),然后scala编译器把表达式a(1,2,3)翻译成对函数值的apply方法的调用你可以使用这种方式把成员函数和本地函数转换为函数值进而在函数中使用它们不过你还可以通过提供某些但不是全部需要的参数表达一个部分应用函数如下此变量在使用的时候可以仅提供一个参数

val b = sum(1, _: Int, 3)

    如果你正在写一个省略所有参数的部分应用函数表达式println _sum _,而且在代码的那个地方正需要一个函数你就可以省略掉下划线不是需要函数的地方你这样写编译器可能会把它当作一个函数调用因为在scala调用无副作用的函数时默认不加括号)。如下代码就是

List(1,2,3,4,5).foreach(println)

 

闭包

    闭包是可以包含自由未绑定到特定对象变量的代码块这些变量不是在这个代码块内或者任何全局上下文中定义的而是在定义代码块的环境中定义局部变量)。比如说在函数字面量中使用定义在其外的局部变量这就形成了一个闭包如下代码foreach中就创建了一个闭包

var sum = 0

List(1,2,3,4,5).foreach(x => sum += x)

scala闭包捕获了变量本身而不是变量的值变量的变化在闭包中是可见的反过来若闭包改变对应变量的值在外部也是可见的

 

尾递归

    递归调用这个动作在最后的递归函数叫做尾递归。scala编译器可以对尾递归做出重要优化当其检测到尾递归就用新值更新函数参数然后把它替换成一个回到函数开头的跳转

    你可以使用开关“-g:notailcalls”关掉编译器的尾递归优化

别高兴太早,scala里尾递归优化的局限性很大因为jvm指令集使实现更加先进的尾递归形式变得困难尾递归优化限定了函数必须在最后一个操作调用本身而不是转到某个函数值或什么其他的中间函数的情况

scala你不要刻意回避使用递归相反你应该尽量避免使用whilevar配合实现的循环

 

高阶函数

    带有其他函数作为参数的函数称为高阶函数

 

柯里化

    柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数并且返回接受余下的参数且返回结果的新函数的技术如下就是一个柯里化之后的函数

def curriedSum(x: Int)(y: Int) = x + y

    这里发生的事情是当你调用curriedSum实际上接连调用了两个传统函数第一个调用的函数带单个名为x的参数并返回第二个函数的函数值这个被返回的函数带一个参数y,并返回最终计算结果你可以使用部分应用函数表达式方式来获取第一个调用返回的函数也即第二个函数如下

val onePlus = curriedSum(3)_

    高阶函数柯里化配合使用可以提供灵活的抽象控制更进一步当函数只有一个参数时在调用时你可以使用花括号代替小括号,scala支持这种机制其目的是让客户程序员写出包围在花括号内的函数字面量从而让函数调用感觉更像抽象控制不过需要注意的是花括号也就是块表达式因此你可以在其中填写多个表达式但是最后一个表达式的值作为该块表达式的值并最终成为了函数参数如果函数有两个以上的参数那么你可以使用柯里化的方式来实现函数

 

传名参数

    对于如下代码,myAssert带有一个函数参数该参数变量的类型为不带函数参数的函数类型

myAssert(predicate: () => Boolean) = {

if(!predicate())

throw new AssertionError

}

    在使用时我们需要使用如下的语法

myAssert(() => 5 > 3)

    这样很麻烦我们可以使用如下称之为传名参数的语法简化之

myAssert(predicate: => Boolean) = {

if(!predicate)

throw new AssertionError

}

    以上代码在定义参数类型时是以“=>”开头而不是“() =>”,并在调用函数通过函数类型的变量不带“()”。现在你就可以这样使用了

myAssert(5 > 3)

    其中,“predicate: => Boolean说明predicate是函数类型在使用时传入的是函数字面量注意与predicate: Boolean的不同后者predicateBoolean类型的(表达式)。

 

偏函

    偏函数部分应用函数是无关的偏函数是只对函数定义域的一个子集进行定义的函数。 scala中用scala.PartialFunction[-T, +S]来表示偏函数主要用于这样一种场景对某些值现在还无法给出具体的操作即需求还不明朗),也有可能存在几种处理方式视乎具体的需求),我们可以先对需求明确的部分进行定义以后可以再对定义域进行修改。PartialFunction中可以使用的方法如下

isDefinedAt判断定义域是否包含指定的输入

orElse补充对其他域的定义

compose组合其他函数形成一个新的函数假设有两个函数fg,那么表达式f _ compose g _则会形成一个f(g(x))形式的新函数你可以使用该方法对定义域进行一定的偏移

         andThen将两个相关的偏函数串接起来调用顺序是先调用第一个函数然后调用第二个假设有两个函数fg,那么表达式f _ andThen g _则会形成一个g(f(x))形式的新函数刚好与compose相反 



每天向你推送最实用的干货,欢迎关注~

点击这里查看往期精彩内容:


以上是关于每天半小时掌握Scala(day 03)的主要内容,如果未能解决你的问题,请参考以下文章

每天半小时掌握Scala(day 04)

每天半小时掌握Scala(day 01)

5个能够改变一生的自学网站,每天半小时效果看的见!

linux学习之路每天半小时

每天半小时下班的公交车上,把html5学完了...

自动化快速上手--Python--元组--每天半小时