循环是处理集合的经典方式,但随着编程语言中一流函数的更多采用,集合管道成为一种有吸引力的替代方法。在这篇文章中,我通过一系列小示例研究了将循环重构为收集管道。
我分期发表这篇文章。这添加了一个重构循环的示例,该循环汇总了每个目的地机场的航班延误数据。
编程中的一个常见任务是处理对象列表。大多数程序员自然地使用循环来执行此操作,因为它是我们在第一个程序中学习的基本控制结构之一。但是循环并不是表示列表处理的唯一方式,近年来越来越多的人使用另一种方式,我称之为 集合管道 。这种风格通常被认为是函数式编程的一部分,但我在 Smalltalk 中大量使用它。由于 OO 语言支持使一流函数更易于编程的 lambda 和库,因此集合管道成为一个有吸引力的选择。
将简单循环重构为管道
我将从一个简单的循环示例开始,展示我将循环重构为收集管道的基本方法。
假设我们有一个作者列表,每个作者都有以下数据结构。
班级作者...
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
这个例子使用 C#
这是循环。
班级作者...
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
将循环重构为集合管道的第一步是在循环集合上应用 提取变量 。
班级作者...
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
这个变量为我提供了管道操作的起点。我现在还没有一个好名字,所以我会使用一个暂时有意义的名字,希望以后可以重命名。
然后我开始查看循环中的一些行为。我首先看到的是条件检查,我可以使用 .
班级作者...
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
我看到循环的下一部分在 twitter 句柄上运行,而不是作者,所以我可以使用 aa 。
班级作者...
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
Next 在循环中作为另一个条件,我可以再次移动到过滤操作。
班级作者...
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
循环现在所做的就是将其循环集合中的所有内容添加到结果集合中,这样我就可以删除循环并只返回管道结果。
班级作者...
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
这是代码的最终状态
班级作者...
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
我喜欢集合管道的一点是,当列表的元素通过管道时,我可以看到逻辑流。对我来说,它非常接近于我如何定义循环的结果“选择作者,选择拥有公司的人,并让他们的推特句柄删除任何空句柄”。
此外,即使在具有不同语法和管道运算符不同名称的不同语言中,这种代码风格也很熟悉。
爪哇
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
红宝石
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
虽然这与其他示例相匹配,但我会用
compact
替换最终的
reject
Clojure 语言
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
F#
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
同样,如果我不关心匹配其他示例的结构,我会合并地图并选择一个步骤
我发现,一旦我习惯了从管道的角度思考问题,我就可以快速应用它们,即使是使用一种不熟悉的语言。由于基本方法是相同的,因此即使是不熟悉的语法和函数名称也相对容易翻译。
在流水线中重构,并理解
一旦您将某些行为表示为管道,您就可以通过对管道中的步骤重新排序来进行潜在的重构。一个这样的举动是,如果你有一个地图后跟一个过滤器,你通常可以像这样将过滤器移动到地图之前。
班级作者...
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
当您有两个相邻的过滤器时,您可以使用连词将它们组合起来。
班级作者...
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
一旦我有了一个像这样的简单过滤器和映射形式的 C# 收集管道,我就可以用 Linq 表达式替换它
班级作者...
public string Name { get; set; }
public string TwitterHandle { get; set;}
public string Company { get; set;}
我认为 Linq 表达式是 的一种形式,类似地,您可以使用任何支持列表理解的语言来执行类似的操作。您更喜欢列表理解形式还是管道形式(我更喜欢管道),这是一个品味问题。一般来说,管道更强大,因为你不能将所有管道重构为理解。