groovy邮件功能

    技术2024-03-18  100

    关于本系列

    本系列旨在将您的观点重新定位为实用的心态,帮助您以新的方式看待常见问题并找到改善日常编码的方式。 它探讨了函数式编程的概念,允许使用Java™语言进行函数式编程的框架,在JVM上运行的函数式编程语言以及语言设计的一些未来方向。 该系列面向那些了解Java及其抽象如何工作,但很少或没有使用功能语言经验的开发人员。

    告白:我再也不想用一种非垃圾收集的语言工作了。 我用像C ++这样的语言缴纳了多年的会费,而且我不想放弃现代语言的便利性。 这就是软件开发如何进行的故事。 我们构建抽象层来处理(并隐藏)世俗的细节。 随着计算机功能的增长,我们将更多的任务转移给了语言和运行时。 十年前,开发人员回避了解释型语言,因为它们对生产应用程序来说太慢了,但现在已经很普遍了。 功能语言的许多功能在十年前就已经令人望而却步地慢了下来,但现在变得非常有意义了,因为它们优化了开发人员的时间和精力。

    我在本系列文章中介绍的许多功能都显示了功能语言和框架如何处理世俗的细节。 但是,您不必使用功能语言即可开始从功能构造中受益。 在本期以及下期中,我将展示一些函数式编程如何已经渗透到Groovy中。

    Groovy的功能性清单

    Groovy大大增强了Java集合库,包括添加功能构造。 Groovy为您提供的第一个好处是在列表上提供了不同的视角,乍一看似乎微不足道,但提供了一些有趣的好处。

    看到列表的方式不同

    如果您的背景主要是使用C或类似C的语言(包括Java),则您可能会将列表概念化为索引集合。 即使没有显式使用索引,这种观点也使遍历一个集合变得容易,如清单1所示,它们是Groovy代码:

    清单1.使用(隐藏)索引的列表遍历
    def perfectNumbers = [6, 28, 496, 8128] def iterateList(listOfNums) { listOfNums.each { n -> println "${n}" } } iterateList(perfectNumbers)

    Groovy还包括一个eachWithIndex()迭代器,该迭代器在需要显式访问的情况下将索引作为代码块的参数提供。 即使我没有在清单1的iteraterList()方法中使用索引,我仍然将其视为插槽的有序集合,如图1所示:

    图1.列表作为索引槽

    许多函数式语言在列表上的观点略有不同,幸运的是Groovy拥有这种观点。 与其将列表视为带索引的插槽,不如将其视为列表中第一个元素(标头 )和列表其余部分( 尾部 )的组合,如图2所示:

    图2.列表的头和尾

    将列表视为头尾,可以让我使用递归来遍历列表,如清单2所示:

    清单2.使用递归的列表遍历
    def recurseList(listOfNums) { if (listOfNums.size == 0) return; println "${listOfNums.head()}" recurseList(listOfNums.tail()) } recurseList(perfectNumbers)

    在清单2的recurseList()方法中,我首先检查是否作为参数传递的列表中没有元素。 如果是这样,那么我就可以完成并可以返回。 如果没有,我打印出列表中的第一个元素(可通过Groovy的head()方法使用),然后在列表的其余部分上递归调用recurseList()方法。

    递归在平台中内置了技术限制(请参阅参考资料 ),因此这不是万能药。 但是对于包含少量项目的列表应该是安全的。 我对调查对代码结构的影响更感兴趣,以期限制会消失或消失的日子。 考虑到这些缺点,递归版本的好处可能不会立即显现出来。 为了使它更加完善,请考虑过滤列表的问题。 在清单3中,我展示了一个过滤方法的示例,该方法接受一个列表和一个谓词(布尔测试)来确定该项是否属于列表:

    清单3.使用Groovy进行命令式过滤
    def filter(list, p) { def new_list = [] list.each { i -> if (p(i)) new_list << i } new_list } modBy2 = { n -> n % 2 == 0} l = filter(1..20, modBy2)

    清单3中的代码很简单:我为要保留的元素创建了一个holder变量,遍历列表,使用包含谓词检查每个元素,并返回已过滤项的列表。 当我调用filter() ,我提供了一个指定过滤条件的代码块。

    考虑清单3中的filter方法的递归实现,如清单4所示:

    清单4.使用Groovy进行递归过滤
    def filter(list, p) { if (list.size() == 0) return list if (p(list.head())) [] + list.head() + filter(list.tail(), p) else filter(list.tail(), p) } l = filter(1..20, {n-> n % 2 == 0})

    在清单4的filter()方法中,我首先检查传递的列表的大小,如果没有元素,则将其返回。 否则,我将根据过滤谓词检查列表的开头; 如果通过,则将其添加到列表中(带有一个初始的空列表,以确保我总是返回正确的类型); 否则,我递归过滤尾巴。

    清单3和清单4的区别突出了一个重要问题:谁在关注状态? 在命令式版本中, 我是。 我必须创建一个新的变量命名new_list , 我必须添加的东西给它, 我必须回到它,当我完成了。 在递归版本中,该语言管理返回值,并将其作为每个方法调用的递归返回建立在堆栈上。 请注意, 清单4中 filter()方法的每个退出路径都是一个返回调用,它将在堆栈上建立中间值。

    尽管不如垃圾回收那样显着改善生活,但这确实说明了编程语言的一个重要趋势:卸载活动部件。 如果绝对不允许接触列表的中间结果,那么我将无法以与之交互的方式引入错误。

    关于列表的这种视角转变使您可以探索其他方面,例如列表的大小和范围。

    Groovy中的惰性列表

    功能语言的常见特征之一是惰性列表 :仅在需要时才生成其内容的列表。 惰性列表使您可以推迟对昂贵资源的初始化,直到您完全需要它们为止。 它们还允许创建无限序列:没有上限的列表。 如果您不需要事先说出列表的大小,则可以将其设置为所需的大小。

    首先,在清单5中,向您展示一个使用懒惰列表的示例,然后,向您展示实现:

    清单5.在Groovy中使用惰性列表
    def prepend(val, closure) { new LazyList(val, closure) } def integers(n) { prepend(n, { integers(n + 1) }) } @Test public void lazy_list_acts_like_a_list() { def naturalNumbers = integers(1) assertEquals('1 2 3 4 5 6 7 8 9 10', naturalNumbers.getHead(10).join(' ')) def evenNumbers = naturalNumbers.filter { it % 2 == 0 } assertEquals('2 4 6 8 10 12 14 16 18 20', evenNumbers.getHead(10).join(' ')) }

    清单5中的第一个方法prepend()创建了一个新的LazyList ,允许您添加值。 下一个方法integers()使用prepend()方法返回一个整数列表。 我发送给prepend()方法的两个参数是列表的初始值和包含用于生成下一个值的代码的代码块。 integers()方法的作用就像是一个工厂,该工厂返回在前面具有一个值的整数的惰性列表,并在后面计算一个附加值。

    要从列表中检索值,我调用getHead()方法,该方法从列表顶部返回值的参数个数。 在清单5中 , naturalNumbers是所有整数的惰性序列。 要获取其中的一些,我调用getHead()方法,指定我想要多少个整数。 如断言所示,我收到了前10个自然数的列表。 使用filter()方法,我检索了一个偶数的惰性列表,并调用getHead()方法来获取前10个偶数。

    清单6中显示了LazyList的实现:

    清单6. LazyList实现
    class LazyList { private head, tail LazyList(head, tail) { this.head = head; this.tail = tail } def LazyList getTail() { tail ? tail() : null } def List getHead(n) { def valuesFromHead = []; def current = this n.times { valuesFromHead << current.head current = current.tail } valuesFromHead } def LazyList filter(Closure p) { if (p(head)) p.owner.prepend(head, { getTail().filter(p) }) else getTail().filter(p) } }

    惰性列表包含在构造函数中指定的头和尾。 getTail()方法确保tail不为null并执行它。 getHead()方法一次收集一个我想返回的元素,将现有元素从列表的头部拉出,并要求尾部生成一个新值。 对n.times {}的调用针对请求的元素数执行此操作,该方法返回收获的值。

    清单5中的filter()方法使用与清单4相同的递归方法,但将其实现为列表的一部分,而不是独立的函数。

    惰性列表存在于Java中(请参阅参考资料 ),但是在具有功能特征的语言中更容易实现。 惰性列表在生成资源非常昂贵的情况下非常有用,例如获取完美数字列表。

    完美数字的懒惰列表

    如果您一直在关注本系列文章,那么您会清楚地知道我最喜欢的几内亚猪代码,可以找到理想的数字(请参阅“ 功能性思考,第1部分 ”)。 到目前为止,所有实现的缺点之一是需要指定分类编号。 取而代之的是,我想要一个返回懒散列表的完美数字的版本。 为了实现这个目标,我编写了一个功能强大,非常紧凑的完美数字查找器,它支持惰性列表,如清单7所示:

    清单7.简化的数字分类器版本,包括nextPerfectNumberFrom()方法
    class NumberClassifier { static def factorsOf(number) { (1..number).findAll { i -> number % i == 0 } } static def isPerfect(number) { factorsOf(number).inject(0, {i, j -> i + j}) == 2 * number } static def nextPerfectNumberFrom(n) { while (! isPerfect(++n)) ; n } }

    如果factorsOf()和isPerfect()方法中的代码晦涩难懂,那么您可以在上一部分中看到这些方法的派生。 新方法nextPerfectNumber()使用isPerfect()方法查找除作为参数传递的数字之外的下一个完美数字。 即使对于较小的值,此方法调用也将花费很长时间执行(尤其是考虑到此代码的优化程度不高); 没有那么多完美的数字。

    使用这个新版本的NumberClassifier ,我可以创建一个由完美数字组成的惰性列表,如清单8所示:

    清单8.延迟初始化的完美数字列表
    def perfectNumbers(n) { prepend(n, { perfectNumbers(nextPerfectNumberFrom(n)) }) }; @Test public void infinite_perfect_number_sequence() { def perfectNumbers = perfectNumbers(nextPerfectNumberFrom(1)) assertEquals([6, 28, 496], perfectNumbers.getHead(3)) }

    使用清单5中定义的prepend()方法,我构造了一个以初始值为首的完美数列表,以及一个知道如何计算下一个完美数为尾的闭包块。 我使用1之后的第一个完美数字初始化列表(使用静态导入,以便可以更轻松地调用NumberClassifier.nextPerfectNumberFrom()方法),然后让列表返回前三个完美数字。

    计算新的完美数字非常昂贵,因此我宁愿尽可能少地做。 通过将其构建为惰性列表,我可以将计算推迟到最佳时间。

    如果您对“列表”的抽象是“编号槽”,则考虑无限序列会更加困难。 将列表视为“第一个元素”和“列表的其余部分”会鼓励您考虑列表中的元素而不是结构,这反过来又使您可以考虑诸如惰性列表之类的事情。

    结论

    开发人员实现生产率飞跃的方法之一是通过构建有效的抽象来隐藏细节。 如果我们仍然使用1和0进行编码,我们将无处可寻。 功能语言的吸引人的方面之一是尝试从开发人员那里提取更多细节。 JVM上的现代动态语言已经为您提供了许多这些功能。 在本期中,我展示了如何仅对列表的构造方式稍加改变就能使语言在迭代期间管理状态。 同样,当您将列表视为“头”和“尾”时,它允许您构建诸如惰性列表和无限序列之类的东西。

    在下一部分中,您将看到Groovy元编程如何使您的程序更具功能性-并使您能够扩充第三方功能库以使其在Groovy中更好地工作。


    翻译自: https://www.ibm.com/developerworks/java/library/j-ft7/index.html

    Processed: 0.009, SQL: 9