Ruby Ruby 2.7 (實驗性) 新特性:管道算符

franklinyu · 2019年06月28日 · 最后由 zw963 回复于 2019年12月21日 · 7878 次阅读

Ruby 2.7 中即將加入新特性:管道算符(pipe operator)。Ruby 2.6:

scrap.parse_document.load_http("http://example.com")

Ruby 2.7:

load_http("http://example.com")
  |> parse_document
  |> scrap

此特性早在四月份就併入了主分支;如無意外,會出現在今年聖誕的 2.7 中。Matz 也在推上徵求大家意見。可以參照DEV 這個月的文章

話說在那條推上看到了熟悉的 ID……

注:之前使用了錯誤的例子,經評論提示後改正。謝謝!不過這樣一來這個算符更顯無用……

那次征集意见后 matz 已经认为这个名字要改了,也许叫 chaining operator,符号也要改 https://bugs.ruby-lang.org/issues/15799#note-29

他解决的不是你提到的场景,所以才被社区反对... 目前解决的问题是 a.b.c 可以写作 a |> b |> c,除了多行代码排版好看点,还有 Range 这种不用包括号,看不到有什么好处,反而占用了一个宝贵的操作符

函数式这么玩,是因为他们没对象… 这个东西,写到时候挺爽的,维护起来略麻烦

糖衣炮弹用处不大

喜欢这个特性

a?.b?.c 这样的话该怎么写管道?

jasl 回复

管道可以避免无节操的继承关系,防止 object 无限变大,是非常好的特性。口水 Elixir 这个特性好久了。

相当于把 abc 变成了 cba?

python 和 php 都支持静态类型检测了,ruby 什么时候跟上?不支持静态类型,IDE 的智能提示就像弱智。

jasl 回复

还记仇来,你真的不知道是什么东西?还抠字眼。

gaicitadie 回复

朋友,我记你的仇,可能么?

语法不管怎么改,含义还是类似 Elixir 的 pipe,把上一个操作的返回值变成下一个操作的某个参数,让代码组合变得更容易。有些场景下 pipe 比链式调用泛用性更高。链式调用的缺点之一是必须把函数混入进某个 object,导致 object 承载多个部分的功能然后变得臃肿。

gaicitadie 回复

毕竟军爷

darkbaby123 回复

相当于把 abc 变成了 cba?

darkbaby123 回复

这个特性不是你想象的那种含义……所以很多人反弹

顶楼的例子是错的。

jasl 回复

哈哈哈 算了

Rei 回复

已改正,不過這樣不是更沒用了?相當於鏈式換個寫法?還不如函數式的管道呢

jasl 回复

3 不知道什麼時候出來,今年年末出的應該是 2.7 吧

franklinyu 回复

对,今年圣诞节是 2.7,3.0 感觉是明年了(其实 16 年提 3x3 这概念的时候 Ruby 3.0 是指今年...)

franklinyu 回复

这事反弹还有个因素,就是大家都觉得现在提供的功能不是大家想要的,对此 Matz 的回应非常不友好,等过了好久才有 Rei 贴的那个回复,讲了下 Elixir 风格的管道在 Ruby 上不好实现(其实我没看懂)

jasl 回复

我也沒看懂!List-1 和 Lisp-2 是啥?不能直接作爲語法糖麼?如果不好實現,爲啥 Elixir 能搞?

franklinyu 回复

我只能理解他可能从语言的理论上说这事不好做,或者是有什么 overhead 在里面(比如在虚拟机层面没法高效实现这样的),但是他有没讲清楚。

我不太关注 Ruby 本身的改进(除了别人喂的),但我知道之前有些很 cool 的东西,比如 auto fiber,大面都能实现,而且改善明显,但是有一些 edge 完全搞不定,就不 generic 了,于是只能砍掉的例子,其实我觉得 ko1 要搞得新并发模型 Guild 很可能也是这下场。。。。

按照 ruby 2.0 是 20 周年的时候发布,或许 ruby 3.0 要到 30 周年了

franklinyu 回复

按 matz 之前的意思

load_http("http://example.com")
  |> parse_document
  |> scrap

就是

load_http("http://example.com").parse_document.scrap

对,没什么用。

倒是激起了我对 elixir 的兴趣

😂 函数式编程没有对象才搞了这个玩意,面向对象语言学了有啥意义噗

@zzz6519003 @Rei 如果是这样,那确实没有用。反而怎么写都没有 . 简单。从语言层面来说我觉得 Ruby 也没必要关注“给现有功能多一种表达方法”的事情,这就不是语言设计者该做的事情。

@jasl Lisp 据我对 Emacs Lisp 有限的了解,它允许 variable 和 function 用同一个名字,比如变量和方法都叫 foo ,执行再会根据使用场景辨别到底是哪一种。这大概是 Matz 说它们不属于同一个 namespace 的意思。不过这跟 Ruby 有什么关系,以及为什么会影响这个 issue 就不知道了。

这个是 elixir 数据结构在管道传递中经历函数转换,每次都有一个状态 . 感觉 ruby 还是点点链式写法比较好懂一点

ruby 搞这个的意义,我是不知道了,elixir 没对象,没管道要一层一层套,ruby 有对象直接用点感觉更方便,elixir 写多了感觉还是挺爽的,就是要改下思维

最急需加入的就是类似 python 的类型标注,可加可不加,不强制。

def func(name: str) -> str:
    return "Hello" + name
nine 回复

无节操的继承关系是什么?。。

darkbaby123 回复

代码组合真的变得更容易了么?

增加语言复杂度。破坏兼容性。 像 C++ 一样已经有 1 千多个新特性了。

