认识不同Java.net语言中类似的函数结构

函数式编程语言包含多个系列的常见函数。但">开发人员有时很难在语言之间进行切换,因为熟悉的函数具有不熟悉的名称。函数式语言倾向于基于函数范例来命名这些常见函数。从脚本背景衍生而来的语言倾向于使用更具描述性的名称(有时是多个名称,包含多个指向同一个函数的别名)。

在本期文章中,我将继续探讨 3 种重要函数(过滤、映射和缩减)的实用性,展示来自每种 Java 下一代语言的实现细节。文中的讨论和示例旨在减轻 3 种语言对类似函数结构使用的不一致名称时可能引起的混淆。

过滤

在过滤 函数中,您可指定一个布尔值条件(通常为一个高阶函数的形式),将它应用到一个集合。该函数返回集合的子集,其中的元素与该条件匹配。过滤与查找 函数紧密相关,后者返回集合中第一个匹配的元素。

Scala

Scala 拥有多个过滤函数变体。最简单的情形基于传递的条件来过滤某个列表。在第一个示例中,我创建一个数字列表。然后使用了 filter() 函数,并传递了一个代码块,指定了所有元素都可以被 3 整除的条件:

val numbers = List.range(1, 11)numbers filter (x => x % 3 == 0)// List(3, 6, 9)

我可依靠隐式的参数来创建该代码快的更加简洁的版本:

numbers filter (_ % 3 == 0)// List(3, 6, 9)

第二个版本不那么冗长,因为在 Scala 中,您可以将参数替换为下划线。两个版本都可以得到相同的结果。

过滤操作的许多示例都使用了数字,但 filter() 适用于任何集合。此示例将 filter() 应用到一个单词列表来确定 3 字母单词:

val words = List("the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog")words filter (_.length == 3)// List(the, fox, the, dog)

Scala 中的另一个过滤函数变体是 partition() 函数,它将一个集合拆分为多个部分。这种拆分基于您传递的高阶函数来确定分离条件。在这里,partition() 函数将返回两个列表,它们依据哪些列表成员可被 3 整除来进行拆分:

numbers partition (_ % 3 == 0)// (List(3, 6, 9),List(1, 2, 4, 5, 7, 8, 10))

filter() 函数返回一个匹配元素集合,而 find() 仅返回第一个匹配元素:

numbers find (_ % 3 == 0)// Some(3)

但是,find() 的返回值不是匹配的值本身,而是一个包装在 Option 类中的值。Option 有两个可能的值:Some 或 None。像其他一些函数式语言一样,Scala 使用 Option 作为一种约定来避免在缺少某个值时返回 null。Some() 实例包装实际的返回值,在 numbers find (_ % 3 == 0) 的情况下,该值为 3。如果我尝试查找某个不存在的值,那么返回值将为 None:

numbers find (_ < 0)// None

Scala 还包含多个函数,它们基于一个判定函数来处理一个集合并返回值或丢弃它们。takeWhile() 函数返回集合中满足判定函数的最大的值集:

List(1, 2, 3, -4, 5, 6, 7, 8, 9, 10) takeWhile (_ > 0)// List(1, 2, 3)

dropWhile() 函数跳过满足判定条件的最大元素数量:

