Julia: 自定义中缀运算符

创建于 2016-06-17  ·  65评论  ·  资料来源: JuliaLang/julia

https://groups.google.com/forum/#!topic/julia -dev/FmvQ3Fj0hHs 有一个关于为自定义中缀运算符创建语法的讨论。

...

编辑添加注释: @johnmyleswhite指出下面的评论线程是对自行车的邀请。 请不要发表新评论,除非您有真正的新内容要添加。 下面有几个建议,用“万岁”表情符号(爆炸锥)标记。 您可以使用这些图标跳过讨论而只阅读提案,或者找到不同的提案,以便您投票“赞成”或“反对”。

对这个 bug 的赞成/反对票是关于你是否认为 Julia 应该有任何自定义的中缀习语。 以下具体想法的赞成/反对票应在@Glen-O 的第一条评论中进行。 (在澄清之前,该错误有 3 次反对票和 1 次赞成票。)

...

初始提案(仅历史兴趣):

似乎获胜的提议是:

    a |>op<| b #evaluates (in the short term) and parses (in the long term) to `op(a,b)`

为了完成这项工作,只需要进行微小的更改:

  • <|的优先级放在 $#$ |> $#$ 的优先级之上,而不是相同。
  • 使<|组从左到右。
  • 制作函数<|(a,b...)=(i...)->a(i...,b...) 。 (正如讨论线程中所指出的,这将具有独立用途,以及在上述成语中的用途)

选修的:

  • 创建具有适当优先级和分组的新函数>|(a...,b)=(i...)->b(a...,i...)|<(a,b...)=a(b...)

    • pipe first 表示求值,而 pipe last 将其维护为一个函数,而 > 和 < 表示哪个是函数。

  • 创建具有适当优先级和分组的新函数>>|(a...,b)=(i...)->b(i...,a...)<<|(a,b...)=(i...)->a(b...,i...)
  • 为 |> 创建同义词»和(/或) pipe |>«和(/或) rcurry<| ; 和(/或) lcurry<<| ; 单字符同义词用作中缀运算符。
  • 在 base 中创建一个@infix宏,它执行下面的第一个解析器修复。

长期:

  • 教解析器将a |>op<| b更改为op(a,b) ,因此在运行代码时不会涉及额外的开销,因此实际上可以在中缀位置定义运算符。 (这类似于解析器当前对二进制a:b和三进制a:b:c的不同处理方式。为了最大程度的可定制性,它应该对匹配的同义词执行此操作,而不是对不匹配的同义词执行此操作,例如a |> b « c仍将被视为两个二元运算符。)
  • 教解析器理解逗号和/或空格,以便上述定义中的省略号按预期工作而无需额外的括号。

(与 https://github.com/JuliaLang/julia/issues/6946 有关)

parser speculative

最有用的评论

斯特凡不比我大。

所有65条评论

呼应 julia-dev 线程,我认为引用 Stefan 对此提案的主要评论会很有用:

只是为了在这里设定期望,我认为在 Julia 1.0 之前不会有太多的“语法创新”。 (我能想到的唯一例外是新的f.(v)向量化调用语法。)虽然通过某种方式使任意函数表现为中缀运算符可能很好,但这并不是语言中的紧迫问题。

作为参与过 Julia 开发历史的人,我认为将精力集中在语义变化而不是句法变化上会更好。 在 Julia 达到 1.0 之前,还有许多极其重要的语义问题需要解决。

请特别注意,实现此功能不仅仅是作者需要考虑的一次性差异:每个人都必须考虑他们的工作如何与此功能交互,因此更改实际上增加了长期每个在解析器上工作的人的工作量。

我认为 johnmyleswhite 的评论对于建议的“长期”解析器更改非常中肯。 但是,据我所知,“小改动”和“可选”组非常独立且影响很小。

也就是说:启用此提案的最小版本所需的解析器更改仅涉及普通二元运算符的优先级和分组,在其他情况下或多或少是例行公事的那种更改。 从事无关事物的解析器开发人员不需要跟踪这一点,就像他们需要跟踪所有许多已经存在的运算符的含义一样。

就我个人而言,我发现这种语法非常丑陋且难以输入。 但我同意拥有更通用的中缀语法会更好。

我认为考虑这个问题的正确方法是仅作为语法问题:您想要的是使用带有中缀语法的op ,因此定义其他函数和运算符来获得它是迂回的。 换句话说,这一切都应该在解析器中完成。

我实际上会考虑为此回收|并使用a |op| b 。 可以说,通用中缀语法比按位或更重要。 (我们之前讨论过回收位运算符;它们看起来确实有点浪费语法。)

a f b在数组连接和宏调用语法之外可用。

a f b可能会起作用,但它似乎很脆弱。 想象一下,试图向某人解释为什么a^2 f b^2 f c^2是合法的但a f b ca+2 f b+2 f c+2不是。 (我知道,最后一个假设优先级是 prec-times,但无论优先级是什么,这种一般的事情都是一个问题)。

至于a |op| b :最初我赞成一个类似的提议a %op% b ,正如您在 google groups 线程中看到的那样。 但是提议的|><|的好处是它们每个都可以作为二元运算符单独使用,并且它们自然地结合起来可以按需要工作(即,给定正确的优先级和分组。 ) 这意味着您可以使用现有的解析器机制在短期内实现这一点,从而避免将来给解析器开发人员带来麻烦,正如我在上面对 johnmyleswhite 的回复中所说的那样。

因此,虽然我喜欢a |op| b并且当然不会反对它,但我认为我们应该寻找一种方法来使用两个不同的运算符来简化所需的解析器更改。 如果我们追求最大的可输入性,而不是反对让|表示“管道”而不是“按位或”,那么a |op\\ ba |op& b呢?

“解析器开发人员的头疼”是最低可能的担忧。

“解析器开发人员的头疼”是最低可能的担忧。

作为解析器开发人员,我明确同意这一点。

|><|都是非常好的中缀运算符,但是使用其他两个运算符实现通用运算符语法的好处为零。 关于这种语法有多冗长和不吸引人,还需要说更多。

使用其他两个运算符实现通用运算符语法的好处为零。

需要明确的是,这里的长期愿景是二进制f <| y 、二进制x |> f和三进制x |> f <| z ,其中第一个只是一个函数,但第二个两个在解析器中实现为转换。

可以使用两个普通函数|><|来实现这一点的想法只是实现这一愿景的临时桥梁。

关于这种语法有多冗长和不吸引人,还需要说更多。

这是一个公平的观点。 用|&替换|><| #$ 怎么样? 它们作为一对和单独的都是有意义的,尽管它们对曲棍球运动员来说可能有点刺耳。

为此同时窃取|&并不是一个好的 ASCII 分配,我怀疑很多人更喜欢分隔符是对称的。

如果人们出于其他原因想要x |> f <| y三元运算符,那很好,但我认为应该单独考虑。 我不确定解析器是否应该将|>转换为翻转的<| 。 其他类似的运算符,如<不能那样工作。 但这也是一个单独的问题。

偷两个| 和 & 因为这不是 ASCII 的良好分配,我怀疑许多人更喜欢分隔符是对称的。

行。

我知道><很难输入。 就标准键盘上的对称性和可打字性而言,我想最简单的可能是&%%&之类的东西,但这非常难看,R 并行与否。 /||/可能也值得考虑。

...

我不确定解析器是否应该将 |> 转换为翻转的 <|

我想你误会了。 a |> b应该解析为b(a) 。 (没有特殊解析的版本是((x,y)->y(x))(a,b) ,它的计算结果相同,但开销更大。)

a |> b 应该解析为 b(a)

啊,好的,明白了。

我认为我们可以讨论多年使用哪些角色。 我相信@StefanKarpinski (作为迄今为止这次谈话中最资深的人)做出裁决,我会接受的。 即使这是我反对的东西(例如a f b 。)

这里有一些选项可以查看吸引人的地方:
a |>op<| b (保持当前|>不变)
a |{ op }| b (在许多常见键盘上接近且相同的移位状态,不太难看。作为独立键盘有点奇怪。)
a \| op |\ ba /| op |/ b或它们的组合
a $% op %$ b (相对可打字,R-inspired。但有点难看。)
a |% op %| b
a |- op -| b
a |: op :| b
a | op \\ b
a | op ||| b
a op b

斯特凡不比我大。

看起来好像你刚刚提名了自己,然后,在这个问题上的 BDFL 权力! ;)

