scala读书学习笔记005

1、由于Scala的操作标识符支持任意长度,Java跟Scala在这里有个细微的差异。在Java中,x<-y这样的代码会被解析成四个语法符号,等同于 x < - y。而在Scala中,<-会被解析成一个语法符号,所以给出的解析结果是 x<- y。如果你想要的效果是前一种,需要用空格将<和-分开

2、Scala所有的控制结构都返回某种值作为结果,scala的if可以有返回值。Scala更进一步让for、try和match也都有返回值

3、在任何你打算写变量名的地方通过“file<- filesHere”这样的生成器(generator)语法,我们将遍历filesHere的元素。每一次迭代,一个新的名为file的val都会被初始化成一个元素的值。

5、表达式之所以被称作“表达式”,是因为它能返回有意义的值,一个类型可以由for表达式的<-子句决定的集合,都可以直接用表达式来替换。

6、Scala提供了好几种Java中没有的方式来定义函数。除了方法(即那些以某个对象的成员形式存在的函数),还有嵌套函数、函数字面量和函数值等

7、可以在某个函数内部定义函数。就像局部变量一样,这样的局部函数只在包含它的代码块中可见

8、使用外层函数的参数是Scala提供的通用嵌套机制的常见而有用的示例

9、Scala支持一等函数。不仅可以定义函数并调用它们,还可以用匿名的字面量来编写函数并将它们作为值(value)进行传递,函数字面量被编译成类,并在运行时实例化成函数值(function value)。[2]因此,函数字面量和函数值的区别在于,函数字面量存在于源码,而函数值以对象形式存在于运行时。这跟类(源码)与对象(运行时)的区别很相似,函数字面量被编译成类,并在运行时实例化成函数值(function value)。[2]因此,函数字面量和函数值的区别在于,函数字面量存在于源码,而函数值以对象形式存在于运行时。这跟类(源码)与对象(运行时)的区别很相似

10、如果你想要在函数字面量中包含多于1条语句,可以将函数体用花括号括起来,每条语句占一行,组成一个代码块(block)。跟方法一样,当函数值被调用时,所有的语句都会被执行,并且该函数的返回值就是对最后一个表达式求值的结果

11、为了让函数字面量更加精简,还可以使用下画线作为占位符,用来表示一个或多个参数,只要满足每个参数只在函数字面量中出现一次即可。例如,_ > 0

12、_ + _将会展开成一个接收两个参数的函数字面量。这就是为什么只有当每个参数在函数字面量中出现不多不少正好一次的时候才能使用这样的精简写法。多个下画线意味着多个参数,而不是对单个参数的重复使用。第一个下画线代表第一个参数,第二个下画线代表第二个参数,第三个下画线代表第三个参数,以此类推。

13、这里的下画线并非是单个参数的占位符,它是整个参数列表的占位符。注意你需要保留函数名和下画线之间的空格,否则编译器会认为你引用的是另一个符号,比如一个名为println_的方法,这个方法很可能并不存在。
当你这样使用下画线时,实际上是在编写一个部分应用的函数。

14、Scala用于表示部分应用函数的语法体现了Scala在设计取舍方面跟其他经典函数式编程语言(比如Haskell或ML)的区别。在这些函数式语言当中,部分应用函数被当作默认的用法。不仅如此,这些语言拥有非常严格的静态类型系统,通常对于你在做部分应用时会犯的每一种错误都有明确的提示。Scala在这方面跟指令式编程语言(比如Java)更为接近,对于那些没有给出全部参数的方法,都认为是错误。还有,面向对象的传统的子类型和全局公共的根类型等特性,允许某些在经典的函数式编程语言看来是有问题的程序通过编译。