words dropWhile (_ 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 可处理所有类型,包括字符串:

def words = ["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 被用作一个专门的过滤器:它丢弃与判定条件匹配的最大前缀,仅过滤列表的第一部分:

def moreWords = ["the", "two", "ton"] + wordsmoreWords.dropWhile {it.startsWith("t")}// [quick, brown, fox, jumped, over, the, lazy, dog]

Clojure

Clojure 拥有令人震惊的集合操作例程数量。由于 Clojure 的动态类型,其中许多例程都是通用的。许多开发人员倾向于使用 Clojure,因为它的集合库非常丰富和灵活。Clojure 使用传统的函数式编程名称,如 (filter ) 函数所示:

(def numbers (range 1 11))(filter (fn [x] (= 0 (rem x 3))) numbers); (3 6 9)

像其他语言一样,Clojure 为简单的匿名函数提供了简洁的语法:

(filter #(zero? (rem % 3)) numbers); (3 6 9)

而且与其他语言中一样,Clojure 的函数适用于任何适用的类型,比如字符串:

(def words ["the" "quick" "brown" "fox" "jumped" "over" "the" "lazy" "dog"])(filter #(= 3 (count %)) words); (the fox the dog)

Clojure 的 (filter ) 返回类型为 Seq,它通过圆括号来描述。Seq 是 Clojure 中的顺序集合的核心抽象。

映射

所有 Java 下一代语言中常见的第二个主要的函数变形是映射。映射函数接受一个高阶函数和一个集合,然后向每个元素应用传递的函数并返回一个集合。返回的集合(不同于过滤)的大小与原始集合相同,但更新了值。

Scala

Scala 的 map() 函数接受一个代码块并返回转换的集合:

List(1, 2, 3, 4, 5) map (_ + 1)// List(2, 3, 4, 5, 6)

map() 函数适用于所有适用的类型,但它不一定返回集合元素的已转换集合。在此示例中,我在一个字符串中返回所有元素的大小列表:

words map (_.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 函数也适用于可能未以传统方式嵌套的数据结构。例如,您可将一个字符串视为一个嵌套字符系列:

words flatMap (_.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() 方法适用于您可向其提供合理的判定条件的任何集合,比如一个字符串列表:

def words = ["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 ) 函数,它接受一个高阶函数(其中包含运算符)和一个集合:

(map inc numbers); (2 3 4 5 6 7 8 9 10 11)

(map ) 的第一个参数可以是任何接受单个参数的函数:命名函数、匿名函数或已存在的函数,比如递增其参数的 inc。此示例中演示了更典型的匿名语法,它生成一个字符串中的单词长度的集合:

(map #(count %) words); (3 5 5 3 6 4 3 4 3)

Clojure 的 (flatten ) 函数类似于 Groovy 的:

(flatten [[1 2 3] [4 5 6] [7 8 9]]); (1 2 3 4 5 6 7 8 9)

折叠/缩减

在 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) a else b)// 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 + (range 1 11)); 55

Clojure 将对类似 reduce 的功能的高级支持包含在一个名为 reducers 的库中,后面的一期文章将会介绍这个库。

结束语

学习不同范例(比如函数式编程)的部分挑战在于学习新术语。在不同社区使用不同的词汇时,这一过程会变得更加复杂。但一旦掌握了相似性,您就会看到,所有 3 种 Java 下一代语言都以令人惊奇的方式在语法上提供了重叠的功能。

在下一期中,我将探讨 Java 下一代语言中的内存化,讨论函数特性如何结合使用来实现简洁有力。

文中提供的一系列简短的编码示例可帮助您整理这 3 种语言在命名这些重要的函数结构的方式上的比较容易混淆的区别。

时间: 2024-07-28 14:54:22

认识不同Java.net语言中类似的函数结构的相关文章

核心函数特性及Java下一代语言如何实现和组合它们

所有编程语言都在增加函数特性,因为运行时已变得强大到足够适应性能或内存开销.函数式编程的许多收益之一是,您可将麻烦或容易出错的任务卸载到运行时.另一个收益是将函数特性简洁地组合到您代码中的能力. 在本期文章中,我将探讨 Java 下一代语言中的内存化.然后,通过利用 Clojure 示例,我将展示通过利用函数特性之间的协调作用,如何实现http://www.aliyun.com/zixun/aggregation/17253.html">常见问题的一般解决方案. 内存化 内存化 这个词是

选择合适的java脚本语言

脚本 选择合适的java脚本语言--如果你正考虑在java应用中集成脚本解释器,最难得是决定使用那种 摘要:脚本语言已经向java开发者证明了它的价值.它让客户实现应用功能的扩展和界面的个性化,从而程序的价值得以提升.另外,它们可以显著的简化程序开发者的设计任务,通过实现动态定义.装载和评估.对于开发人员,集成一种或多种脚本语言的任务是简单的,从越来越长的可选列表中选出一个确实困难的.本文描述了一些伴随java应用中脚本语言支持的问题,并从不同角度比较了Groovy, JudoScript, P

数组中求乘积-JAVA中二维数组的函数运算

问题描述 JAVA中二维数组的函数运算 for(i=0;i<MAX;i++){ for(j=0;j<MAX;j++){ d[i][j]=0; for(k=0;k<MAX;k++) d[i][j]+=a[i][k]*b[k][j]; } } 就是在这个程序中为什么最后的乘积是 for(k=0;k<MAX;k++) d[i][j]+=a[i][k]*b[k][j];

用java实现DB2的自定义函数

函数 我们知道,在DB2中提供的函数有限.在笔者的实际工作中就遇到了这个问题.所以我们在用到DB2的项目中,很多时候需要自己定义函数.好在DB2也提供了自定义函数的功能.下面笔者就某个项目来说.用java来实现DB2自定义函数,首先要写出函数的java实现代码import COM.ibm.db2.app.*; public class DB2Udf extends UDF{ /** * */ public DB2Udf() { } /** * 返回两个数值的按位与操作结果 * @param nu

Strsafe.h:更安全的C语言字符串处理函数

原文出处:Strsafe.h: Safer String Handling in C:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/strsafe.asp 在微软公司举行的Microsoft Windows Security Push 活动期间,一批测试者.程序管理经理和普通程序员共同决定要为 C 语言量身定制一套具有较高安全性的字符串处理函数,并且希望这些函数能被 Windows 程

zk 怎么用java 调用前台的js函数

问题描述 zk 怎么用java 调用前台的js函数 单位的项目用的zk框架,今天经理要求在界面中加入echarts图表,我想问下,后台java里怎么把json数据传到前台的js 函数里 解决方案 http://quzhan87com.iteye.com/blog/1061964 看这篇文章能解决你的问题. 解决方案二: 前台用ajax调用后台的java,接收后台的数据 解决方案三: http://zhidao.baidu.com/link?url=o8q9FOoe6FC6inMGrb6BPTrC

c语言函数-c语言中的函数使用问题,

问题描述 c语言中的函数使用问题, printf(""<%d> ""++count)中<%d>是什么意思?望各位大神帮帮我 解决方案 C语言下的函数可变参数的使用c语言中system()函数的使用C语言函数传值问题 解决方案二: 打印count的值,以十进制输出(%d),且count要先自己加一,才会打印. 解决方案三: 百度一下你就知道~~ 解决方案四: 用来输出十进制的整数 解决方案五: 你可以当一个符号:这个符号代表一个10进制的数字,

c语言-C语言 PCRE正则表达式 函数解析

问题描述 C语言 PCRE正则表达式 函数解析 求指教:PCRE库函数中,pcre_exec()的返回值是什么意思? *ovector数组的元素又是代表什么意 解决方案 int pcre_exec(const pcre *code, const pcre_extra *extra, const char *subject, int length, int startoffset, int options, int *ovector, int ovecsize) pcre_exec()返回匹配串的

C语言的回调函数怎么转换成C#的委托啊?

问题描述 在p/invoke调用的时候委托和回调怎么转换啊? 解决方案 解决方案二:这个回调函数原型是:typedeflong(CALLBACK*fnFileData)(void*object,inttype,unsignedchar*data,intdataLength);现在我要把写成C#里面的delegate类型,但是不知道unsignedchar*data该怎么写publicdelegateInt32audioRawCallback(IntPtr*callbackObject,Int32