Julia: 建议:弃用然后删除功能管道

创建于 2017-01-30  ·  37评论  ·  资料来源: JuliaLang/julia

提议

弃用当前使用|>作为函数管道。 也就是说,语法x |> f将被弃用,取而代之的是正常的调用语法f(x) 。 在弃用期之后, Base.:(|>)将未定义。

此更改最初由 tkelman 在https://github.com/JuliaLang/julia/issues/16985#issuecomment -227015399 中提出。

关于函数管道的各种语法(特别是,参见 #5571)存在很多有争议的争论,其中包括模仿各种语言的论据。 那个讨论已经_ad nauseum_,我不想重新讨论它。 这不是本提案的目的。

基本原理

许多经过深思熟虑、维护良好的包已经实现了宏,这些宏为各种通用和特定用例提供了方便的管道语法。 示例包括Lazy.jlFunctionalData.jlPipe.jlChainMap.jl等。

StefanKarpinski 和 andyferris 在 #17155 中为我们提供了任意函数​​组合,在许多情况下都可以达到类似的目的。

正如 tkelman 在 #5571 中类似论证的那样,Base 中的函数管道与熟悉的调用语法相反; 在 Base 语言中同时使用这两者本质上是支持使用 2 种不同的语法来实现相同的目标。 虽然通常有多种方法可以使用 Base 中的解决方案编写相同的内容,但通常这些解决方案至少遵循类似的心智模型。 在这种情况下,句法使用了完全相反的心智模型。

函数管道通过在对象之后应用动作违反了最小意外原则。 也就是说,如果您阅读sum(x) ,当您看到sum()时,您会立即知道您要将参数中的值相加。 当您看到x |> sum时,您会看到x ,然后突然之间您将其值相加。 几乎没有其他基本解决方案将动作放在最后,这使得管道变得奇怪。

管道在其他语言中确实有先例,例如 Hadley Wickham 在 R 中的%>% (它不是基础 R 的一部分),有时这种风格/流程是有意义的。 但是,为了在 Base Julia 中保持一致性,我建议我们推迟为包提供管道语法的责任,这可以重新定义|>或提供他们认为合适的便利宏。

行动项目

如果该提案被接受,行动项目将是:

  • [ ] 删除 Base 中语法的使用,如果有的话
  • [ ] 在 0.6 或 1.0 中为Base.:(|>)提供正式的弃用
  • [ ] 在后续版本中将其删除
deprecation design julep

最有用的评论

如果我们要弃用它,我们是否也应该弃用*以进行字符串连接? 这具有与string(a, b)冗余类似的问题,并且鉴于ab不是数字,因此违反了最小意外原则。

更一般地说,我们可能应该弃用所有中缀表示法,因为有多种调用约定(如*(a, b)a * b )会让人感到困惑——我们可以将当前的 3 种不同的语法缩减为一种并获得完全的一致性。 为了避免丑陋,我们可能会考虑将函数调用移动到括号内,并且可能还去掉多余的逗号。

所有37条评论

函数管道为函数调用提供了后缀语法,这在 REPL 中便于交互式数据生成和进一步的可视化/汇总。

我见过很多人输入的一个用例是

julia> somecomplicatedthingproducingarray
...

<ARROW UP>

julia> somecomplicatedthingproducingarray |> summarize

其中summarize函数类似于绘图或直方图

@jiahao我并不是说它没有用,而是我们应该在 Base 中保持一致,让包提供这样的东西。

还有ans用于 repl 使用

在这个提案中, |>是否仍会被解析为中缀运算符?

@ajkeller34 :当然,包可以自由地做任何他们想做的事情(尽管它们必须在类型盗版和共存方面很好地相互配合),而没有与旧的语义兼容的限制基本定义。

删除 Base 中语法的使用(如果存在)

这是我现在非常过时的尝试: https ://github.com/tkelman/julia/commit/212727cdc4aaa3221763580f15d42cfe198bcc1c
当时,base 中的大多数用途都非常微不足道。 一些测试对“将这个东西通过管道传递给这个匿名函数”的使用可能会更好地使用管道,但是由于其中大多数都多次重用同一个匿名函数,因此可能值得给它一个名称并像调用它一样调用它那时的正常功能。

如果有人好奇,我现在有ChainRecursive.jl 。 一旦完成,我将发布关于 ChainMap.jl 及其各种子代的解体的讨论。

让我在这里提供一些阻力,因为我有一些既得利益并且特别喜欢|>使之成为可能的东西。

我赞同@jiahao ,当你想在 REPL 中快速尝试时, |>非常有用。 此外,我发现当你的论点太大或值得一些平衡时,它也很有用(是的,我说过) 。 在链接示例的情况下,实际上让参数比被调用的函数更突出更好。 sum(x)是一个太简单的例子,确实应该写成sum(x) )。 在 Escher.jl 中,所有向元素添加属性的函数都有一个柯里化方法。 这与|>非常吻合(这是计划好的,它也适用于map ),并且很高兴能够在行尾尝试一些东西并看到 UI 更新立即地。 我不必找到表达方式的开头并四处走动。 至少与 Escher 一起使用,建议的替代方法是将大表达式分配给组成名称的变量,如padded_box_contents_aligned_right_tomato_background (或更糟糕的box34 ),然后在它们上调用函数。 与精美的阅读相反<big UI expression> |> aligncontents(right) |> pad(1em) |> fillcolor("tomato")