哪都能杠起来。。。也是醉了。。

zzz6519003 回复

只拿 Elixir 举例,我个人是觉得函数式的写法更灵活点,最主要的原因是函数不用绑定 this ,所以数据和行为(函数)可以更自由的组合。这也是跟 OO 最大的差别。代价就是因为每次数据和行为组合都要严格指定,代码必然会更多,所谓的更 explicit,比如 user.save()Repo.insert(user)

比如有时会碰到“一个数据在不同的业务场景下有不同的关注点” ,在 Rails 里就代表一个 model 有多种不同的功能,它们可以用 concern 实现并混入这个 model,这是一种 mixin 的方式。另一种方式是用 ContextA.do_something(some_model) 的方式来处理,把每个业务场景下的逻辑放在自己的模块里。函数式基本就强制你使用后者了。更复杂的逻辑可能涉及多个函数调用,就可以用 pipe 描述成 params |> ContextA.get_model() |> ContextB.calculate() 这种方式,简化一下函数嵌套。

为什么说链式调用没有 pipe 灵活?因为 obj.do_something 的前置条件就是这个方法必须归属于调用者对象,有时这是一种限制,导致必须用继承或 mixin 来组织代码以达到复用的目的。所有链式调用都可以写成 pipe 的方式,反过来未必成立。

darkbaby123 回复

反而言之,如果使用继承或 mixin 来组织代码,就可以把 pipe 改成链式调用?神奇!

sevk 回复

「增加語言複雜度」是有的,「破壞兼容性」怎麼說?

darkbaby123 回复

抱歉,之前我的用法誤導了你。經過評論區大家的改正,我發現並不是 Elixir 那種管道。

zzz6519003 回复

这不是我想表达的重点…… 其实都是两种语言里的不同特性,放一起比较也没什么意义。想了解你可以看看 Elixir。

zzz6519003 回复

在混合范式语言里,比如 JavaScript,你说的这个就是基操。链式调用的精髓是方法自身要返回 this,至于方法是继承来的还是 mixin 混入的并没有什么区别。

@darkbaby123 提到继承和 mixin,是想说如果考虑“关注分离”(concern separation) 的话 FP 和 OO 的区别,特别是 explicitly 和 implicitly 的区别。

至于你问的用继承或 mixin 来组织代码可以把 pipeline 变成 chainable,这是可行的,但不是一定的。通过继承或 mixin 得到的 methods 需要返回对象自身,满足这个前提那么就是 chainable methods。而 pipeline 不存在这个限制,这也是 @darkbaby123 说它更灵活一些的根本原因。

另外在混合范式的语言里,FP 和 OO 的特性经常是可以“互利互惠”的,比如说 JavaScript 的 pipeline operator 允许你这么来实现 mixin:

// without pipeline operator:
class Article extends Sharable(Editable(Model)) {
}

// with pipeline operator:
class Article extends Model |> Editable |> Sharable {
}

这是因为 JavaScript 的 mixin 本身就是一个 Function,它接收一个对象,返回扩展/修改后的新对象,而基于原型的 JavaScript 只需要一个对象就可以 extends(继承),于是就这样把 OO 和 FP 的特点都发挥出来了。

P.S. JavaScript 的 pipeline operator 目前还处于 proposal stage 1 的阶段,但可以使用 babel 来支持。FireFox 已经开始支持这一特性。

nightire 回复

链式调用的精髓是方法自身要返回 this? 记笔记

我怎么感觉像是在写 F#?

HDJ 回复

osx 方便写么

Babel 7.5.0 Released: dynamic import and F# pipelines https://babeljs.io/blog/2019/07/03/7.5.0

函数式特性又多了一丢丢

nightire 回复

链式调用的精髓是方法自身要返回 this 不理解啊。。。

zzz6519003 回复

这应该不难理解吧?我用 JavaScript 举例,典型的链式调用是这样:

o.foo().bar();

如果你把它分解成两段,那无非就是:

let n = o.foo();
n.bar();

换言之,o.foo 所返回的结果必然是一个拥有 bar 方法的对象,不然的话你怎么接着就调用 .bar() 呢?

o.foo 所返回的那个对象也应该是自己(this),这样才能让链式调用中链条上的每一个环节(方法)所处理的上下文(this)都是一致的。理论上你当然可以让 o.foo 返回其他的对象而不是 this,并且只要那个对象自身也有 bar 方法,那么这个链式调用的形式依然可以得到维持,但这种链式调用的意义不大,因为方法调用的上下文不断在变化,这既没有太大的现实意义,又使得代码晦涩难懂。如果一定要更换上下文,那不如分成不同的语句来调用(就像前面的例子一样),保证代码的可读性远比形式主义的“链式调用”更为重要。

另外举一个经典的例子,比方说 jQuery 就是以链式调用的 API 为它的特色的,你可以去读 jQuery 的源码,所有支持链式调用的方法,它们返回的必然还是 $(也就是链式调用最初封装好的 jQuery 选择器,它就是 jQuery 的 this)。

我们可以把:

$('#test').addClass('show').removeClass('hide')

写成:

let el = $('#test');
el = el.addClass('show');
el = el.removeClass('hide');

当然,后面两次对于 el 的重新赋值都是没必要的(但这么写也不会错,道理便是如此),因为 addClassremoveClass 方法返回的对象就是 el 自己,所以你完全可以把它们连起来,这就成了“链式调用”。

顶楼是错的,要讨论再开一帖吧。

話說在那條推上看到了熟悉的 ID……

难道楼主说的是我?哈哈

需要 登录 后方可回复, 如果你还没有账号请 注册新账号