36个谜题!深入理解Scala的有趣途径
Posted 异步图书
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了36个谜题!深入理解Scala的有趣途径相关的知识,希望对你有一定的参考价值。
点击标题下【异步图书】可快速关注
谜题很有趣。我还记得第一次遇到Java语言的“谜题”的情景。Neal Gafter给我看了他和Josh Bloch刚收集的一道Java谜题。那时候,Neal正在维护我编写的javac编译器。这道题太难了,我猜错了几次,让Neal乐坏了。
今天我将隆重介绍一本Scala的新书——《Scala谜题》
很高兴现在有一本书能在Scala中延续谜题的传统。本书将为读者展示36个谜题,之所以称为谜题,是因为它们会产生令人意外的结果,有特性间的交互作用或者有不同于表面的代码运行结果。这些谜题是从Scala社区几年来广泛的输入中收集而来的。
Andrew和Nermin提炼出每个谜题的本质,使其易于理解。在享受了从一系列选项中选出正确答案的乐趣之后,每道题后面会告诉你为什么会这样,什么原因导致了这个令人意外的结果?这正是本书真正的亮点,因为它对观察到的程序行为的深层原理进行了清晰地讲解。我特别喜欢本书的一点是这些讲解总是给你带来全新的视角。它们不仅告诉你令人意外的行为的趣事,而且集Scala深入讲解于一体。这样,谜题就会有助于你更深入地理解这门语言。
谁适合读?
本书适合于对Scala感兴趣的开发者、对JVM平台上的语言以及函数式编程感兴趣的程序员阅读。
本书主要内容
让代码做我们希望它做的事,是作为一个开发者的基本目的。所以没有什么比我们自认为理解的一段代码表现出与我们期望相反的行为更迷人、更重要的了。
本书汇集了一些Scala的例子。这不仅是以一种寓教于乐的方式来更好地理解这门语言,而且帮助你认识许多反直观的雷区和陷阱,防止它们在生产系统中干扰到你。
全书共有36章,具体内容如下。
第1章“使用占位符”,本章中,我们通过引入占位符语法从函数调用中删除了一些陈词滥调。
第2章“初始化变量”,本章中,我们检查了Scala里给多个变量赋值的盛衰起伏。
第3章“成员声明的位置”,本章我们审视了类成员的周边环境对初始化的影响。
第4章“继承”,本章我们通过类初始化顺序跟踪几个抽象和重载值。
第5章“集合操作”,这一章我们试图计数几个集合而不用太担心它们确切的类型。
第6章“参数类型”,本章我们通过定义两种特定类型的帮助函数的通用版本尽量写出好的函数式程序。
第7章“闭包”,本章对几个延迟的访问器函数上的索引数据序列略窥一二。
第8章“Map表达式”,本章我们用for表达式并通过map的应用来转换一些字母汤。
第9章“循环引用变量”,本章我们定义并随机地访问一对相互引用的变量。
第10章“等式的例子”,本章我们在case class的hashCode方法中混进调试代码。
第11章“lazy val”,本章我们试图初始化一个随环境而变化的lazy值。
第12章“集合的迭代顺序”,本章我们试图按值的顺序输出罗马数字。
第13章“自引用”,本章我们会查看Scala如何处理自引用变量。
第14章“Return语句”,本章我们将体验程序流控制。
第15章“偏函数中的_”,本章我们调用两个偏应用函数作为计数器。
第16章“多参数列表”,本章我们给偏函数提供不同数量的参数。
第17章“隐式参数”,本章我们从一个有隐式参数的方法创建一个偏应用函数。
第18章“重载”,本章我们定义并调用参数数量可变的重载方法。
第19章“命名参数和缺省参数”,本章我们分别调用一个指定和没有指定参数名的方法。
第20章“正则表达式”,本章我们从正则匹配中删除一些调试代码。
第21章“填充”,本章我们每次用一个“*”字符按希望的长度填充一个字符串。
第22章“投影”,本章我们将一个从调用Java代码(gulp!)返回的map投影到一个Scala类型的map。
第23章“构造器参数”,本章我们实例化一个类的多个实例,这个类的构造器是由按名字传递的参数指定的。
第24章“Double .NaN”,本章我们对几个double数组进行排序。
第25章“getOrElse”,本章我们组合两个列表,当试图从结果中抽取一个元素时就有些令人迷惑了。
第26章“Any Args”,本章我们重构一个方法,当调用方法的代码忘记改变时,用多个参数列表的方式调用重构的方法依然可以成功。
第27章“null”,本章我们对一个从调用Java代码返回的字符串应用模式匹配。
第28章“AnyVal”,本章我们初始化一个其特定的类型在子类中声明的AnyVal子类型。
第29章“隐式变量”,本章我们通过导入附加的隐式变量的方式在程序中间创建一个“测试模式”上下文。
第30章“显式声明类型”,本章我们定义两种隐式转换,让编译器去弄清楚它们的返回类型。
第31章“View”,本章我们优化一个只能在map数据项的值上操作的map调用。
第32章“toSet”,本章我们在增加附加的元素之前将一个列表转换成一个集合。
第33章“缺省值”,本章我们使用两种不同的方式为map数据项指定缺省值。
第34章“关于Main”,本章我们对一个用显式main方法的“保守”对象进行重构,把它修改成继承App特质的方法。
第35章“列表”,本章我们使用圆括号和大括号定义一个匿名函数。
第36章“计算集合的大小”,本章我们重构一个计数方法让它可以处理通用的集合类型。
如何阅读本书
本书中的谜题并非按特定顺序编写。你可以随机打开一个谜题阅读,会与从头到尾地读一样轻松。
如果你对Scala语言的特定方面感兴趣并且在寻找相关的谜题,本书最后的主题索引正是为你准备的。我们尽量根据读者要探索的主题对谜题进行了分类。
谜题中所有的代码例子都要用2.11 Scala REPL解释执行,最近的一些变更,例如一些不支持的写法会让程序行为与Scala 2.10.X版本稍微不同,我们已经增加了注释或注脚。
谜题概览
1.使用占位符 1
2.初始化变量 5
3.成员声明的位置 9
4.继承 14
5.集合操作 21
6.参数类型 24
7.闭包 29
8.Map表达式 33
9.循环引用变量 37
10.等式的例子 44
11.lazy val 51
12.集合的迭代顺序 54
13.自引用 58
14.Return语句 62
15.偏函数中的_ 67
16.多参数列表 73
17.隐式参数 78
18.重载 83
19.命名参数和缺省参数 88
20.正则表达式 93
21.填充 97
22.投影 101
23.构造器参数 106
24.Double.NaN 111
25.getOrElse 116
26.Any Args 120
27.null 124
28.AnyVal 129
29.隐式变量 135
30.显式声明类型 141
31.View 145
32.toSet 148
33.缺省值 154
34.关于Main 159
35.列表 165
36.计算集合的大小 169
试读:第1章 使用占位符
Scala特别强调要书写简单、简洁的代码。匿名函数的语法arg => expr
,使它很容易用最小模板构建函数字面量,甚至函数由多个语句组成时也一样可以。
用有自解释参数的函数还可以做得更好,而且还可以用占位符语法。占位符语法可以省去参数声明。例如:
List(1,2).map { i => i + 1 }
用占位符语法,就变成:
List(1,2).map { _ + 1 }
以下两个语句是等价的:
scala> List(1,2).map { i => i + 1 }
res1:List[Int] = List(2,3)scala>
List(1,2).map { _ + 1 }
res0:List[Int] = List(2,3)
如果你给以上简单的例子增加调试语句会如何呢?这有助于你理解函数是什么时候应用的。让我们看看在REPL中执行以下代码会是什么结果。
List(1,2).map { i => println("Hi");i + 1 }
List(1,2).map { println("Hi"); _ + 1 }
可能的结果
1.打印出:
Hi
List[Int] = List(2,3)
Hi
List[Int] = List(2,3)
2.打印出:
Hi
Hi
List[Int] =
List(2,3)
Hi
Hi
List[Int] = List(2,3)
3.打印出:
Hi
Hi
List[Int] =
List(2,3)
Hi
List[Int] =
List(2,3)
4.第一个语句打印出:
Hi
Hi
List[Int] =
List(2,3)
第二个语句编译失败。
解释
你无需关心编译错误,因为代码编译没有问题。然而程序并没有表现出你期望的结果。正确的答案是3:
scala> List(1, 2).map { i => println("Hi"); i + 1 }
Hi
Hires23:
List[Int] =
List(2,3)scala>
List(1, 2).map { println("Hi"); _ + 1 }
Hires25:
List[Int] = List(2,3)
怎么会是这样呢?如果这个有显式参数的函数打印“Hi”两次,因为它会对列表中的每个元素都调用一次函数,那么为什么使用占位符语法的函数不能有同样的结果呢?
因为匿名函数常常被当作参数传递,在代码中往往会看到它们在花括号{ ... }
里,就很容易认为这些花括号表示一个匿名函数。但是,实际上它们只是界定了一个块表达式,一个或多个表达式最后决定了这个块的结果。
两个代码块的解析方式决定了它们有不同的行为。第一个语句{ i => println("Hi"); i + 1 }
被当成一个arg => expr
形式的函数字面量表达式,这里的表达式是块println("Hi"); i + 1
。因为println语句是函数体的一部分,所以每次调用函数就要执行一次。
scala> val printAndAddOne =(i: Int) =>
{println("Hi"); i + 1 }
printAndAddOne: Int => Int =
scala> List(1, 2).map(printAndAddOne)
Hi
Hi
res29: List[Int] = List(2, 3)
第二个表达式中,代码块被认为是println("Hi")``和``_+ 1
两个表达式。当这个代码块执行的时候,将最后一个表达式(便利性所需的函数类型,Int => Int)
传递给map
。其中的println语句不是函数体的一部分,它是在map
的参数评估时被调用的,而不是作为map
的一部分执行。
scala> val printAndReturnAFunc = { println("Hi"); (_: Int) + 1 }
Hi
printAndReturnAFunc: Int => Int = scala>
List(1, 2).map(printAndReturnAFunc)res30:
List[Int] = List(2, 3)
讨论
这里学到的关键一点是:用占位符语法定义的匿名函数的范围只延伸到含有下划线(_
)的表达式。这不同于常规的匿名函数,常规的匿名函数的函数体是包含从箭头标识符(=>
)一直到代码块结束的所有代码。请看下面这个例子:
scala> val regularFunc = { a: Any => println("foo"); println(a); "baz" }regularFunc: Any => String = scala> regularFunc("hello")foo hello res42: String = baz
而使用占位符语法的函数被封闭在它自己的代码块里。例如,下面两个函数是等价的:
scala> val anonymousFunc = { println("foo"); println(_: Any); "baz" }foo anonymousFunc: String = baz scala> val confinedFunc = { println("foo"); { a: Any => println(a) }; "baz" }foo confinedFunc: String = baz
Scala鼓励简洁的代码,但太简洁时就会出现这样的情况。使用占位符语法时一定要注意由它所创建的函数范围。
延伸推荐
点击关键词阅读更多新书:
||||||
点击阅读原文,试读《Scala谜题》
以上是关于36个谜题!深入理解Scala的有趣途径的主要内容,如果未能解决你的问题,请参考以下文章