我知道在这之后我可以在 Escher 中定义|>并且我可能会,但是看到WARNING: using Escher.|> in module YourPackage conflicts with an existing identifier.几乎肯定会给这个赋予不同的含义,这对我来说非常令人震惊!

StefanKarpinski 和 andyferris 在 #17155 中为我们提供了任意函数​​组合,在许多情况下都可以达到类似的目的。

box |> fill("orange") |> pad(2em)的替代品是(fill("orange") ∘ pad(2em))(box)而不是box |> fill("orange") ∘ pad(2em) ? 这两个似乎正交。

在我看来,Escher 使用闭包作为对象就像是为了使用这种语法而定义了一个 DSL(这对于任何不是单输入、单输出的东西都有严重的限制),在那里它可能会得到更好的服务,并且更通用,如果它使用多个可用的链接宏之一。

删除 Base 对此的定义将允许喜欢这种语法的人用它做更多有趣的事情。

@shashi我理解您的观点,但是您可以使用我在该问题中引用的软件包之一获得相同的行为,不是吗? 例如,在您的 Escher 示例中,您可以使用 FunctionalData 执行<strong i="6">@p</strong> vbox(<really big thing>) | pad(2em)或 Lazy 执行@> vbox(...) pad(2em)

删除 Base 对此的定义将允许喜欢这种语法的人用它做更多有趣的事情。

除非它不可用,因为使用它的唯一安全方法是Escher.|>(...)Lazy.|>(...)

假设,如果您使用两个定义和导出它的不同包,假设它没有在Base中定义,那么如何使用|>作为中缀运算符?

@kmsquire这取决于用例。 |>仍然会像现在一样被解析为中缀运算符,只是在 Base 中没有值。 如果您在宏中使用它,那么任何特定包如何定义它都无关紧要,因为它只是成为调用表达式中的第一个参数。

<|为例,它被解析为中缀运算符但没有值。 即使它是未定义的,我们仍然有

julia> dump(:(a <| b))
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol <|
    2: Symbol a
    3: Symbol b
  typ: Any

包可以为Base.:(<|)定义和导出方法,这些方法意味着不同的东西,就像使用+一样。

但是提供很好的函数管道的包在宏中这样做,我假设正是这个原因。

FWIW,没有链接包需要在评估期间使用|> ,因为在链接期间,所有内容都被压缩到一个表达式中。 我想如果包确实去定义|>它将恰好是 base.xml 中的定义。 尽管他们可能应该改用链接宏。 请参阅DataFramesMeta以获取有关如何构建与链接一起使用的接口的好示例。

如果我们要弃用它,我们是否也应该弃用*以进行字符串连接? 这具有与string(a, b)冗余类似的问题,并且鉴于ab不是数字,因此违反了最小意外原则。

更一般地说,我们可能应该弃用所有中缀表示法,因为有多种调用约定(如*(a, b)a * b )会让人感到困惑——我们可以将当前的 3 种不同的语法缩减为一种并获得完全的一致性。 为了避免丑陋,我们可能会考虑将函数调用移动到括号内,并且可能还去掉多余的逗号。

|>仍然会像现在一样被解析为中缀运算符,只是在 Base 中没有值。 如果您在宏中使用它,那么任何特定包如何定义它都无关紧要

仍然不确定为什么我们需要将其从 Base 中删除。

@bramtayl提出了一个很好的观点:

我想如果包确实去定义 |> 这正是定义是基础。

