写点什么

集合管道模式(上)

作者:冰心的小屋
  • 2022-11-11
    上海
  • 本文字数:2136 字

    阅读完需:约 7 分钟

集合管道模式(上)

译者 | TalkingData 李冰心

原文 | https://martinfowler.com/articles/collection-pipeline


译者按:

想了解 Java 流底层的实现机制,发现 Martin Fowler 发表的一篇关于集合管道模式的文章,有幸得以拜读,看过之后收获颇丰,索性将其翻译,能力有限敬请包涵。

集合管道

集合管道是一种编程模式,将相关计算转化为一系列操作,通常情况下每个操作输出结果为一个集合,该输出结果作为下一个操作的输入,常见的有 filter、map 和 reduce。


在函数式编程语言中集合管道模式随处可见,而面向对象语言借助于 lambdas 该模式又大放异彩。如果你对这种模式知之甚少,本文会载着你在各种语言中穿梭,途中会向你讲解相关精彩的案例,相信你一定会不虚此行。


在软件中,集合管道模式操作简单是一种最常见、让人屡试不爽的模式,在 unix 黒白命令行中;在面向对象万物皆对象的类中;在函数式编程一等公民的函数中,不同环境不同表现形式,相同操作却有着不同的名称,知其精髓,你会发现你根本离不开它。

1. Unix 管道

最初接触这种模式,是在 Unix 系统终端进行操作时。


案例:搜索 bliki/entries 目录下,文件内容包含文本'nosql'所有文件。

# 使用grep操作grep -l 'nosql' bliki/entries
复制代码

附加需求 1:分文件统计'nosql'个数

grep -l 'nosql' bliki/entries/* | xargs wc -w 
复制代码

附加需求 2:按个数大小排序

grep -l 'nosql' bliki/entries/* | xargs wc -w | sort -nr
复制代码

附加需求 3:top4 中的 2~4

grep -l 'nosql' bliki/entries/* | xargs wc -w | sort -nr | head -4 | tail -3
复制代码

可以 unix 管道的简洁和高效。

2. Smalltalk

之后使用 Smalltalk 时也发现了相同的模式。


案例:假设有一系列文章 someArticles,每篇文章 article 有很多标签 tag,想统计包含标签'nosql'的文章。

someArticles select: [ :each | each tags includes: #nosql]
复制代码

select 方法定义:使用了 Lambda 表示式方式。


附加需求 1:按照单词数量排序

(someArticles      select: [ :each | each tags includes: #nosql])       sortBy: [:a :b | a words > b words]
复制代码

附加需求 2:top4 中的 2~4

((someArticles       select: [ :each | each tags includes: #nosql])      sortBy: [:a :b | a words > b words])       copyFrom: 1 to: 3
复制代码

与 unix 管道最核心的相似之处:对于集合操作的 select、sortBy 和 copyFrom 输入输出都是集合,unix 是一行行记录构成的流,而 Smalltalk 是对象集合,但概念相同。

3. Ruby 

最近使用 Ruby 进行了很多开发,改进后的语法操作管道更加便捷。

some_articles  .select{|a| a.tags.include?(:nosql)}  .sort_by{|a| a.words}  .take(3)
复制代码

在面向对象语言中,一种很自然构造集合管道的方式是方法链,同样也可以使用嵌套函数。

4. Lisp

(remove-if-not   (lambda (x) (member 'nosql (article-tags x)))   (some-articles))
复制代码

为了排序,再次使用了 lambda:

(sort   (remove-if-not      (lambda (x) (member 'nosql (article-tags x)))      (some-articles))   (lambda (a b) (> (article-words a) (article-words b))))
复制代码

使用方法 subseq:

(subseq   (sort      (remove-if-not         (lambda (x) (member 'nosql (article-tags x)))         (some-articles))      (lambda (a b) (> (article-words a) (article-words b)))) 0 3)
复制代码

每种语言逐步构建的集合管道,看起来这是赏心悦目!不过每种语言所传达的含义是否清晰明了有待商议。

对于上述语言方法使用的先后顺序决定了集合管道的操作顺序,在你脑中应该很容易勾画出这种场景:源源不断的数据流入管道,经过层层过滤,最终输出。

5. 嵌套函数

构建管道除了使用流失形式,在 Lisp 中还可以使用嵌套函数,为了便于理解需要控制嵌套的层级。


最近流行的 lisp、Clojure 通过语法糖的形式避免了嵌套层级:

(->> (articles)     (filter #(some #{:nosql} (:tags %)))     (sort-by :words >)     (take 3))
复制代码

符号 "->>" 是一个线程宏,通过使用 lisp 强大的语法宏功能将每个表达式结果作为下一个表达式的输入。 对于大多数函数开发者,通过使用内置工具库,可游刃有余的将嵌套函数转化为线性管道,导致管道线性划无阻轻重,这也是为什么经过了这么长的时间,lisp 才支持操作符"->>" 。


这些天常听到函粉对集合管道的赞叹,夸奖函数式编程语言的强大,而对面向对象设计语言不屑一顾,这种说法很片面,因为对于面向对象设计语言 Smalltalk,其开发者早已广泛的使用了集合管道,只不过在 C++、Java 和 C#支持的有限而已,例如没有 lambda、集合操作不丰富,以至于很多面向对象开发人员陷入了处理集合管道问题的泥潭中。


Java 大行其道时并不支持 lambdas,对于像我这样 Smalltalk 开发者,使用 Java 时只能一边抱怨一边忍受,曾经尝试过各种方式构建集合管道,但每种实现都需要大量的辅助代码,最终被迫放弃。在 2000 年左右我开始使用 Ruby,重要原因是 Ruby 对于集合管道的支持,回想我的 Smalltalk 时代,常常感慨万千。


Lambdas 短小精悍的语义得到了广泛的赞许,C#已支持多年,现在 Java 也开始支持。集合管道注定会被越来越多的语言支持。



发布于: 刚刚阅读数: 7
用户头像

分享技术上的点点滴滴! 2013-08-06 加入

一杯咖啡,一首老歌,一段代码,欢迎做客冰屋。

评论

发布
暂无评论
集合管道模式(上)_集成管道模式_冰心的小屋_InfoQ写作社区