a @op@ b ?

我想我的投票是使用\||\/||/中的所有 4 个。 向下评价,向上咖喱; 向功能栏。 所以:
a \| f (或f |/ a )-> f(a)
a /| f (或f |\\ a )-> (b...)->f(a,b...)
f |\ b (或b //| f )-> (a...)->f(a...,b)
因此:
a \| f |\ b (或a /| f |/ b )-> f(a,b)
a \| f |\ b |\ c (或a /| b /| f |/ c )-> f(a,b,c)

除了|/之外,4 个主要运算符中的每一个都单独有用。 冗余肯定不是 Pythonic,但我认为逻辑上的整洁是 Julian。 实际上,您可以使用您认为更容易键入的任何版本的中缀习语; 它们都具有同等的可读性,因为一旦你学会了一个,你自然会理解两者。

显然,如果您交换所有斜线,这将同样有意义,因此向上箭头用于评估,向下箭头用于柯里化。

我仍在等待 On High 的消息(我为我的新手在猜测这意味着什么时的笨拙表示歉意)。 但是,如果任何比这个自行车棚更高的人做出裁决,对于这个或任何其他具有至少两个新符号的版本,我很乐意编写一个短期补丁(使用函数)和/或一个适当的补丁(使用转换)。

我们尽量避免使用 BDFL :)

我只是想我会注意一些快速的事情。

首先,提出的符号的另一个好处(“独立使用”)是<|可以在其他上下文中使用,以提高可读性。 例如,如果您有一个字符串数组A ,并且想要将它们左侧的所有内容填充到 10,那么现在,您必须编写map(i->lpad(i,10),A) 。 这是相对难以阅读的。 有了这个符号,它就变成map(lpad<|10,A) ,我想你会同意它明显更干净。

其次,这背后的想法是保持符号一致。 已经有一个|>运算符,用于将函数调用的“修复”从前缀更改为后缀。 这只是扩展符号。

第三,使用直接中缀作为a f b的可能性有一个更大的问题。 a + ba * b最终必须具有相同的优先级,因为+*是函数名,并且系统不可能具有可变优先级。 那,或者它必须以不同的方式对待现有的中缀运算符,这可能会导致混淆。

例如,如果您有一个字符串数组A ,并且想要将它们左侧的所有内容填充到 10,那么现在,您必须编写map(i->lpad(i,10),A) 。 这是相对难以阅读的。 有了这个符号,它就变成map(lpad<|10,A) ,我想你会同意它明显更干净。

我坚决不同意。 建议的语法是——请原谅我——ASCII 沙拉,接近 Perl 和 APL 的一些最严重的冒犯,在其他语言中没有先例,可以让不经意的读者了解正在发生的事情。 当前的语法虽然长了几个字符(五个?),但任何知道i->expr是 lambda 语法的人都非常清楚——它在大量且不断增长的语言中使用。

a + b 和 a * b 最终必须具有相同的优先级,因为 + 和 * 是函数名,并且系统具有可变优先级是不可行的。 那,或者它必须以不同的方式对待现有的中缀运算符,这可能会导致混淆。

我不认为这是一个真正的问题。 我们可以只说a f b中缀的优先级是什么,并保留所有现有的优先级。 这是可行的,因为优先级是由函数名决定的; 任何名为“+”的函数都将具有“+”优先级。

是的,我们已经为1+2 in 1+2语法这样做了,这不是问题。

我不认为这是一个真正的问题。 我们可以只说 afb 中缀的优先级是什么,并保留所有现有的优先级。 这是可行的,因为优先级是由函数名决定的; 任何名为“+”的函数都将具有“+”优先级。