并且仍然使用多个定义它的包的唯一方法是不使用它中缀。

我不明白为什么要在宏内部使用|>需要删除 Base 中的定义。

它不是。 我的观点是,无论 Base 中的情况如何, |>都可以在宏内部使用。 任何正确解析的运算符也是如此。 提议的重点是使 Base 在函数调用方面自洽,然后可以通过包来实现管道行为。 软件包是否特别使用|>并不重要; 他们也可以使用<|或任何其他中缀运算符。

@ararslan对,这不是我要问的,我马上更新了我的评论,抱歉。

无论如何,我不太明白“在函数调用方面基本自包含”的情绪。 似乎这只会使在非宏上下文中使用|>变得更加困难。 我个人认为|>对于新手来说是值得学习的东西,尽管它令人惊讶。 它至少节省了 REPL 的工作量。 稍后意识到|>是一个函数,就像任何其他中缀函数一样,这很有趣,并强化了函数只是值的教训。

也许只是正式的东西:请在发布周期开始时讨论/决定弃用和/或语法更改,而不是在结束时。 目前所有主要的开发人员和包负责人都花费时间和精力完成 0.6,他们可能没有时间去想另一个(好)的想法。

“我并不是说它没有用,而是我们应该保持一致”

有时有用性胜过一致性? 我不知道不一致,但我发现|>语法很有用。 如果它被移除,我将不会觉得我获得了任何有形的东西。

如果可以的话,请解释我的反对票:

Base 中的大部分内容可能会发生在包中。 我们应该将字典移动到一个包中吗? 也许像排序和洗牌这样的列表操作? 收款操作等? 我确信关于 Base 中应该包含哪些内容和不应该包含哪些内容进行了长时间而详细的讨论,但我认为某些功能可能包含在 Base 中的原因有以下三个:
1)该功能是启用基础中的其他功能所必需的。
2) 该功能是该语言的重要组成部分,许多 Julia 程序员和包将使用它,因此希望有一个每个人都同意的单一实现/语法,而不是许多人自己滚动的碎片化。
3) 在 base 中包含该功能使“原始” Julia 使用起来更愉快或感觉更全功能,这有助于语言传播和采用。

sum这样的东西可能会达到所有 3 点,我认为函数管道会达到第二点和第三点:

在最初的(写得很好的)提案和这个线程的讨论中,一个共同的主题是存在几个通过宏提供类似管道功能的包:Lazy.jl、Pipe.jl、ChainMap.jl 等。的多个软件包强烈表明社区中的许多人发现管道是一个有用且理想的功能,并且这些软件包出现在这个讨论线程中表明这里的许多人理解并支持管道的使用。

鉴于管道在 Julia 社区和其他语言中是一个常见且流行的功能,即使在本次讨论中,人们似乎也同意它有很多用途,尤其是在 REPL(Julia 大放异彩的地方),并且 Julia 生态系统中已经存在碎片化。 ..我的阅读不是应该从 Base 中删除它,而是应该增强 Base 中可用的管道语法,以便减少对碎片的需求。 提供不同方式的不同软件包,例如绘图似乎没问题; 不同的流行包提供不同的应用功能方式似乎很可怕。

我进一步认为,从 Base 中移除管道但留下中缀运算符是相当令人惊讶的:在 Julia 中,您不能定义自己的中缀运算符,但是有一个未使用的中缀运算符|>挂在附近,您可以将其定义为你先请? 如果这是一个很好的功能,为什么不给我们一个可靠的 10 或 20 个中缀运算符来定义呢?

最后,我相信保持管道精确是很自然的,因为它与其他功能应用程序不同。 这是一个特性,而不是一个错误,它不同于其他应用函数的约定; 这种差异使它在某些用例中大放异彩。 在其他情况下(稍微挥手)名词出现在动词之前,其中许多是在原始函数应用程序难以处理的情况下的语法糖。 在我的脑海中,赋值x = 5将名词(符号x )放在动词之前(绑定到一个值)。 同样用于访问类型t.a而不是getfield的字段。 最深刻的是,数组索引z[5]读起来像“从z获取第 5 个元素”,通常比getindex(z, 5)更自然。

如果这是一个很好的功能,为什么不给我们一个可靠的 10 或 20 个中缀运算符来定义呢?