15、运行时从这个函数字面量创建出来的函数值(对象)被称作闭包(closure)。该名称源于“捕获”其自由变量从而“闭合”该函数字面量的动作。没有自由变量的函数字面量,比如(x: Int) => x + 1,称为闭合语(closed term),这里的语(term)指的是一段源代码。因此,运行时从这个函数字面量创建出来的函数值严格来说并不是一个闭包,因为(x: Int) => x + 1按照目前这个写法已经是闭合的了。而运行时从任何带有自由变量的函数字面量,比如(x: Int) => x + more,创建的函数值,按照定义,要求捕获到它的自由变量more的绑定。相应的函数值结果(包含指向被捕获的more变量的引用)就被称作闭包,因为函数值是通过闭合这个开放语(open term)的动作产生的。Scala的闭包捕获的是变量本身,而不是变量引用的值,反过来也是成立的:闭包对捕获到的变量的修改也能在闭包外被看到。
如果一个闭包访问了某个随着程序运行会产生多个副本的变量会如何呢?例如,如果一个闭包使用了某个函数的局部变量,而这个函数又被调用了多次,会怎么样?闭包每次访问到的是这个变量的哪一个实例呢?只有一个答案是跟Scala其他组成部分是一致的:闭包引用的实例是在闭包被创建时活跃的那一个。

16、scala编译器能够检测到尾递归并将它替换成跳转到函数的最开始,并在跳转之前将参数更新为新的值。

17、在Scala中使用尾递归是比较受限的,因为用JVM指令集实现更高级形式的尾递归非常困难。Scala只能对那些直接尾递归调用自己的函数做优化。

18、所有的函数都能被分解成每次函数调用都一样的公共部分和每次调用不一样的非公共部分。公共部分是函数体,而非公共部分必须通过实参传入。当你把函数值当作入参的时候,这段算法的非公共部分本身又是另一个算法!每当这样的函数被调用,你都可以传入不同的函数值作为实参,被调用的函数会(在由它选择的时机)调用传入的函数值。这些高阶函数(higher-order function),即那些接收函数作为参数的函数,让你有额外的机会来进一步压缩和简化代码。

19、match:(String, String) => Boolean,它首先是个函数,因此在类型声明中有个=>符号。这个函数接收两个字符串类型的参数(分别是文件名和查询条件),返回一个布尔值
20、高阶函数可以帮助我们在实现API时减少代码重复,高阶函数的另一个重要的用处是将高阶函数本身放在API当中来让调用方代码更加精简。

21、scala中exists方法代表了一种控制抽象。这是Scala类库提供的一个特殊用途的循环结构,并不是像while或for那样是语言内建的。Scala类库当中还有许多其他循环方法。跟exists一样,它们通常能帮助你缩短代码。

22、函数式编程技巧,那就是柯里化(currying),一个经过柯里化的函数在应用时支持多个参数列表,而不是只有一个。val second = first(1) second: Int => Int = <function1>,这里的first和second函数只是对柯里化过程的示意,它们跟curriedSum并不直接相关。尽管如此,我们还是有办法获取到指向curriedSum的“第二个”函数的引用。这个办法就是通过占位符表示法,在一个部分应用函数表达式中使用curriedSum,就像这样:vak = one Plus = curriedSum(1)_onePlus:Int = <function1>

23、每当你发现某个控制模式在代码中多处出现时,就应该考虑将这个模式实现为新的控制结构。

24、使用这个方法的好处是,确保文件在最后被关闭的是withPrintWriter,而不是用户代码。因此不可能出现使用者忘记关闭文件的情况。这个技巧被称作贷出模式(loan pattern),因为是某个控制抽象函数,比如withPrintWriter,打开某个资源并将这个资源“贷出”给函数op。当函数完成时,它会表明自己不再需要这个“贷入”的资源。这时这个资源就在finally代码块中被关闭了,这样能确保不论函数是正常返回还是抛出异常,资源都会被正常关闭。
可以用花括号而不是圆括号来表示参数列表,这样调用方的代码看上去更像是在使用内建的控制结构。在Scala中,只要有那种只传入一个参数的方法调用,都可以选择使用花括号来将入参包起来,而不是圆括号。Scala允许用花括号替代圆括号来传入单个入参的目的是,为了让调用方程序员在花括号当中编写函数字面量。这能让方法用起来更像是控制抽象。

