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 这种不用包括号,看不到有什么好处,反而占用了一个宝贵的操作符
你换个频道还是在杠,已经告诉你了 Sorbet 现在已经开源,Ruby 3 的时候会作为官方标准(给所有标准库加上类型标注)
其次,“静态类型检测”是个什么东西?从来没有这种玩意
语法不管怎么改,含义还是类似 Elixir 的 pipe,把上一个操作的返回值变成下一个操作的某个参数,让代码组合变得更容易。有些场景下 pipe 比链式调用泛用性更高。链式调用的缺点之一是必须把函数混入进某个 object,导致 object 承载多个部分的功能然后变得臃肿。
这事反弹还有个因素,就是大家都觉得现在提供的功能不是大家想要的,对此 Matz 的回应非常不友好,等过了好久才有 Rei 贴的那个回复,讲了下 Elixir 风格的管道在 Ruby 上不好实现(其实我没看懂)
我只能理解他可能从语言的理论上说这事不好做,或者是有什么 overhead 在里面(比如在虚拟机层面没法高效实现这样的),但是他有没讲清楚。
我不太关注 Ruby 本身的改进(除了别人喂的),但我知道之前有些很 cool 的东西,比如 auto fiber,大面都能实现,而且改善明显,但是有一些 edge 完全搞不定,就不 generic 了,于是只能砍掉的例子,其实我觉得 ko1 要搞得新并发模型 Guild 很可能也是这下场。。。。
按 matz 之前的意思
load_http("http://example.com")
|> parse_document
|> scrap
就是
load_http("http://example.com").parse_document.scrap
对,没什么用。
@zzz6519003 @Rei 如果是这样,那确实没有用。反而怎么写都没有 .
简单。从语言层面来说我觉得 Ruby 也没必要关注“给现有功能多一种表达方法”的事情,这就不是语言设计者该做的事情。
@jasl Lisp 据我对 Emacs Lisp 有限的了解,它允许 variable 和 function 用同一个名字,比如变量和方法都叫 foo
,执行再会根据使用场景辨别到底是哪一种。这大概是 Matz 说它们不属于同一个 namespace 的意思。不过这跟 Ruby 有什么关系,以及为什么会影响这个 issue 就不知道了。
ruby 搞这个的意义,我是不知道了,elixir 没对象,没管道要一层一层套,ruby 有对象直接用点感觉更方便,elixir 写多了感觉还是挺爽的,就是要改下思维
最急需加入的就是类似 python 的类型标注,可加可不加,不强制。
def func(name: str) -> str:
return "Hello" + name
只拿 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 的方式,反过来未必成立。
这不是我想表达的重点…… 其实都是两种语言里的不同特性,放一起比较也没什么意义。想了解你可以看看 Elixir。
在混合范式语言里,比如 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 已经开始支持这一特性。
Babel 7.5.0 Released: dynamic import and F# pipelines https://babeljs.io/blog/2019/07/03/7.5.0
这应该不难理解吧?我用 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
的重新赋值都是没必要的(但这么写也不会错,道理便是如此),因为 addClass
和 removeClass
方法返回的对象就是 el
自己,所以你完全可以把它们连起来,这就成了“链式调用”。
update: 这个特性已经被移除了 https://github.com/ruby/ruby/commit/2ed68d0ff9a932efbc4393c869534040dec8f647