我并不是说编写解析器使其工作很困难。 我的意思是它会导致一致性问题,因此我说“或者它必须以不同的方式对待现有的中缀运算符,这可能会导致混淆”。 除其他外,请考虑¦在概念上看起来并没有什么不同,但其中一个是预定义的中缀运算符,而另一个则不是。

我坚决不同意。 建议的语法是——请原谅我——ASCII 沙拉,接近 Perl 和 APL 的一些最严重的冒犯,在其他语言中没有先例,可以让不经意的读者了解正在发生的事情。 当前的语法虽然长了几个字符(五个?),但任何知道 i->expr 是 lambda 语法的人都非常清楚——它在大量且不断增长的语言中使用。

也许我应该更清楚我在说什么。 我是说能够将操作描述为“lpad by 10”比i->lpad(i,10)所做的要清楚得多。 在我看来, lpad<|10是你能得到的最接近的,以非特定于上下文的形式。

如果我描述我来自哪里,也许会有所帮助。 首先,我是一名数学家和数学物理学家,而“lambda 语法”虽然从编程的角度来看是明智的,但对于那些在编程方面经验不足的人来说并不是最清楚的。 据我了解,Julia 的主要目标是成为一种科学计算语言,因此与 MATLAB 非常相似。

我必须问 - lpad<|10x|>sin|>exp还多是“ASCII 沙拉”吗? 但是添加了|>符号。 与 bash 脚本相比,其中|用于将左侧的参数传递给右侧的命令 - 如果您知道它被称为“管道”,它会_little_更有意义,但如果您'不擅长编程,这没有意义。 在这方面, |>实际上更有意义,因为它看起来隐约像一个箭头。 然后<|是符号的自然扩展。

与其他一些建议相比,例如%func% ,它_确实_在另一种语言中有先例,但对于没有广泛的编程知识的人来说是完全不透明的语言编程知识。

请注意,我回顾了一个较早的讨论,我发现在另一种语言中使用了一种在理论上会非常好的表示法。 Haskell 显然使用a |> b c d来表示b(a,c,d) 。 如果函数名后面的空格允许您以这种方式指定“参数”,它会很好地工作 - map(lpad 10,A) 。 唯一的问题是一元运算符 - 例如, map(+ 10,A)会产生错误,因为它会解释为 "+10" 而不是i->+(i,10)

a f b上:优先级问题可能没有 Glen-O 建议的那么糟糕,但除非用户定义的中缀函数具有最低的优先级,否则它们确实存在。 说,为了争论,我们给他们预先时间。 在这种情况下,
a^2 f b^2 => f(a^2,b^2)
a+2 f b+2 => a+f(2,b)+2
a^2 f^2 b^2 => (f^2)(a^2,b^2)
a f+2 b => 语法错误?

这都是您编写解析器的自然结果,因此从这个意义上说,这并不是特别令人头疼的事情。 但对于该习语的普通用户来说,它并不是特别直观。

关于咖喱习语的用处
我同意 Glen-O 的观点,即(i)->lpad(i,10)lpad<|10差(或者,如果我们这样选择, lpad |\ 10或其他)。 i是完全无关的认知负担和潜在的错误来源; 事实上,我发誓,当我刚才打字的时候,我最初无意中输入(i)->lpad(x,10) 。 所以,在我看来,进行中缀咖喱操作是个好主意。
但是,如果这是我们的意图,那么无论我们选择什么中缀习语,我们都可以创建自己的 curry 操作。 如果是a f b ,那么类似lpad rcurry 10就可以了。 重点是可读性,而不是击键。 所以我认为这只是<|的一个弱论点。

a |> b c d
我非常喜欢这个提议。 我认为我们可以让|>接受两边的空格,所以a b |> f c d => f(a,b,c,d)

(注意:如果我对a b |> f c d的建议和 Glen-O 对map(lpad 10,A)的建议,这确实会产生一个极端情况: (a b) |> f c d => f((x)->a(x,b),c,d) 。但我认为这是可以忍受的。)

在运算符优先级方面,这仍然存在与a f b类似的问题。 但不知何故,如果您至少可以根据运算符|>的优先级来谈论它们,而不是的三元运算符的优先级,我认为它们会更容易接受

在 0.5 上尝试lpad.(["foo", "bar"], 10) 。 现有的|>并不是所有人都喜欢的。

@tkelman :我看到了这个问题,但你的意思是什么? 您认为我们应该先修复现有的|> ,然后再为其添加额外用途? 如果是这样,怎么做?

我个人认为我们应该摆脱现有的|>

在 0.5 上尝试lpad.(["foo", "bar"], 10) 。 现有的|>并不是所有人都喜欢的。

我认为你错过了重点。 是的, func.()表示法很好,并且在某些情况下绕过了这个问题。 但是我使用map函数作为一个简单的演示。 任何将函数作为参数的函数都将受益于此设置。 作为一个例子,纯粹为了证明我的观点,您可能希望根据一些数字的最小公倍数和一些参考数字对它们进行排序。 哪个看起来更整洁,更容易阅读: sort(A,by=i->lcm(i,10))sort(A,by=lcm 10)

我想再次指出,定义中缀运算符的任何方式都将允许创建一个运算符来执行 Glen-O 想要<|做的事情,这样在最坏的情况下他将能够编写类似sort(A,by=lcm |> currywith 10)的东西a ... f ... b => f(a,b) 。 我理解现有的|>或提议的<|是否值得运营商与这一点有一定的关系,但让我们尽量不要走得太远。

就个人而言,我认为a |> b c提案是迄今为止最好的提案。 它遵循 Haskell 的现有约定; 它在逻辑上与现有的|>运算符相关; 它既易于阅读,又易于打字。 我觉得它自然延伸到其他用途的事实是次要的。 如果您不同意,请至少提及您对核心成语的感受,而不仅仅是建议的次要用途。

我的意思是它会导致一致性问题,因此我说“或者它必须以不同的方式对待现有的中缀运算符,这可能会导致混淆”。

我同意很难决定a f b的优先级。 例如in显然受益于比较优先级,但很可能许多用作中缀的函数不需要比较优先级。 但是我没有看到任何一致性问题。 不同的运算符有不同的优先级。 添加a f b不会强制我们的牌给予+*相同的优先级。