25、没有使用传名参数:def myAssert(predicate:() => Boolean), 对传名(by-name)类型而言,空的参数列表,即(),是去掉的,这样的类型只能用于参数声明,并不存在传名变量或传名字段。
def byNameAssert(predicate:=> Boolean)和def boolAssert(predicate:Boolean)的区别:
这两种方式有一个显著的区别需要注意。由于boolAssert的参数类型为Boolean,在boolAssert(5 > 3)圆括号中的表达式将“先于”对boolAssert的调用被求值。而由于byNameAssert的predicate参数类型是=> Boolean,在byNameAssert(5 > 3)的圆括号中的表达式在调用byNameAssert之前并不会被求值,而是会有一个函数值被创建出来,这个函数值的apply方法将会对5 > 3求值,传入byNameAssert的是这个函数值。
因此,两种方式的区别在于如果断言被禁用,你将能够观察到boolAssert的圆括号当中的表达式的副作用,而用byNameAssert则不会。
传名参数()和传值参数(def callByValue(x: Int)):在Scala中,当参数通过传值调用函数时,它会在调用函数之前计算一次传入的表达式或参数值。但是使用传名调用函数时,在函数内部访问参数时会重新计算传入表达式的值。这里的示例显示了它们的差异和语法。也就说传名参数被函数使用时会被重新计算且可能会被计算多次。
Scala中的call by name仅在调用方支持,而lazy仅在定义方支持。
传名参数 call by name
参数几乎只是以调用函数时的任何(未计算)形式替换到函数体中。这意味着它可能需要在体内多次计算。
按需调用 call by need
在调用函数时不会计算参数,但第一次使用时需要,然后值被缓存。之后,每当再次需要该参数时,都会查找缓存的值。scala中使用lazy val可以做到这点
传值参数 call by value
调用函数时会计算参数,即使函数最终没有使用这些参数。


26、一个经过柯里化的函数在应用时支持多个参数列表,而不是只有一个,没有经过柯里化的函数:def plainOldSum(x:Int,y:Int) = x+Y
经过柯里化的方式1:def curriedSum(x:Int)(y:Int) = x+y,这次是经过柯里化的。跟使用一个包含两个Int参数列表不同,应用这个函数到两个参数列表,每个列表包含一个Int参数。柯里化方式2:def first(x:Int) = (y:Int) => x+y

27、组合和继承。组合的意思是一个类可以包含对另一个类的引用,利用这个被引用类来帮助它完成任务;而继承是超类/子类的关系。

28、柯里化技术在提高适用性还是在延迟执行或者固定易变因素等方面有着重要重要的作用,加上scala语言本身就是推崇简洁编码,使得同样功能的函数在定义与转换的时候会更加灵活多样。柯里化可以将参数间解耦,并且支持更多的语法,最终是将函数完全变成「接受一个参数;返回一个值」的固定形式,这样对于讨论和优化会更加方便。

29、在scala柯里化中,闭包也发挥着重要的作用。所谓的闭包就是变量出了函数的定义域外在其他代码块还能其作用,这样的情况称之为闭包。就上述讨论的案例而言,如果没有闭包作用,那么转换后函数其实返回的匿名函数是无法在与第一个参数。

30、用组合子来思考通常是一个设计类库的好办法:对于某个特定的应用领域中对象,它们有哪些基本的构造方式,这样的思考是很有意义的。简单的对象如何构造出更有趣的对象?如何将组合子有机地结合在一起?最通用的组合有哪些?它们是否满足某种有趣的法则?如果对这些问题你都有很好的答案,那么你的类库设计就走在正轨上。

31、修饰符abstract表明该类可以拥有那些没有实现的抽象成员,contents被声明为一个没有实现的方法。换句话说,这个方法是Element类的抽象(abstract)成员。一个包含抽象成员的类本身也要声明为抽象的,做法是在class关键字之前写上abstract修饰符。Element类中的content方法并没有标上abstract修饰符。一个方法只要没有实现(即没有等号或方法体),那么它就是抽象的。跟Java不同,我们并不需要(也不能)对方法加上abstract修饰符。那些给出了实现的方法叫作具体(concrete)方法。
另一组在叫法上的区分是声明(declaration)和定义(definition)。Element类声明了content这个抽象方法,但目前并没有定义具体的方法。

