学习不同 Java.net 语言中类似的函数结构
前言
函数式编程语言包含多个系列的常见函数。但开发人员有时很难在语言之间进行切换,因为熟悉的函数具有不熟悉的名称。函数式语言倾向于基于函数范例来命名这些常见函数。从脚本背景衍生而来的语言倾向于使用更具描述性的名称(有时是多个名称,包含多个指向同一个函数的别名)。
在本期文章中,我将继续探讨3种重要函数(过滤、映射和缩减)的实用性,展示来自每种Java下一代语言的实现细节。文中的讨论和示例旨在减轻3种语言对类似函数结构使用的不一致名称时可能引起的混淆。
过滤
在过滤函数中,您可指定一个布尔值条件(通常为一个高阶函数的形式),将它应用到一个集合。该函数返回集合的子集,其中的元素与该条件匹配。过滤与查找函数紧密相关,后者返回集合中第一个匹配的元素。
Scala
Scala拥有多个过滤函数变体。最简单的情形基于传递的条件来过滤某个列表。在第一个示例中,我创建一个数字列表。然后使用了filter()函数,并传递了一个代码块,指定了所有元素都可以被3整除的条件:
valnumbers=List.range(1,11) numbersfilter(x=>x%3==0) //List(3,6,9)
我可依靠隐式的参数来创建该代码快的更加简洁的版本:
numbersfilter(_%3==0) //List(3,6,9)
第二个版本不那么冗长,因为在Scala中,您可以将参数替换为下划线。两个版本都可以得到相同的结果。
过滤操作的许多示例都使用了数字,但filter()适用于任何集合。此示例将filter()应用到一个单词列表来确定3字母单词:
valwords=List("the","quick","brown","fox","jumped","over","the","lazy","dog") wordsfilter(_.length==3) //List(the,fox,the,dog)
Scala中的另一个过滤函数变体是partition()函数,它将一个集合拆分为多个部分。这种拆分基于您传递的高阶函数来确定分离条件。在这里,partition()函数将返回两个列表,它们依据哪些列表成员可被3整除来进行拆分:
numberspartition(_%3==0) //(List(3,6,9),List(1,2,4,5,7,8,10))
filter()函数返回一个匹配元素集合,而find()仅返回第一个匹配元素:
numbersfind(_%3==0) //Some(3)
但是,find()的返回值不是匹配的值本身,而是一个包装在Option类中的值。Option有两个可能的值:Some或None。像其他一些函数式语言一样,Scala使用Option作为一种约定来避免在缺少某个值时返回null。Some()实例包装实际的返回值,在numbersfind(_%3==0)的情况下,该值为3。如果我尝试查找某个不存在的值,那么返回值将为None:
numbersfind(_<0) //None
Scala还包含多个函数,它们基于一个判定函数来处理一个集合并返回值或丢弃它们。takeWhile()函数返回集合中满足判定函数的最大的值集:
List(1,2,3,-4,5,6,7,8,9,10)takeWhile(_>0) //List(1,2,3)
dropWhile()函数跳过满足判定条件的最大元素数量:
wordsdropWhile(_startsWith"t") //List(quick,brown,fox,jumped,over,the,lazy,dog)
Groovy
Groovy不是一个函数式语言,但它包含许多函数范例,一些范例的名称源自脚本语言。例如,在函数式语言中,该函数在传统上被称为filter()的函数,就是Groovy中的findAll()方法:
(1..10).findAll{it%3==0} //[3,6,9]
像Scala的过滤函数一样,Groovy可处理所有类型,包括字符串:
defwords=["the","quick","brown","fox","jumped","over","the","lazy","dog"] words.findAll{it.length()==3} //[The,fox,the,dog]
Groovy还有一个类似partition()的函数,称为split():
(1..10).split{it%3} //[[1,2,4,5,7,8,10],[3,6,9]]
split()方法的返回值是一个嵌套数组,就像Scala中从partition()返回的嵌套列表。
Groovy的find()方法返回集合中第一个匹配的元素:
(1..10).find{it%3==0} //3
不同于Scala,Groovy遵循Java约定,在find()未能找到元素时返回null:
(1..10).find{it<0} //null
Groovy还拥有takeWhile()和dropWhile()方法,它们具有与Scala的版本类似的语义:
[1,2,3,-4,5,6,7,8,9,10].takeWhile{it>0} //[1,2,3] words.dropWhile{it.startsWith("t")} //[quick,brown,fox,jumped,over,the,lazy,dog]
与Scala示例中一样,dropWhile被用作一个专门的过滤器:它丢弃与判定条件匹配的最大前缀,仅过滤列表的第一部分:
defmoreWords=["the","two","ton"]+words moreWords.dropWhile{it.startsWith("t")} //[quick,brown,fox,jumped,over,the,lazy,dog]
Clojure
Clojure拥有令人震惊的集合操作例程数量。由于Clojure的动态类型,其中许多例程都是通用的。许多开发人员倾向于使用Clojure,因为它的集合库非常丰富和灵活。Clojure使用传统的函数式编程名称,如(filter)函数所示:
(defnumbers(range111)) (filter(fn[x](=0(remx3)))numbers) ;(369)
像其他语言一样,Clojure为简单的匿名函数提供了简洁的语法:
(filter#(zero?(rem%3))numbers) ;(369)
而且与其他语言中一样,Clojure的函数适用于任何适用的类型,比如字符串:
(defwords["the""quick""brown""fox""jumped""over""the""lazy""dog"]) (filter#(=3(count%))words) ;(thefoxthedog)
Clojure的(filter)返回类型为Seq,它通过圆括号来描述。Seq是Clojure中的顺序集合的核心抽象。
映射
所有Java下一代语言中常见的第二个主要的函数变形是映射。映射函数接受一个高阶函数和一个集合,然后向每个元素应用传递的函数并返回一个集合。返回的集合(不同于过滤)的大小与原始集合相同,但更新了值。
Scala
Scala的map()函数接受一个代码块并返回转换的集合:
List(1,2,3,4,5)map(_+1) //List(2,3,4,5,6)
map()函数适用于所有适用的类型,但它不一定返回集合元素的已转换集合。在此示例中,我在一个字符串中返回所有元素的大小列表:
wordsmap(_.length) //List(3,5,5,3,6,4,3,4,3)
在函数式编程语言中常常会产生嵌套列表,以至于嵌套列表对解除嵌套(通常称为扁平化)的库支持很常见。以下是扁平化一个嵌套列表的示例:
List(List(1,2,3),List(4,5,6),List(7,8,9))flatMap(_.toList) //List(1,2,3,4,5,6,7,8,9)
获得的List中仅包含元素,删除了额外的基础架构。flatMap函数也适用于可能未以传统方式嵌套的数据结构。例如,您可将一个字符串视为一个嵌套字符系列:
wordsflatMap(_.toList) //List(t,h,e,q,u,i,c,k,b,r,o,w,n,f,o,x,...
Groovy
Groovy还包含多个称为collect()的映射变体。默认的变体接受一个代码块,以便将该变体应用到集合的每个元素:
(1..5).collect{it+=1} //[2,3,4,5,6]
像其他语言一样,Groovy允许对简单的匿名高阶函数使用简写;it保留字用于替代单独的参数。
collect()方法适用于您可向其提供合理的判定条件的任何集合,比如一个字符串列表:
defwords=["the","quick","brown","fox","jumped","over","the","lazy","dog"] words.collect{it.length()} //[3,5,5,3,6,4,3,4,3]
Groovy还有一个类似于flatMap()的折叠内部结构的方法,称为flatten():
[[1,2,3],[4,5,6],[7,8,9]].flatten() //[1,2,3,4,5,6,7,8,9]
flatten()方法也适用于不太明显的集合,比如字符串:
(words.collect{it.toList()}).flatten() //[t,h,e,q,u,i,c,k,b,r,o,w,n,f,o,x,j,...
Clojure
Clojure包含一个(map)函数,它接受一个高阶函数(其中包含运算符)和一个集合:
(mapincnumbers) ;(234567891011)
(map)的第一个参数可以是任何接受单个参数的函数:命名函数、匿名函数或已存在的函数,比如递增其参数的inc。此示例中演示了更典型的匿名语法,它生成一个字符串中的单词长度的集合:
(map#(count%)words) ;(355364343)
Clojure的(flatten)函数类似于Groovy的:
(flatten[[123][456][789]]) ;(123456789)
折叠/缩减
在3种Java下一代语言中,第三个常见函数在名称上拥有最多变体和许多细微的区别。foldLeft和reduce是一个名为catamorphism的列表操作概念上的特定变体,该概念是列表折叠的一种泛化。在此示例中,“折叠左侧”表示:
使用一个二进制函数或运算符将列表的第一个元素与第二个元素相结合,创建一个新的第一个元素。
重复第一步,直到列表用完且您得到一个单一元素。
请注意,这是您在对一组数字求和时所做的操作:从0开始,加第一个元素,将结果与第二个元素相加,一直执行此操作,直到列表元素被用完为止。
Scala
Scala拥有最丰富的折叠运算集合,这是因为它在一定程度上简化了动态类型的Groovy和Clojure中没有的多种类型场景。缩减函数常用于执行求和:
List.range(1,10)reduceLeft((a,b)=>a+b) //45
提供给reduce()的函数通常是一个接受两个参数,并返回单个结果的函数或运算符,以便可以使用一个列表。您可以使用Scala的语法糖来缩短函数定义:
List.range(1,10).reduceLeft(0)(_+_) //45
reduceLeft()函数假设第一个元素是运算的左侧。对于相加等运算符,操作数的位置无关紧要,但放置顺序对相除等运算至关重要。如果希望反转运算符应用的顺序,可以使用reduceRight():
List.range(1,10)reduceRight(_-_) //5
了解何时可使用缩减等高级抽象是掌握函数编程的一个关键。此示例使用reduceLeft()来确定集合中最常的单词:
words.reduceLeft((a,b)=>if(a.length>b.length)aelseb) //jumped
缩减和折叠运算拥有重叠的功能,它们具有细微的差别,但这不属于本文的讨论范围。但是,通常可以看到它们的一个明显区别。在Scala中,签名reduceLeft[B>:A](op:(B,A)=>B):B表明惟一想要的参数就是组合元素的函数。初始值应该是集合中的第一个值。相对而言,签名foldLeft[B](z:B)(op:(B,A)=>B):B表示结果的一个初始种子值,所以您可以返回与列表元素类型不同的类型。
以下是一个使用foldLeft对集合求和的示例:
List.range(1,10).foldLeft(0)(_+_) //45
Scala支持运算符重叠,所以两个常见的折叠操作foldLeft和foldRight分别拥有相应的运算符:/:和:\。因此,您可以使用foldLeft创建sum的简洁版本:
(0/:List.range(1,10))(_+_) //45
类似地,要找到一个列表中每个元素的级联区别(求和运算的反向操作,无可否认这种需求很少见),您可以使用foldRight()函数或:\运算符:
(List.range(1,10):\0)(_-_) //5
Groovy
Groovy通过使用重叠来支持与Scala的reduce()和foldLeft()选项相同的功能,从而进入缩减类别。该函数的一个版本接受一个初始值。此示例使用inject()方法生成一个集合的总和:
(1..10).inject{a,b->a+b} //55
替代形式接受一个初始值:
(1..10).inject(0,{a,b->a+b}) //55
Groovy拥有一个比Scala或Clojure小得多的函数库—Groovy是一种不强调函数式编程的多范例编程,看到这种情况毫不奇怪。
Clojure
Clojure主要是一种函数式编程语言,所以它支持(reduce)。(reduce)函数接受一个可选的初始值,以便同时涵盖Scala所处理的reduce()和foldLeft()情形。(reduce)函数没有给用户带来任何惊喜。它接受一个需要两个参数的函数和一个集合:
(reduce+(range111)) ;55
Clojure将对类似reduce的功能的高级支持包含在一个名为reducers的库中,后面的一期文章将会介绍这个库。
结束语
学习不同范例(比如函数式编程)的部分挑战在于学习新术语。在不同社区使用不同的词汇时,这一过程会变得更加复杂。但一旦掌握了相似性,您就会看到,所有3种Java下一代语言都以令人惊奇的方式在语法上提供了重叠的功能。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。