请注意, |>已经具有与比较相邻的优先级。 对于任何其他优先级,坦率地说,我认为括号很好。

如果您不同意我的观点,并且如果我们使用a |> f b ,那么可能会有类似的运算符|+>|*>|^> ,其中工作方式与|>相同,但优先于其中央操作员。 我认为这是矫枉过正,但这是一种可能性。

解决优先级问题的另一种可能性是使用包含某种括号的自定义中缀运算符的语法,例如(a f b)

相关讨论: https ://github.com/JuliaLang/julia/issues/554、https://github.com/JuliaLang/julia/issues/5571、https : //github.com/JuliaLang/julia/pull/14476https://github.com/JuliaLang/julia/issues/11608https://github.com/JuliaLang/julia/issues/15612。

我必须问 - lpad<|10 比 x|>sin|>exp 是什么“ASCII 沙拉”? 然而 |> 符号被添加了。

我想@tkelman争论

我们应该摆脱现有的|>。

部分原因是_both_ lpad<|10x|>sin|>exp冒险进入 ASCII 沙拉领域:)。

我认为@toivoh(a f b)带有强制括号,是迄今为止最好的建议。

https://github.com/JuliaLang/julia/issues/11608相关(因此也与 https://github.com/JuliaLang/julia/issues/4882 和 https://github.com/JuliaLang/julia/pull /14653): 如果(a f b) => f(a,b) ,那么(a <strong i="8">@m</strong> b) => (<strong i="10">@m</strong> a b)是有意义的。 这将允许用普通(因此更加透明) (y @~ a*x+b)替换y ~ a*x+b的现有特殊情况宏逻辑。

此外,“parens required”可能是简洁中缀定义的首选习语。 与其说(用一个愚蠢的例子) a + b = string(a) * string(b) ,不如(通过 lint 工具或编译器警告)鼓励你说(a + b) = string(a) * string(b) 。 我意识到这实际上并不是为中缀选择“需要括号”选项的直接结果,但它是一个方便的习惯用法,可以让我们警告人们在 LHS 上错误地使用中缀,但解雇那些在 LHS 上使用中缀的人目的。

我目前的感觉是,如果您正在应用函数中缀(而不是前缀),
那么它就是一个运算符,并且应该看起来和行为都像一个运算符。

我们支持使用 unicode 定义的中缀运算符。
https://github.com/JuliaLang/julia/issues/552

我想这可能会很好,这样您就可以像在原始建议中一样添加关键字。
所以我们可以有,例如, 1 ⊕₂ 1 == 0
能够为您的中缀指定任意名称似乎有点过分。

应该看起来像操作员。

我同意中缀运算符应该有严格的命名约定。 例如:一个unicode字符,或以介词结尾。 但那些应该是有机发展的约定,而不是编译器强制执行的要求。 当然,我不认为#552 是故事的结局。 如果有几十个硬编码的运算符,那么应该有一种方法可以以编程方式添加更多,即使只是用于原型设计新功能。

...

对我来说, (a f b) (和(a <strong i="10">@m</strong> b) )提案在这个 bug 中比其他提案更胜一筹。 我几乎想制作一个补丁。

(a f b) => f(a,b)
(a f b c d) => f(a,b,c,d)
(a f) =>语法错误
(a+2 f+2 b+2) => (f+2)(a+2,b+2)
(t1=a t2=f t3=b) => (t1=f)((t2=a),(t3=b)) (空格的优先级最低,如在宏中)

...

我提交补丁会不会不合适?

我不明白最后两种情况:

(a+2 f+2 b+2)=>(f+2)(a+2,b+2)
(t1=a t2=f t3=b)=>(t1=f)((t2=a),(t3=b))

我发现(a f b c d)语法很奇怪。 既然1 + 2 + 3可以写成+(1,2,3)那么f(a,b,c)不应该写成(a f b f c)吗?

总的来说,我个人不相信 Julia 应该支持超出当前允许的自定义中缀运算符。

我可以看到(a f b c d)的两个问题。

首先,当您有一个更复杂的表达式时,将难以阅读 - 括号令人沮丧的原因之一是,通常很难一眼看出哪些括号与哪些其他括号配对。 这就是为什么首先需要中缀和后缀 ( |> ) 运算符的原因。 后缀尤其受欢迎,因为它允许从左到右进行漂亮、整洁的阅读,而不必每次都处理括号。

其次,它没有办法很好地完成像元素化这样的事情。 我的理解是f.(a,b)将成为 0.5 中的一个符号,以使f在其参数上通过广播进行元素操作。 如果它是(a f b) ,那么用中缀符号做同样的事情就没有好办法了。 充其量只能是(a .f b) ,在我看来,这失去了.(.+.* $ 所提供的对称性。

例如,比较想要使用 Haskell 中的示例的情况。 #6946 上的 shashi 提出了与此处相同的观点。 在 Haskell 中,你会写circle 10 |> move 0 0 |> animate "scale" "ease" 。 使用这种表示法,它变成((circle(10) move 0 0) animate "scale" "ease") ,它并不比animate(move(circle(10),0,0),"scale","ease")更清晰。 如果你想将你的圈子复制到多个地方,使用|>符号,你可能有circle 10 .|> copy [1 15 50] [3 14 25] 。 在我看来,这是实现这个想法的最巧妙的方式——然后,括号起到处理操作顺序问题的正常作用。

正如我已经指出的那样, a|>f b c的好处是还有一个自然扩展,允许相同的符号有更多的用途—— f b c将解析为“function f with参数bc设置),因此相当于i->f(i,b,c) 。这使它不仅可以用于中缀,还可以用于您可能想要传递的其他情况带有参数的函数(尤其是内置函数)(注意标准是首先具有函数的对象)。

|>也明确了哪个是函数。 例如,如果您有(tissue wash fire dirty metal) ,那么一眼就很难将wash识别为函数。 另一方面, tissue|>wash fire dirty metal有一个大指标,上面写着“ wash是函数”。

