![Scala编程(第5版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/446/43738446/b_43738446.jpg)
第12步 用map方法和for-yield变换
在指令式编程风格中,可以当场改变数据结构直到达成算法的目标。而在函数式编程风格中,需要把不可变的数据结构变换成新的数据结构来达成目标。
不可变集合的一个重要的用于实现函数式变换的方法是map。与foreach方法相同,map方法也接收一个函数作为参数。与foreach方法不同的是,foreach方法用传入的函数对每个元素执行副作用,而map方法用传入的函数将每个元素变换成新的值。举例来说,如下字符串的列表:
![](https://epubservercos.yuewen.com/9944D7/23020655409775506/epubprivate/OEBPS/Images/42832-00-082-1.jpg?sign=1739428840-JeBKdbyONj5ylguPppbLKjoJFZZdfSqr-0-e92f9ea52d200fb3641881ddc5bf9b75)
可以像这样将它变换成由新的字符串组成的新列表:
![](https://epubservercos.yuewen.com/9944D7/23020655409775506/epubprivate/OEBPS/Images/42832-00-083-1.jpg?sign=1739428840-f69BnqXigy2K5piM3tU15DAqcDf9i1Dz-0-53e1fd4171f9079ad99eab4b7bd70f76)
另一种执行这个变换的方式是使用for表达式。可以通过yield关键字(而不是do)来引入需要执行的代码体:
![](https://epubservercos.yuewen.com/9944D7/23020655409775506/epubprivate/OEBPS/Images/42832-00-083-2.jpg?sign=1739428840-gBEg3IuQPeAWtTmokKtHBo2KPhRCzhuI-0-76fe973646433fe2f14ea541c1cfdb55)
for-yield生成的结果与map方法生成的结果完全一样,因为编译器会把for-yield表达式变换成map方法调用。[11]由于map方法返回的列表包含了由传入的函数生成的值,因此返回的列表的元素类型将会是该函数的结果类型。在前一例中,传入的函数返回的是字符串,因此map方法返回List[String]。如果传入map方法的函数返回其他类型,则map方法返回的List也会以相应的类型作为其元素类型。例如,在下面的map方法调用中,传入的函数将字符串变换成整数来表示字符串元素的长度。因此,map方法调用的结果就是包含了这些长度的List[Int]:
![](https://epubservercos.yuewen.com/9944D7/23020655409775506/epubprivate/OEBPS/Images/42832-00-083-3.jpg?sign=1739428840-8oWPDesmkPo4qYksafkDQ76OSbF2wVvJ-0-5721c19d9738cb8594b4a87c145a605c)
像以前一样,你也可以用带有yield的for表达式来完成同样的变换:
![](https://epubservercos.yuewen.com/9944D7/23020655409775506/epubprivate/OEBPS/Images/42832-00-083-4.jpg?sign=1739428840-MPpOSQ4AB5qHfetiHguDqW0HBYwUjVh3-0-eeaadc32cbffbf7c91dedc0cc4d9e032)
很多类型都可以使用map方法,不仅仅是List。这让我们可以在很多类型上使用for表达式。比如说Vector,这是一个对所有它支持的操作提供“实效常量时间”(effectively constant time)性能的不可变序列。由于Vector具备带有正确签名的map方法,因此可以对Vector执行像List一样的函数式变换,可以直接调用map方法,也可以使用for-yield。例如:
![](https://epubservercos.yuewen.com/9944D7/23020655409775506/epubprivate/OEBPS/Images/42832-00-084-1.jpg?sign=1739428840-ijw97QJ92xsEC4zzjysCMd5kwkSqasHh-0-879b683103ce69d92d0889dd92703933)
请注意,当你对List执行map操作时,得到的返回值是一个新的List;而当你对Vector执行map操作时,得到的返回值是一个新的Vector。你会发现绝大多数定义了map方法的类型都具备这个模式。
最后再看一个例子,Scala的Option类型。Scala用Option表示可选的值,而不使用像Java一样用null表达此含义的传统技法。[12]Option要么是一个Some,表示值存在;要么是一个None,表示没有值。
作为一个展示Option实际使用的案例,我们可以考查一下find方法。所有的Scala集合类型,包括List和Vector,都具备find方法,其作用是查找满足给定前提的元素,这个前提是一个接收元素类型的参数并返回布尔值的函数。find方法的结果类型是Option[E],其中,E是集合的元素类型。find方法会逐个遍历集合的元素,将元素传递给前提。如果前提返回了true,find就停止遍历,并将当前元素包装在Some中返回。如果find遍历了所有元素都没有找到能通过前提判断的元素,就会返回None。下面是一些结果类型均为Option[String]的示例:
![](https://epubservercos.yuewen.com/9944D7/23020655409775506/epubprivate/OEBPS/Images/42832-00-085-1.jpg?sign=1739428840-dmfPn3PB5RgQkuIPJrRTIwO44Y4Uzr8v-0-27eb8ab58e3229da721efeb9f1da3708)
尽管Option不是一个集合,它也提供了map方法。[13]如果Option是一个Some,可被称为“已定义”的可选值,则map方法将返回一个新的包含了将原始Some元素传入map方法后得到返回值的新Option。下面的示例对startsW进行了变换,而它本来是一个包含字符串"Who"的Some:
![](https://epubservercos.yuewen.com/9944D7/23020655409775506/epubprivate/OEBPS/Images/42832-00-085-2.jpg?sign=1739428840-RoG1l8Yh12xVAyQAqMPpdpsICHQNlA9w-0-3b9d3e4aae99a9b65a3757f5342dbee8)
与List和Vetcor相同,可以通过对Option执行for-yield来完成这个变换:
![](https://epubservercos.yuewen.com/9944D7/23020655409775506/epubprivate/OEBPS/Images/42832-00-085-3.jpg?sign=1739428840-se3zCu8RwiZ8Sb6yrkInpPAgiwvHEO2S-0-f4850da0c6924cfc1f7678e2f8b6bc0c)
如果对None执行map,None意味着这是一个“未定义”的可选值,将得到一个None。下面是一个展示对startsH(一个None值)执行map操作的示例:
![](https://epubservercos.yuewen.com/9944D7/23020655409775506/epubprivate/OEBPS/Images/42832-00-085-4.jpg?sign=1739428840-TuIGIzlmXiptjEcF2BcbTU35xPtO4vol-0-af46b9c15b3c408a14f54c4aea5bb3b4)
用for-yield完成同样的变换操作:
![](https://epubservercos.yuewen.com/9944D7/23020655409775506/epubprivate/OEBPS/Images/42832-00-085-5.jpg?sign=1739428840-bAAHg4dLTh8uENe5yVWKFIUZutsZgDjx-0-f87d2d34d7ffcae01be7b79e47bb4f2c)
还可以用map方法和for-yield对其他许多类型进行变换,但就目前而言足够了。这一步的主要目的是让你对如何编写典型的Scala代码有一个直观的认识:对不可变数据结构进行函数式变换。