如果除了无人认领的 ASCII 代码(如<|++ 、 ...

没有阅读整个线程 - 但只是想说
我喜欢能够吹奏。 我会投票有用
任何一天的一致性。

我有一个非常温和的保留它的偏好,但只要它仍然是一个中缀运算符,我就不在乎了。 我觉得如果需要导入一个包,我可能不会使用函数管道,这告诉我我不太重视它。

话虽如此,我认为这种“最小意外原则”的论点并不令人信服,因为它对多样化的用户群做出了一些假设。 对于母语-宾语-动词语言的母语人士来说,我想 Julia 的大部分语法都违反了最小意外原则,而且函数管道相当舒服......

没有阅读整个线程

😕

我喜欢能够管

同样,我并不是说一个人不应该能够管道,而是在现有的几个管道包之一中可以轻松地获得该功能。 删除 Base 管道允许包更轻松地定义自己的管道语义,而无需遵守或保持与 Base 提供的任何内容保持一致。

在 Julia 中,您无法定义自己的中缀运算符

这不是真的; 任何被解析为中缀运算符的东西都可以被定义或重新定义。 正如马丁霍尔特斯指出的那样, <|++也同样可用,等等。

我对此持中立态度,但我会认为|>从正常的函数调用语法倒退是它的重点。 即使是管道的最大粉丝也不会要求(AFAIK)例如sin <| x因为这对于sin(x)确实是多余的。 |>适用于那些眼睛和/或大脑更容易想到数据从左到右流动而没有很多括号的情况。

我希望|>更强大,例如允许x |> f(_) + 2g(_) |> h等,并且它不仅仅是一个操作员。 每次有人将x |> f定义为除了f(x)之外的其他东西时,它真的让我感到困惑,因为我们使用的运算符的全部意义在于它是一种不同顺序的调用语法。 由于我们可以重载呼叫,我看不出有充分的理由让x |> f意味着别的东西。

@StefanKarpinski已经可以使用宏获得更强大的管道。 请参阅例如Pipe.jl ,它提供了您所描述的语法。 只要|>是一个运算符(我个人认为|>不值得特殊情况),宏就可以使用任何解析中缀的管道分隔符,即使它不是:call 。 例如,可以类似地使用@~进行管道传输(至少在撰写本文时)。 这种灵活性是在 Julia 中使用宏的优势之一。

我们可以将 Pipe.jl 的功能添加到语言中,然后您无需编写@pipe即可拥有它。

弃用|>的主要原因是,如果我们想为人们更喜欢的其他目的回收语法。

我想我试图争辩说管道不需要成为语言的一部分,它可以(并且已经)存在于一个包中。

但是,如果没有其他我们想要|>的东西,我认为不理会它的(微不足道的)定义没什么坏处。

我不相信目前有任何建议可以在 Base 中重新调整|>的用途。 我没有在 Base 中定义它的论点是它在不损失功能的情况下给了我们更多的一致性。

如果不用担心或解决这个现有的定义,任何“更强大的管道”提案或包实现会变得更简单吗?

@ararslan “那不是真的;任何解析为中缀运算符的东西都可以定义或重新定义。”

从手册“&& 和 || 运算符”中,它们被解析但无法重新定义(这是一件好事)。 我相信唯一的例外。

所谓的“逻辑运算符” && 和 || 是中缀。 [一元二元关系]“运算符”是恕我直言,对它们来说是不正确的术语,因为它们不是。 Not 类似于逻辑按位 & 和 | 确实允许重载(我不确定这是一个不错的选择)。

@PallHaraldsson这些是控制流,而不是与&|+等意义相同的运算符。

如果可能的话,让我们试着在这里保持话题,拜托。

@tkelman这是一个很好的观点。 我怀疑我们可以使未来的管道语法向后兼容。 例如,如果_是保留的,那么当它的参数包含_|>可以具有特殊含义,否则执行与现在相同的操作。

还有另一个问题:要使|>为您的对象工作,您是定义|>还是“函数调用运算符”(即向其添加方法)? 如果|>是函数调用的内置语法可能会更简洁,以确保f(x)x |> f始终相同。

这里的共识很明显是反对的,所以我会继续关闭这个问题。 我感谢大家的讨论。

我知道这个问题已经结束了。 只想对保留运营商说“谢谢”。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

yurivish picture yurivish  ·  3评论

dpsanders picture dpsanders  ·  3评论

wilburtownsend picture wilburtownsend  ·  3评论

manor picture manor  ·  3评论

i-apellaniz picture i-apellaniz  ·  3评论