32、推荐的做法是对于没有参数且只通过读取所在对象字段的方式访问可变状态(确切地说不改变状态)的情况下尽量使用无参方法。这样的做法支持所谓的统一访问原则,使用方代码不应受到某个属性是用字段还是用方法实现的影响。唯一的区别是字段访问可能比方法调用略快,因为字段值在类初始化时就被预先计算好,而不是在每次方法调用时都重新计算。另一方面,字段需要每个Element对象为其分配额外的内存空间。因此属性实现为字段好还是方法好,这个问题取决于类的用法,而用法可以是随着时间变化而变化的。核心点在于Element类的使用方不应该被内部实现的变化所影响。

33、Scala鼓励我们将那些不接收参数也没有副作用的方法定义为无参方法(即省去空括号)。同时,对于有副作用的方法,不应该省去空括号,因为省掉括号以后这个方法调用看上去就像是字段选择,因此你的使用方可能会对其副作用感到意外。
同理,每当你调用某个有副作用的函数,请确保在写下调用代码时加上空括号。

34、Scala编译器会默认假定你的类扩展自scala.AnyRef,这对应到Java平台跟java.lang.Object相同。继承(inheritance)的意思是超类的所有成员也是子类的成员,但是有两个例外:一是超类的私有成员并不会被子类继承;二是如果子类里已经实现了相同名称和参数的成员,那么该成员不会被继承。对后面这种情况我们也说子类的成员重写(override)了超类的成员。如果子类的成员是具体的而超类的成员是抽象的,我们也说这个具体的成员实现(implement)了那个抽象的成员。

35、ArrayElement和Array[String]之间存在的组合(composition)关系。这个关系被称为组合,因为ArrayElement用一个Array[String]组合出来,Scala编译器会在为ArrayElement生成的二进制类文件中放入一个指向传入的conts数组的字段

36、原类定义Class ArrayElement(conts:Array[String]) extend Element,采用参数化字段定义 Class ArrayElement(val contents:Array[String]) extend Element,现在contents参数前面放了一个val。这是同时定义参数和同名字段的简写方式。具体来说,ArrayElement类现在具备一个(不能被重新赋值的)contents字段。该字段可以被外界访问到。该字段被初始化为参数的值。

37、Scala要求我们在所有重写了父类具体成员的成员之前加上这个修饰符。而如果某个成员并不重写或继承基类中的某个成员,这个修饰符则是被禁用的。override的规约对于系统进化来说更为重要。

38、scala中被调用的方法实现是在运行时基于对象的类来决定的,而不是变量或表达式的类型决定的。在设计类继承关系的过程中,你想确保某个成员不能被子类继承。在Scala中,跟Java一样,可以通过在成员前面加上final修饰符来实现,有时候还想要确保整个类没有子类,可以简单地将类声明为final的,做法是在类声明之前添加final修饰符。

39、如果你主要追求的是代码复用,一般来说应当优先选择组合而不是继承。只有继承才会受到脆弱基类问题的困扰,会在修改超类时不小心破坏了子类的代码。
关于继承关系,你可以问自己一个问题,那就是要建模的这个关系是否是is-a(是一个)的关系。[8]例如,我们有理由说ArrayElement是一个Element。另一个可以问的问题是这些类的使用方是否会把子类的类型当作超类的类型来使用。for表达式有一个部分叫作yield,通过yield交出结果。这个结果的类型和被遍历的表达式是同一种(也就是数组)。数组中的每个元素都是将对应的行line1和line2拼接起来的结果。因此这段代码的最终结果跟第一版的beside一样,不过由于它避免了显式的数组下标,获取结果的过程更少出错。

40、工厂对象包含创建其他对象的方法。使用方用这些工厂方法来构造对象,而不是直接用new构建对象。这种做法的好处是对象创建逻辑可以被集中起来,而对象是如何用具体的类表示的可以被隐藏起来。这样既可以让你的类库更容易被使用方理解,因为暴露的细节更少,同时还提供了更多的机会让你在未来在不破坏使用方代码的前提下改变类库的实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值