一些最新的反对意见在我看来就像是在说“但你可以滥用这个功能!” 我的回答是:当然可以。 如果您愿意,您已经可以使用宏编写完全不可读的代码。 解析器的工作是启用合法使用; 为了制止滥用行为,我们有惯例/成语,在某些情况下还有 deinters。 具体来说:

我不明白最后两种情况:

无论如何,这些都不是要效仿的例子; 它们只是展示了优先规则的自然后果。 我认为最后两个例子都有资格滥用语法,尽管(a^2 ಠ_ಠ b^2) => ಠ_ಠ(a^2,b^2)已经足够清楚了。

f(a,b,c) 不应该写成 (afbfc)

坦率地说,我提出的(a f b c d)是事后才想到的。 我认为这是有道理的,我可以举出一些有用的例子,但如果它有争议,我不想在这个问题上挂起这个提案。

[例如:

  • f 是对象 a 的“对象方法”,可能很复杂,使用 b、c 和 d,可能更简单。
  • f 是一种“自然广播”方法,例如push! ]

虽然如果f+那样 $ (a f b f c)是有意义的,但我认为大多数运算符实际上并不像+ ,所以它不应该是我们的模型。

当你有一个更复杂的表达式时,它会很难阅读

同样,我的回答是,“所以不要滥用它”。

假设我们想要某种方式来编写一个复杂的表达式,比如带有f中缀的 $#$ a / (b + f(c,d^e)) $#$。 在@toivoh的提议中,这将是a / (b + (c f d^e)) 。 在类似 Haskell 的用法中,如果|>优先级被更改以修复这个特定示例a / (b + c |> f d^e) ,它将是a / (b + (c |> f d^e))或“最佳”。 我认为@toivoh在这里很容易做到。

(tissue wash fire dirty metal)

我认为解决这个问题的方法是中缀运算符的强命名约定。 例如,如果有一个约定,中缀运算符应该是一个 unicode 字符,或者以介词或下划线结尾,那么上面的内容将类似于(tissue wash_ fire dirty metal) ,这与该表达式所希望的一样清晰.

...

逐元素

这是一个合理的担忧。 (a .f b)是个坏主意,因为它可能被读作((a.f) b) 。 我的第一个建议是(a ..f b) ,但这并没有让我很高兴。

circle 10 |> move 0 0 |> animate "scale" "ease"

我用过 jquery,所以我肯定看到了这样的函数链接的优势。 但我认为这与中缀运算符不同。 使用(a f b)提案,您可以将上述内容写为:

circle 10 |> (move <| 0 0) |> (animate <| "scale" "ease")

...它不像 Haskell 版本那么简洁,但仍然非常可读。

也许它可以仅限于()内的三件事:
(a f (b,c))
.(a f (b,c))使用运算符.(

最后,回应一般观点:

总的来说,我个人不相信 Julia 应该支持超出当前允许的自定义中缀运算符。

显然,我们都有权发表自己的意见。 (我不清楚竖起大拇指是否指的是评论的那部分,但如果是这样,那就是三倍。)

但我的反对意见是:

  • Julia 已经有几十个中缀运算符,其中许多非常小众。 不可避免地会提出更多建议。 当有人说“你怎么能有而不是§ ?”,我宁愿回答“自己做”而不是“等到下一个版本被广泛采用”。
  • (a § b)之类的东西非常易读,而且语法很轻量级,可以从一两个示例中学习。
  • 我不是第一个提出这个问题的人,也不会是最后一个。 我知道语言设计者应该非常非常怀疑爬行(错误)的特性,因为一旦你添加了一个丑陋的特性,以后基本上不可能修复。 但正如我上面所说,我认为(a f b)足够干净,你不会后悔的。

我真的不确定(a f b)的清晰度

这是一个可能的用例:
select((((:emp_id, :last_name) from employee_tbl) where (:city, == ,'indianapolis')) orderby :emp_id));

这当然是中缀函数的可行使用。
select函数要么是身份函数,要么将构建的查询发送到数据库。

这是清晰的代码吗?
我只是不知道。

.(a f b)

是的,这是有道理的。 但它的可读性不是很高。

(a @. f b)更具可读性吗? 因为启用它的@.宏将是一个简单的单行。

[[[想一想,如果我们允许中缀宏而不需要括号,@Glen-O 可以使用它们来做他想做的事: circle(10) @> move 0 0 @> animate "scale" "ease" => @> (@> circle(10) move 0 0) animate "scale" "ease" =macro> animate(move(circle(10),0,0),"scale","ease") 。 我认为该解决方案比(a f b)更丑陋,但至少它可以解决我眼中的这个整体错误。]]]

...

select((((:emp_id, :last_name) from employee_tbl) where (:city, = ,'indianapolis')) orderby :emp_id);

我肯定更愿意为“where”使用宏,这样条件表达式就不必被奇怪地引用。 所以:

select((((:emp_id, :last_name) from employee_tbl) <strong i="22">@where</strong> city == 'indianapolis') orderby :emp_id);

括号有点烦人,但另一方面,我认为解析器没有合理的方法来处理没有它们的这种表达式。

select((((:emp_id, :last_name) from employee_tbl) <strong i="6">@where</strong> city == 'indianapolis') orderby :emp_id);

括号有点烦人,但另一方面,我认为解析器没有合理的方法来处理没有它们的这种表达式。

再想一想,该表达式中的优先级正好是从右到左。 因此,使用中缀宏,它也可以是:

select((:emp_id, :last_name) <strong i="11">@from</strong> employee_tbl <strong i="12">@where</strong> city == 'NYC' <strong i="13">@orderby</strong> :emp_id)

甚至:

<strong i="17">@select</strong> (:emp_id, :last_name) <strong i="18">@from</strong> employee_tbl <strong i="19">@where</strong> city == 'NYC' <strong i="20">@orderby</strong> :emp_id

因此,虽然我仍然喜欢(a f b) ,但我开始看到中缀宏也是一个很好的答案。

这是通过示例的完整建议,然后是优点和缺点:

主要用途:

  • a <strong i="28">@m</strong> b => <strong i="30">@m</strong> a b
  • a <strong i="33">@m</strong> b c => <strong i="35">@m</strong> a b c
  • a <strong i="38">@m</strong> b <strong i="39">@m2</strong> c => <strong i="41">@m2</strong> (<strong i="42">@m</strong> a b) c
  • <strong i="45">@defineinfix</strong> f; a <strong i="46">@f</strong> b => macro f(a,b...) :(f($a,$b...)) end; <strong i="48">@f</strong> a b => f(a,b)

极端案例:(不是好的代码,只是为了展示解析器的工作方式)

  • t1=a <strong i="54">@m</strong> t2=b t3=c => <strong i="56">@m</strong> t1=a t2=b t3=c (虽然这不是好的编程风格)
  • t1 + a <strong i="59">@m</strong> t2 + b => <strong i="61">@m</strong> t1+a t2+b (虽然这不是好的编程风格)
  • a b <strong i="64">@m</strong> c => 语法错误 (??)
  • a <strong i="67">@m</strong> b [c,d] =>不要,但是<strong i="70">@m</strong> a b[c,d] <strong i="72">@m</strong> a b ([c,d]) ETA:不,随着补丁的出现,这可能会更好。)
  • a <strong i="75">@m</strong> b ([c,d]) => <strong i="77">@m</strong> a b ([c,d])
  • [a <strong i="80">@m</strong> b] => 不好的风格,用括号澄清,但是[a (<strong i="83">@m</strong> b)] (??)
  • a @> f b => @> a f b => f(a,b)
  • <strong i="90">@outermacro</strong> a b <strong i="91">@m</strong> c d => <strong i="93">@outermacro</strong> a (<strong i="94">@m</strong> b c d)

好处:

  • 定义中缀宏,免费获得中缀函数(一次性的宏评估开销。这不像解析器魔法那么低开销,但比每次评估都有额外的函数调用要好得多)
  • 可以导致强大的 DSL,如上面类似 SQL 的示例所示
  • 无需单独的|>运算符,因为这是一个单行宏。 <|和@Glen-O 的其余提案也是如此。
  • 明确的,因此被意外使用的风险非常低,不像(a f b)
  • 如图所示, @defineinfix宏可以允许函数而不是宏的简写。

(次要)缺点:

  • 在 RtoL 的大多数情况下,优先级和分组似乎工作得很好,但也会有需要括号的例外情况。
  • 我认为a @> f b甚至a <strong i="112">@f</strong> b不如(a f b)可读性好(尽管它们也不太可怕。)

鉴于这个线程变得如此活跃,我将提醒人们我最初对这个主题的关注:关于语法的问题通常会产生大量的活动,但这种活动量通常与长期价值不成比例正在讨论的变化。 在很大程度上,这是因为关于语法的话题最终接近于关于品味的纯粹争论。

活动量通常不成比例

抱歉。 我可能是最内疚的进入来回。

另一方面,我认为这个线程显然取得了“可用”的进展。 在我看来,最新的建议(a f b)或 [ a @> f b ,其中a <strong i="10">@f</strong> b可定义为快捷方式] 显然优于之前的建议,如a %f% ba |> f <| b

尽管如此,我认为进一步的来回评论可能不会取得任何进一步的进展,我鼓励人们从现在开始使用大拇指或大拇指向下,除非他们有真正新的建议(即是,不仅仅是对现有提案的正字法更改)。 我在“投票提案”中添加了“万岁”表情符号(爆炸锥)。 如果您认为我们应该为中缀位置的任意函数提供专门的语法,那么请对整个错误投反对票。

...

ETA:我认为这个讨论现在已经足够成熟,可以得到decision的标签了。

供参考,(我希望其他人指出这一点)。
如果你想嵌入类似 SQL 的语法,我认为适合这项工作的工具是非标准字符串文字。
像所有宏一样,它们在调用时可以访问范围内的所有变量,
它们允许您指定自己的 DSL,您可以选择优先级,并且它们在编译时运行。

select((((:emp_id, :last_name) from employee_tbl) where (:city, == ,"indianapolis")) orderby :emp_id));

写得更好

sql"SELECT emp_id, last_name FROM employee_tbl WHERE city == 'indianapolis' ORDER BY emp_id"

非标准字符串文字是一种非常强大的语法。
我找不到任何用于嵌入 DSL 的好例子。
但他们可以做到。

在这种情况下,我认为结果比任何可以定义的中缀操作都要干净得多。
尽管它确实有必须编写自己的微解析器/标记器的开销。


我真的不认为需要一个决策标签。
这没有作为 PR 的实现,也没有任何可用的原型。
这让人们可以测试它。
对比https://github.com/JuliaLang/julia/issues/5571#issuecomment -205754539 的8个可用原型

