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

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

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……

难道楼主说的是我? 哈哈

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