感觉管道运算符与链式调用有一定的重复性

如果 moonbit 把第一个参数名为 self 的限制放开,
像 dlang 一样的 Uniform Function Call Syntax (UFCS),
moonbit 就完全不需要管道这个运算符了

initial
  |> function1
  |> function2(other_arguments)

就可以写成

initial
    .function1()
    .function2(other_arguments)

允许 dot 调用普通函数的话,调用第三方包里的函数不太方便。

另外在 MoonBit 里普通函数和方法(第一个参数为 self,或者显式定义成 T::method)在语义上是不一样的。方法可以用来给 trait 提供实现,普通函数不行。所以把它们通过 pipe/dot 区分开的话,程序的语义会更简单和清晰

包、struct、trait 都有名字空间的共性,如果访问其内名字他们都用 ::,对于第三方包应该就不存在问题了,这样也统一了所有命名空间名字的访问方式:

initial
    .func()
    .pkg_name::func(...)
    .trait_name::method(...) 
    .method_name(...)              // 当前命名空间内有 method_name 的 trait 方法

现在的 pipe 就是这样的:

initial
|> func()
|> @pkg_name.func(...)
|> Type::method_name(...)
|> Trait_name::method_name(...)

只不过调用外部类型的方法时,用 pipe 要写成 @pkg.Type::method,比较啰嗦。所以这时用 . 更好

理解你的意思,
只是感觉 pipe 操作符与链式调用是一个东西,
如果把它们统一了,就减少了一个语法特性,语言内核就更小了

是的,它们确实是可以统一的。直接上 UFCS 确实是另一种可能的 tradeoff。目前的设计主要是综合考虑 语义简单 + 常用场景使用起来比较轻量

同一个事情有多种语法可以来做,从我的角度来说会很难受。
团队风格很难统一,不希望再通过额外的配置和工具来约束。

现在其实每件事情都有一个明显最佳的方法来做。如果是有 self 的方法就用 .method(),如果是普通函数那就用 |>。但有一个问题是现在 .|> 没法在同一个管线里混排,这个我们可能会想办法改进一下

3 个赞

赞同 这个说法 :grinning:

同样让我有选择困惑的还有 T::method 语法糖

这个同样是鼓励能用 . 就用 .。但不是所有方法都可以用 . 来调用,比如 defaultfrom_int 之类的。此外把方法做高阶函数用的时候没办法用 .。这时候就必须有 T::method 这么个显式调用方法的语法。

1 个赞