每次阅读该主题时,我对此的感觉都会起伏不定。 我想在我尝试之前我不会真正知道。 现在我什至不知道我会用它做什么。 (与我在 F# 中使用的|><|的一些定义不同)

类似 SQL 的语法,适合这项工作的工具是非标准字符串文字

无论 SQL 是否最好使用 NSL 来完成,我认为有一个级别的 DSL 足够复杂,内联宏会非常有用,但不会复杂到值得编写自己的微解析器/标记器。

现在我什至不知道我会用它做什么。 (与我在 F# 中使用的 |> 和 <| 的一些定义不同)

内联宏提议将使人们能够滚动他们自己的|> -like 或<| -like 宏,因此您可以将它用于您在 F# 中所做的任何事情。

(我不想陷入来回的自行车脱落争论,但我还是因为下面的原因做出了回应,而且我确实认为内联宏观提议用一块相对光滑的石头杀死了多只鸟。)

我真的不认为需要一个决策标签。

我之前问过我是否适合创建解析器补丁,但没有人回答。 到目前为止,唯一的一句话是:

我认为在 Julia 1.0 之前不会有太多的“语法创新”。

这似乎反对现在制作补丁,因为它可能只是闲置并且有点腐烂。 但是,现在您说不值得对此做出决定(包括现在不决定的决定?),除非我们有“作为 PR [或] 可用原型的实现”。

这意味着什么? (什么是 PR?)使用字符'@'而不是标记@的宏会完成这项工作,因此<strong i="22">@testinline</strong> a '@'f b => @f(a, b) ? 或者我应该向 julia-parser.scm 提交补丁? (实际上我已经开始考虑编写这样的补丁,看起来应该很简单,但我的 Scheme 很生疏。)我需要创建测试用例吗?

目前,这个错误有 13 名参与者。 总共有 5 人对一项或多项提案进行了投票和/或对错误本身投了反对票,在内联宏提案提交后,只有其中一个人(我)这样做了。 这并不能让我确信是时候进行原型设计了。 当自上次严肃提案以来投票的人数接近参与者人数的一半时,我希望某种粗略的共识会变得清晰,然后是时候进行原型设计和测试和决定(或者,作为情况可能是,放弃这个想法)。

通过“实现为 PR [或] 可用原型”。
我的意思是可以玩的东西。
因此,可以看出它在实践中的感受。

PR 是一个拉取请求,因此补丁是您一直在使用的术语。

如果你做了 PR,它可以被下载和测试。
更简单的是,如果你用宏实现它
或非标准字符串文字,
无需构建 Julia 即可对其进行测试。

好像这不是我的决定,但我怀疑如果没有我可以玩的东西,我是否能做出自己的看法。

还 +1 表示不会来回脱落自行车。

...或者可能是带有宏和非标准字符串文字的Infix.jl包。

在这次谈话中,我们肯定已经达到了“工作代码或 GTFO”这一点。

好的,这是工作代码: https ://github.com/jamesonquinn/JuliaParser.jl

ETA:我应该引用一个特定的提交,还是上面链接到最新的 master 可以?

...

(它没有我希望您想要的任何便利宏,例如|><|~@defineinfix的等价物删除_deprecate_ 现在无用的~|>运算符的特殊情况逻辑。这只是解析器的更改使其工作。我已经测试了基本功能,但并非所有极端情况。

...

我认为目前~的丑陋黑客表明这种事情有一个明确的用例。 使用这个补丁,当你需要宏行为时,你会说@~ ; 干净得多,没有特殊情况。 或者有没有人真的相信~是完全独一无二的,没有人愿意再这样做?

请注意,补丁(它还不是 PR,因为它针对本机引导解析器,但目前方案应该首先在 PR 方面)比这里的问题名称更普遍有用。 问题名称是“自定义中缀运算符”; 该补丁提供了中缀宏,中缀运算符只是其副作用。

目前的补丁并不是一个重大变化,但我希望如果这成为计划,下一步将是弃用当前存在的~|> ,这最终会导致突破性的变化。

...

添加了一些简单的测试。

11608 以非常明确的共识结束,即我们中的许多人不想要中缀宏,并且当前的一个~解析案例是一个错误(早期是为了 R 兼容性而没有其他特别好的理由)。 我们打算弃用并最终摆脱它,只是还没有完成(以及修改 JuliaStats 包中公式接口的 API 的工作)。

宏现在在技术上是通用的,但它们的输入参数始终是ExprSymbol或文字。 因此,它们并不能像函数(中缀或其他)那样真正扩展到包中定义的新类型。 前缀注释的宏 DSL 或字符串文字可以更好地服务于中缀宏的可能用例。

(对不起,我过早发布;现在修复。)

在 #11608 中,我看到了几个否定的论点:

===

下面会变成什么?
...
y = 0.0 @in@ x == 1.0 ? 1 @in@ 2 : 3 @in@ 4

这是在线程中处理的:

像这样的情况就是为什么我总是使用括号......

同样的先例...适用而不是宏: 0.0 in 1 == 1.0 ? 2 in 2 : 3 in 4

===

人们必须实现、维护、测试、学习使用等 Julia 的更多功能。

在这里(部分)回答(和附议):

“解析器开发人员的头疼”是最低可能的担忧。

===

有没有办法让 2 个包同时定义同一个宏运算符,可以在单个用户代码库中明确地一起使用?

这是一个有趣的观点。 显然,如果宏只是调用一个函数,那么我们就拥有了函数的全部调度能力。 但如果它是一个真正的宏,如~ ,那么它就更复杂了。 是的,您可以想象一些骇人听闻的变通方法,例如尝试将其作为函数调用,并捕获任何错误以将其用作宏……但不应该鼓励这种丑陋的做法。

尽管如此,这对于任何宏来说都是一个问题。 如果两个包都导出一个宏,你根本不能同时使用“使用”。

这可能是中缀宏的更多问题吗? 好吧,这取决于人们最终将它们用于什么:

  • 只是一种拥有用户定义的中缀函数的方法。 在这种情况下,它们并不比任何其他功能差。 调度工作正常。
  • 作为使用其他编程风格的一种方式,使用@Glen-O 上面讨论的|><|等运算符。 在那种情况下,我认为很快就会形成关于什么宏意味着什么的共同约定,而几乎没有发生冲突的机会。
  • 作为制作专用 DSL 的一种方式,例如上面的 SQL 示例。 我认为这些将在特定情况下使用,碰撞的机会也不算太糟糕。
  • 对于 R 的~之类的东西。 起初,这看起来是最有问题的; 在 R 中, ~用于几个不同的事情。 但是,我认为即使在那里,它也是可以管理的,例如:

macro ~(a,b) :(~(:$a, quote($b))) end

然后,函数~可以根据 LHS 的类型进行调度,但 RHS 始终是 Expr。 这种事情将允许它在 R(回归和图形)中的主要用途共存,也就是说,尽管来自不同的包,但仍能正确调度。

(注意:以上内容已被编辑。最初,我认为像a ~ b + c这样的 R 表达式通过 R 的惰性求值使用了bc的绑定。但它没有t; bc是显式传递的数据框中的列名,而不是隐式传递的本地范围内的变量名。)

===

唯一的出路是开发一个实际的实现。

我已经做到了。

===

宏现在在技术上是通用的,但它们的输入参数始终是 Expr、Symbol 或文字。 因此,它们并不能像函数(中缀或其他)那样真正扩展到包中定义的新类型。

这与上面的观点有关。 就中缀宏调用特定函数而言,该函数仍然可以通过以正常方式分派进行扩展。 只要它不调用特定函数,它就在做一些不应该扩展或重新定义的结构/句法(例如|>现在所做的)。 请注意,即使它调用了一个函数,它是一个宏这一事实仍然很有用; 例如,它可以引用它的一些参数,或者将它们处理成回调,甚至可以同时与变量的名称和绑定进行交互,这是直接函数调用无法做到的。

===

前缀注释的宏 DSL 或字符串文字可以更好地服务于中缀宏的可能用例。

正如在引用的线程中指出的那样:

[中缀]更容易解析(对于英语和大多数西方人来说),因为我们的语言就是这样工作的。 (同样的事情通常适用于运营商。)

例如,哪个更具可读性(和可写性):

select((:emp_id, :last_name) <strong i="8">@from</strong> employee_tbl <strong i="9">@where</strong> city == 'NYC' <strong i="10">@orderby</strong> :emp_id)

或者

send(orderby((<strong i="14">@where</strong> selectfrom((:emp_id, :last_name), employee_tbl) city == 'NYC'), :emp_id))

?

===

最后:

11608 以非常明确的共识关闭

对我来说看起来相当平均,“谁来做这项工作”投下了决定性的一票。 现在至少部分没有实际意义; 我已经在 J​​uliaParser 中完成了这项工作,如果人们喜欢这个想法,我愿意在 Scheme 中完成这项工作。

这是我在这个线程中的最后一篇文章,除非对我被黑的 juliaparser 有积极的反应。 我无意强加我的意志; 只是为了表达我的观点。

我主张支持中缀宏( a <strong i="6">@m</strong> b => <strong i="8">@m</strong> a b )。 这并不意味着我不知道反对的论点。 以下是我如何总结反对的最佳论点:

语言功能从 -100 开始。 中缀宏提供什么可以克服这个问题? 就其本质而言,使用中缀宏无法完成前缀宏无法完成的任何事情。

我的回答是:Julia 首先是面向 STEM 程序员的语言。 数学家、工程师、统计学家、物理学家、生物学家、机器学习人员、化学家、计量经济学家……我认为大多数人意识到的一件事是一个好的符号的有用性。 举一个我熟悉的统计学例子:添加独立随机变量相当于卷积 PDF,甚至相当于卷积 CDF 的导数,但通常使用前者表达的东西可能比后者更简洁易懂一个数量级.

在某种程度上,中缀与前缀与后缀是一个品味问题。 但在许多情况下,也有客观的理由更喜欢中缀。 前缀和后缀会导致背靠背运算符难以消化,比如那些让 Forth 程序员听起来像德国政治家的那些,或者让 Lisp 程序员听起来像 Chomskian 漫画的那些,中缀将运算符置于通常是认知能力最强的地方自然的地方,尽可能靠近它们的所有操作数。 没有人用 Forth 写数学论文是有原因的,甚至德国数学家在写方程时也使用中缀运算符。

是的,中缀宏可用于编写混淆代码。 但是现有的前缀宏同样容易被滥用。 如果不被滥用,中缀宏可以导致更清晰的代码。

  • (a+b <strong i="18">@choose</strong> b)击败binomial(a+b,b) ;
  • score ~ age + treatment击败linearDependency(:score, :(age + treatment)) ;
  • domSelect("#logo") @| css "color" "red" @| fadeIn "slow" <strong i="25">@thenApply</strong> addClass "dummy"击败了addOneTimeEventListener(fadeIn(css(domSelect("#logo"),"color","red"),"slow"),"done",(obj,evt)->addClass(obj,"dummy"))

我意识到这些只是玩具示例,但我认为该原则是有效的。

以上可以用非标准字符串文字完成吗? 好吧,第二个和第三个示例将用作 NSL。 但是 NSL 的问题在于它们给了你太多的自由:除非你熟悉特定的语法,否则甚至无法确定 NSL 的标记是什么,更不用说它的操作顺序了。 使用中缀宏,您有足够的自由来执行上述所有示例,但在阅读“好”代码时不清楚标记是什么以及隐含括号的位置。

它需要将某些东西从未知的未知转移到已知的未知。 不幸的是,没有一种机制可以做到这一点。 你的论点需要一个不存在的结构。

现在<|是右关联的 (#24153),最初的a |>op<| b提案是否有效?

我为 Steven 在https://github.com/JuliaLang/julia/pull/24404#issuecomment -341570934 中提到的 hack 制作了一个包:

我不是这会影响多少潜在的中缀运算符,但我真的很想使用<~ 。 解析器不会合作——即使我仔细隔开,它也希望a <~ b表示a < (~b)

<-也有类似的问题。

抱歉,如果此问题或其他问题已涵盖此内容,但我找不到。

我们可能需要a < ~b中的空格; 我们之前已经添加了类似的规则。 然后我们可以添加<-<~作为中缀运算符。

谢谢@JeffBezanson ,那太好了! 这是一个特例,还是更一般的规则? 我确信规则应该有一些细节,以允许更多的中缀运算符,提供清晰和可预测的代码,并尽可能少地破坏现有代码。 无论如何,我感谢您的帮助和快速响应。 新年快乐!

如果a <~ ba < ~b不同,我希望将a =+ 1视为错误(或至少警告)

我知道这是一个相当古老的讨论,并且提出的问题是很久以前提出的,但我认为值得回答:

现在<|是右关联的 (#24153),最初的a |>op<| b提案是否有效?

不,不幸的是, |>仍然优先。 完成的更新使得,如果您定义<|(a,b)=a(b) ,那么您可以成功地执行a<|b<|c以获得a(b(c)) ...但这是一个不同的概念。

冻结 2 年,2 天和 5 天前发表评论和提交!

请参阅文档可自定义的二元运算符 f45b6be

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

相关问题

Keno picture Keno  ·  3评论

helgee picture helgee  ·  3评论

tkoolen picture tkoolen  ·  3评论

manor picture manor  ·  3评论

StefanKarpinski picture StefanKarpinski  ·  3评论