Julia: 功能链

创建于 2014-01-27  ·  232评论  ·  资料来源: JuliaLang/julia

是否可以允许在Any上调用任何函数,以便将该值作为第一个参数传递给该函数,并在之后添加对该值传递给该函数调用的参数?
例如

sum(a::Int, b::Int) -> a + b

a = 1
sum(1, 2) # = 3
a.sum(2) # = 3 or
1.sum(2) # = 3

是否可以确定性地指示函数将返回什么以避免运行时异常?

最有用的评论

所以我们目前的各种努力清单等等
我认为值得人们检查一下(理想情况是在发表意见之前,但有)
它们都略有不同。
(我尝试按时间顺序订购)。

配套

非包装原型

有关:


也许应该将其编辑为顶级帖子之一。

更新日期:2020-04-20

所有232条评论

.语法非常有用,因此我们不会将其仅作为函数调用的同义词。 我不了解1.sum(2)sum(1,2) 。 对我来说,这似乎使事情变得混乱。

关于例外的问题是单独的问题吗? 我认为答案是否定的,除了在try..catch中包装函数体。

1.sum(2)示例是微不足道的(我也更喜欢sum(1,2)),但这只是为了证明函数本身不是该类型ex所拥有。 可以将1传递给第一个参数为Real的函数,而不仅仅是传递给期望第一个参数为Int的函数。

编辑:我可能误解了您的评论。 当应用某些设计模式(例如通常用于配置的构建器模式)时,点功能将非常有用。 例如

validate_for(name).required().gt(3) 
# vs 
gt(required(validate_for(name)), 3) 

我刚才提到的异常是由于函数返回的不确定性结果(无论如何都是不好的做法)。 例如,调用a.sum(2).sum(4),其中.sum(2)有时返回String而不是Int,但是.sum(4)期望返回Int。 我认为编译器/运行时已经足够聪明,可以评估这种情况-嵌套函数sum(sum(1,2),4)时将是相同的-但功能请求将需要扩展所述功能以对类型执行约束点函数。

人们似乎喜欢的用例之一是“流畅的界面”。 当方法返回对象时,在OOP API中有时会很好,因此您可以执行some_obj.move(4, 5).scale(10).display()

对我来说,这可以更好地表示为函数组合,但是|>不能与参数一起使用,除非您使用anon。 功能,例如some_obj |> x -> move(x, 4, 5) |> x -> scale(x, 10) |> display ,这非常难看。

支持这种事情的一个选择是,如果|>在评估之前将LHS作为RHS的第一个参数,但是它不能像现在这样简单地实现。

另一种选择是某种@composed宏,它将这种行为添加到以下表达式中

您也可以将支持此工作的责任转移给图书馆设计师,他们可以在其中定义

function move(obj, x, y)
    # move the object
end

move(x, y) = obj -> move(obj, x, y)

因此,当您不提供对象时,它会执行部分函数应用程序(通过返回1参数的函数),然后可以在常规|>链中使用它。

实际上,现在可以将|>的定义更改为
您要求的行为。 我会的。

2014年1月27日,星期一,Spencer Russell [email protected]
写道:

人们似乎喜欢的用例之一是“流畅的界面”。 它的
当方法返回对象时,有时在OOP API中很不错,所以您可以
诸如some_obj.move(4,5).scale(10).display()之类的东西

对于我来说,我认为这可以更好地表达为功能组合,但是
|>不适用于参数,除非您使用anon。 函数,例如some_obj
|> x-> move(x,4,5)|> x-> scale(x,10)|>显示效果
丑陋。

一个支持这种情况的选项是|>将LHS推为
RHS评估前的第一个论点,但后来不能
实现为现在的简单功能。

另一个选择是某种@composed宏,可以添加此宏
以下表达式的行为

您也可以将支持此工作的责任转移到图书馆
设计师,他们可以在其中定义

函数move(obj,x,y)
#移动对象
结束

move(x,y)= obj-> move(obj,x,y)

因此,当您不提供对象时,它将执行部分功能
(通过返回1个参数的函数),然后可以在
正常|>链。

-
直接回复此电子邮件或在Gi tHub上查看它

ssfrr我喜欢你的想法! 我没有意识到函数组成|> 。 我看到最近有一个类似的讨论[https://github.com/JuliaLang/julia/issues/4963]。

kmsquire我喜欢扩展当前函数组成以允许您在调用函数ex上指定参数的想法。 some_obj |> move(4, 5) |> scale(10) |> display 。 本地支持意味着减少一种关闭,但是ssfrr提出的建议是目前可行的方法,并且作为一项附加好处,如果实现,它还应与扩展函数组合功能向前兼容。

感谢您的迅速回复:)

实际上, @ ssfrr是正确的-不可能将其实现为简单函数。

您需要的是线程宏(例如http://clojuredocs.org/clojure_core/clojure.core/-%3E)。 不幸的是,@-> @->> @-?>>在Julia中不是可行的语法。

是的,我当时认为infix宏将是实现此目标的一种方式。 我对宏还不够熟悉,无法知道其局限性。

我认为这适用于@ssfrr的compose宏:

编辑:这可能会更清晰一些:

import Base.Meta.isexpr
_ispossiblefn(x) = isa(x, Symbol) || isexpr(x, :call)

function _compose(x)
    if !isa(x, Expr)
        x
    elseif isexpr(x, :call) &&    #
        x.args[1] == :(|>) &&     # check for `expr |> fn`
        length(x.args) == 3 &&    # ==> (|>)(expr, fn)
        _ispossiblefn(x.args[3])  #

        f = _compose(x.args[3])
        arg = _compose(x.args[2])
        if isa(f, Symbol)
            Expr(:call, f, arg) 
        else
            insert!(f.args, 2, arg)
            f
        end
    else
        Expr(x.head, [_compose(y) for y in x.args]...)
    end
end

macro compose(x)
    _compose(x)
end
julia> macroexpand(:(<strong i="11">@compose</strong> x |> f |> g(1) |> h('a',"B",d |> c(fred |> names))))
:(h(g(f(x),1),'a',"B",c(d,names(fred))))

如果我们要使用这种|>语法,那么我肯定会全力以赴,以使其比现在更有用。 仅仅允许将函数应用在右边而不是左边,似乎一直是语法上的巨大浪费。

+1。 当您使用Julia进行数据分析时,这一点尤其重要,因为您通常会使用数据转换管道。 特别是,Python中的Pandas使用起来很方便,因为您可以编写df.groupby(“ something”)。aggregate(sum).std()。reset_index()之类的东西,这是使用当前|>语法编写的噩梦。

:+1:为此。

(我已经考虑过建议为此使用.. infix运算符( obj..move(4,5)..scale(10)..display ),但是运算符|>也会很好)

另一种可能性是添加语法糖来进行咖喱,例如
f(a,~,b)转换为x->f(a,x,b) 。 然后|>可以保留其当前含义。

哦,那将是将任何表达式转换为函数的一种非常好的方法。

可能类似于Clojure的匿名函数文字,其中#(% + 5)x -> x + 5简写。 这也泛化为带有%1,%2等的多个参数,因此#(myfunc(2, %1, 5, %2)x, y -> myfunc(2, x, 5, y)简写

从美学上讲,我认为语法不能很好地适合本来很容易阅读的茱莉亚,但我喜欢一般的想法。

要使用上面的示例(并切换到@malmaud的波浪号而不是%),可以执行以下操作

some_obj |> move(~, 4, 5) |> scale(~, 10) |> display

看起来不错。

这样做的好处是它不会对第一个参数提供任何特殊处理。 缺点是使用这种方式占用符号。

也许在另一个可以使用宏的地方,因此替换仅在宏的上下文内发生。

我们显然不能用~做到这一点,因为它已经是Julia的标准功能了。 Scala使用_做到了这一点,我们也可以这样做,但是弄清楚表达式的哪一部分是匿名函数存在一个很大的问题。 例如:

map(f(_,a), v)

这是什么意思?

map(f(x->x,a), v)
map(x->f(x,a), v)
x->map(f(x,a), v)

它们都是有效的解释。 我似乎记得,Scala使用函数的类型签名来确定这一点,这让我感到很不幸,因为这意味着您无法在不了解所有类型的情况下真正解析Scala。 我们不想这样做(即使我们也不想这样做),因此必须有一个纯粹的语法规则来确定要表达的含义。

是的,我认为您的意思是要走多远。 在Clojure中,整个表达式都包裹在#(...)因此它是明确的。

在Julia中,将_用作值不在乎吗? 像x, _ = somfunc()如果somefunc返回两个值,而您只想要第一个值?

为了解决这个问题,我认为我们需要具有类似插值用法的宏:

some_obj |> @$(move($, 4, 5)) |> @$(scale($, 10)) |> display

但是再次,我认为那时候它变得越来越嘈杂,而且我不认为@$(move($, 4, 5))给我们提供超过现有语法x -> move(x, 4, 5)任何东西,这既是IMO的更美又是更明确的。

我认为这将是infix宏的良好应用。 与#4498一样,如果任何规则也将函数定义为适用于宏的中缀,则我们可能会有一个具有线程化行为的@->@|>宏。

是的,我喜欢infix宏的想法,尽管可以为此引入一个新的运算符来代替具有整个系统的inplace宏。 例如,
some_obj ||> move($,4,5) ||> scale($, 10) |> disp
或只是保留|>但有一条规则
x |> f隐式转换为x |> f($)
some_obj |> scale($,10) |> disp

伙计们,这看起来真的很丑:|> ||>等。
到目前为止,我发现Julia的语法非常清晰,以至于与其他内容相比,上面讨论的这些东西看起来并不那么漂亮。

在Scala中,这可能是最糟糕的事情-他们有很多运算符,如::,:,<<,>> + ::等,等等-如果没有几个月的使用经验,它只会使任何代码变得丑陋且难以阅读。语言。

抱歉得知您不喜欢这些建议,安东。 如果您提出其他建议,将会很有帮助。

哦,对不起,我不是要不客气。 是的-评论家没有建议
没有用

不幸的是我不是构造语言的科学家,所以我只是不
知道要提出什么...好,除了使方法可选地归所有者所有
某些语言中的对象。

我喜欢“科学家构造语言”这一短语-听起来比厌倦Matlab的数值程序员要宏伟得多。

我觉得几乎每种语言都有一种链接功能的方法-通过在OO语言中重复应用. ,或为此目的在更多功能的语言(Haskell,Scala,Mathematica等)中使用特殊语法。 那些后一种语言对于匿名函数参数也有特殊的语法,但是我不认为Julia真的会去那里。

我将重申对Spencer提案的支持-将x |> f(a)转换为f(x, a) ,非常类似于do块的工作原理(并且它强化了一个共同的主题,即朱莉娅(Julia)出于语法糖的目的而享有特权。 x |> f视为x |> f()简写。 它很简单,没有引入任何新的运算符,处理了我们希望函数链接的绝大多数情况,向后兼容,并且符合现有的Julia设计原则。

我也认为这是最好的建议,主要问题是它似乎无法为I / O重定向或其他自定义目的定义|>

请注意, .不是一种特殊的函数链接语法,但是如果左侧的函数返回它刚修改的对象,则它会以这种方式工作,这是库开发人员必须故意做的事情。

类似地,在朱莉娅库开发人员已经可以支持与链接|>通过定义N个参数的函数给出的N-1参数时返回1个参数的功能,如提到这里

但是,如果您想让您的函数支持可变数量的args,这似乎会引起问题,因此,拥有一个可以执行参数填充的运算符会很好。

@JeffBezanson ,如果有办法执行infix宏,似乎可以实现此运算符。 您知道与此相关的意识形态问题吗?

最近, ~是特殊情况,以便引用其参数和调用
@~默认情况下。 |>可以做同样的事情。

当然,几个月后,有人会要求<|做同样的事情...

2014年2月6日,星期四,Spencer Russell [email protected]
写道:

请注意,。 不是特殊的函数链接语法,但是它确实发生了
以这种方式工作,如果左侧的函数返回的对象只是
修改,这是库开发人员必须要做的事情
故意地。

类似地,在Julia中,图书馆开发人员已经可以支持链接
用|>通过定义其函数的N个参数来返回一个函数
给定N-1个参数时的1个参数的总数,如此处所述

如果您想支持您的功能,那似乎会引起问题
可变数量的args,但是,让一个运算符可以执行
参数填充会很好。

@JeffBezanson https://github.com/JeffBezanson ,似乎这
如果有一种方法可以执行infix宏,则可以实现该运算符。 你做
知道这是否存在意识形态问题,或者只是没有实施?

-
直接回复此电子邮件或在Gi tHub上查看它

是的,我绝对不希望这是特例。 实际上,在API设计中处理它并不是那么糟糕,即使您有类型注释要消除歧义,即使变量参数限制也不是太大的问题。

function move(obj::MyType, x, y, args...)
    # do stuff
    obj
end

move(args...) = obj::MyType -> move(obj, args...)

我认为可以通过处理第二个声明的@composable宏来处理此行为。

在#4498中讨论过,在将其与声明infix函数统一的情况下,infix宏概念对我很有吸引力。

为什么Julia创建者如此反对允许对象包含自己的方法? 我在哪里可以阅读有关该决定的更多信息? 该决定背后的思想和理论是什么?

@meglio对于常见问题更有用的地方是邮件列表或StackOverflow julia-lang标签。 有关此主题的先前讨论,请参见Stefan的演讲以及用户开发人员列表的存档。

对我来说,最简单的方法就是将某些占位符替换为
您要编写的内容序列中前一个表达式的值,类似于clojure的as->宏。 所以这:

<strong i="8">@as</strong> _ begin
    3+3
    f(_,y)
    g(_) * h(_,z)
end

将扩展为:

g(f(3+3,y)) * h(f(3+3,y),z)

您可以想到上一行的“下拉”表达式来填充下一行的下划线。

我在决赛周的拖延中开始素描最后一个季度这样的小东西

我们还可以使用|>支持oneliner版本:

<strong i="19">@as</strong> _ 3+3 |> f(_,y) |> g(_) * h(_,z)

@porterjamesj ,我喜欢这个主意!

我同意; 这非常好,并且具有吸引人的通用性。
2014年2月7日下午3:19,“ Kevin Squire” [email protected]写道:

@porterjamesj https://github.com/porterjamesj ,我喜欢这个主意!

直接回复此电子邮件或在Gi tHub上查看它

我喜欢@porterjamesj的想法,不仅是因为呼吸了新鲜空气,而且因为它似乎比以前的想法灵活得多。 我们不仅仅限于使用第一个参数,还可以自由选择中间变量,这似乎也可以立即实现而无需在语言中添加新的语法或特殊情况。

请注意,在Julia中,由于我们不执行obj.method(args...)模式的大部分操作,而是执行method(obj, args...)模式,因此,我们倾向于没有返回返回操作对象的方法。方法链接的目的。 (这是jQuery功能,并且在javascript中很棒)。 因此,我们在这里没有太多的输入内容,但是出于在函数之间设置“管道”的目的,我认为这真的很好。

鉴于clojure的->->>只是上述情况的特例,而且相当普遍,我们也可以很容易地实现它们。 尽管如何称呼他们的问题有些棘手。 也许@threadfirst@threadlast

我也喜欢将其作为宏的想法。

如果按照示例进行扩展,是不是更好呢?

tmp = 3+3; tmp = f(tmp); return h(tmp, z)

避免多次调用同一操作? (也许这已经隐含在@porterjamesj的想法中)

另一个建议:宏是否可能将快捷方式f扩展为f(_)f(y)扩展f(_,y) ? 也许太多了,但是我认为我们可以选择仅在需要时使用占位符...(但是,必须仅在单独的函数调用中允许使用快捷方式,而在g(_) * h(_,z)类的表达式上则不允许使用快捷方式)

@cdsousa关于避免多次调用的观点是一个很好的观点。 clojure实现使用顺序的let绑定来实现这一点; 我不确定我们是否可以解决这个问题,因为我对let的性能了解不多。

那么@as宏是否使用换行符和=>作为分割点来确定什么是替换表达式以及要替换的是什么?

let表现不错; 现在它尽可能快地与变量赋值一样快,否则也很快。

我的玩具实现中的@ssfrr只是过滤掉解析器插入的所有与换行相关的节点(注意,我不是很了解所有这些,在手册中有关于它们的文档可能会很好),然后减少替换在剩余的表达式列表中。 尽管我认为使用let会更好。

@cdsousa

另一个建议:宏可能会将快捷方式f扩展为f(_) ,将f(y)扩展f(_,y)

ff(_)对我来说很有意义。 对于第二点,我认为明确指定位置会更好,因为合理的人可能会认为f(_,y)f(y,_)更自然。

鉴于clojure的->->>只是上述情况的特例,而且很常见,我们也可以很容易地实现它们。 尽管如何称呼他们的问题有些棘手。 也许@threadfirst@threadlast

我认为使用f(_,y...)f(y..., _)指定位置显式可使代码非常容易理解。 尽管额外的语法(和运算符)在Clojure中很有意义,但实际上我们没有额外的运算符,而且我认为额外的宏通常会使代码不太清晰。

那么@as宏是否使用换行符和=>作为分割点来确定什么是替换表达式以及要替换的是什么?

我认为使用|>作为拆分点更为自然,因为它已用于流水线

众所周知, Lazy.jl中有一个线程宏的实现,

@>> range() map(x->x^2) filter(iseven)

从好的方面来说,它不需要任何语言更改,但是如果您要使用多个行,它将变得有些难看。

如果有兴趣,我还可以在Lazy.jl中实现@as> Lazy.jl现在也有一个@as宏。

您也可以对Monads.jl做这样的事情(尽管使用类似Haskell的语法)(注意:它需要更新才能使用当前的Julia语法)。 但是我怀疑仅用于参数线程的专用版本应该能够避免常规方法的性能缺陷。

Lazy.jl看起来是一个非常不错的软件包,并得到了积极维护。 是否有令人信服的理由需要将其列入基础?

函数链接如何与返回多个值的函数一起使用?
链接的结果是什么,例如:

function foo(a,b)
    a+b, a*b   # x,y respectively
end

bar(x,z,y) = x * z - y是?

它不需要像bar(_1,z,_2)这样的语法吗?

再举一个例子:

data = [2.255, 3.755, 6.888, 7.999, 9.001]

干净的书写方式: log(sum(round(data)))data|>round|>sum|>log
但是,如果我们想以2为底的对数,并想舍入到小数点后3位,
然后:我们只能使用第一种形式:
log(2,sum(round(data,3)))

但理想情况下,我们希望能够做到:
data|>round(_,3)|>sum|>log(2,_)
(或类似)

我已经为我建议的工作方式制作了一个原型。
https://github.com/oxinabox/Pipe.jl

它不能解决@gregid的问题,但是我现在正在努力。
它也不能满足扩展参数的需要

它类似于@ one-more-minute的Lazy.jl线程宏,但保留|>符号以提高可读性(个人喜好)。

我可能会慢慢将其包装

另一种选择是:

data |>   x -> round(x,2)  |> sum |>  x -> log(2,x)

尽管比log(2,sum(round(data,2)))这种表示法有时有助于提高可读性。

@shashi还不错,没想到,
我认为通常太冗长而难以阅读

https://github.com/oxinabox/Pipe.jl现在可以解决@gregid的问题。
虽然如果您同时要求_[1]_[2]它会通过多次调用替换项来实现
我不确定这是最可取的行为。

作为局外人,我认为管道运营商将从F#的处理中受益。
诚然,F#经常出现问题,但也许可以在后端做一些魔术以使其不需要它。 就像,在执行操作符时,并没有核心语言。

这会使[1:10] |> map(e -> e^2)产生[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

回想一下, @ ssfrr暗示了这一点,但是示例中的obj参数将自动作为示例中的第二个参数提供给map ,从而使程序员不必定义函数即可。支持它。

您认为这意味着什么?

2015年6月5日下午5:22,H-225 [email protected]写道:

作为局外人,我认为实现此目标的更好方法之一是调整F#对其的处理方式。
诚然,F#经常出现问题,但也许可以在后端做一些魔术以使其不需要它。 就像,在执行操作符时,并没有核心语言。

这将使[1:10] |> map(e-> e ^ 2)的结果为[1、4、9、16、25、36、49、64、81、100]。

就我个人而言,我认为它很好且清晰,但又不太冗长。

显然,一个人可以写出result = map(sqr,[1:10]),但是他们为什么要完全拥有管道运算符?
也许我缺少什么?

-
直接回复此电子邮件或在GitHub上查看。

@StefanKarpinski
基本上,让操作员进行以下两种操作:

  • x |> y(f) = y(x, f)
  • x |> y(f) = y(f, x)

也许有一个接口模式,供操作员使用的任何函数都将要操作的数据作为第一个或最后一个参数,具体取决于上面的哪种模式被选择为该模式。
因此,以map函数为例, map将是map(func, data)map(data, func)

这更清楚吗?

Lazy.jl看起来是一个非常不错的软件包,并得到了积极维护。 是否有令人信服的理由需要将其列入基础?

我认为这是这里的重要问题。

基本可能需要这样做的原因是2倍:

1.)我们可能希望鼓励流水线成为朱利安之路(Julian Way)-可以说它更具可读性
2.)诸如Lazy.jl,FunctionalData.jl和我自己的Pipe.jl之类的东西都需要一个宏来包装要对其执行操作的表达式,这会使它的可读性降低。

我觉得答案可能在于拥有Infix宏。
并定义|>。

我不确定|>,(或它们的堂兄弟do块)是否完全属于核心。
但是不存在在解析器外部定义它们的工具。

具有这种流水线语法的能力似乎非常好。 可以将它们添加到Lazy.j,FunctionalData.jl和Pipe.jl可以使用的Base中,即x |> y(f) = y(f, x)部分吗? :+1:

在查看了在软件包中使用了各种实现的代码之后,我个人发现它不可读,而且非常不朱利安。 左右流水线双关语不利于提高可读性,只会使您的代码从使用括号进行函数评估的完全正常的代码中脱颖而出。 我宁愿不鼓励使用会导致两种不同样式的语法,在这种语法中,以任何一种样式编写的代码相对于以另一种样式编写的代码而言,都由内向外。 为什么不仅仅解决现有的完美语法并鼓励使事情看起来更统一?

@tkelman
我个人从某种功利主义的角度看待它。
当然,如果您只是在做简单的事情,那是没有必要的,但是如果您在写一个函数说,那会做一些相当复杂的事情,或者是漫长的尝试(例如,数据处理),那么我认为这就是管道语法的亮点。

我理解你的意思; 如果您对所有内容都有一个函数调用语法,那么它将更加统一。 不过,就我个人而言,我认为最好简化编写易于理解的[复杂]代码。 当然,您必须学习语法及其含义,但是恕我直言, |>不比如何调用函数更难掌握。

@tkelman我将从另一个角度来看它。 显然,有些人喜欢这种编程风格。 我可以看到,也许您希望对Base的源代码具有一致的样式,但这仅是为了为其首选的_their_ Julia应用程序编程样式添加解析器支持。 朱利安人是否真的想尝试下达命令或以其他方式扼杀别人认为有益的事情?
我发现在Unix中将流水线内容组合在一起非常有用,因此,即使我从未使用过能够在该语言中启用它的编程语言,但我至少会给它带来疑问的好处。

我们确实有|>作为函数管道运算符,但是由于目前的实现方式存在一些实现方面的限制,因此目前运行起来相当缓慢。

在unix外壳中,管道非常有用,所有东西都可以输入文本和输出文本。 对于更复杂的类型和多个输入和输出,它并不那么明确。 因此,我们有两种语法,但是在MIMO情况下,一种语法意义不大。 由于我们具有强大的宏,因此通常不需要解析器支持其他样式的编程或DSL。

好的,谢谢,我正在接受@oxinabox的评论:

但是不存在在解析器外部定义它们的工具。

是否了解可以删除您所引用的实施限制?

可以通过使|>其参数解析为宏而不是函数来实现某些较早的建议。 |>以前的命令对象管道含义已被弃用,因此实际上可以释放它来做一些不同的事情,比如0.5-dev。

但是,这种选择使我想起了~的特殊解析,由于我在其他地方提到的原因,我认为这是一个错误。

解析〜简直太疯狂了,它是base中的一个函数。 使用__1_2似乎更合理(尤其是如果在范围的其他位置定义了这些变量,则可以提高)。 直到我们拥有更高效的匿名功能,这似乎还是行不通...

通过使|>将其参数解析为宏而不是函数来实现

除非你那样做!

解析〜简直是疯了,它是base中的一个函数

它是按位版本的一元运算符。 内嵌二进制~作为宏解析,请参阅https://github.com/JuliaLang/julia/issues/4882 ,我认为这是对ascii运算符的奇怪用法(https://github.com/ JuliaLang / julia / pull / 11102#issuecomment-98477891)。

@tkelman

因此,我们有两种语法,但是在MIMO情况下,一种语法意义不大。

3种语法。 有点儿。
管道输入,正常功能调用和Do-blocks。
甚至可以辩论4个,因为宏也使用不同的约定。


为了我,
Readorder(即从左到右)==应用程序顺序,对于SISO功能链而言,变得更加清晰。

我做了很多类似(使用iterators.jl和pipe.jl)的代码:

  • loaddata(filename) |> filter(s-> 2<=length(s)<=15, _) |> take!(150,_) |> map(eval_embedding, _)
  • results |> get_error_rate(desired_results, _) |> round(_,2)

对于SISO,它更好(根据我个人的喜好),对于MIMO则不是。

朱莉娅似乎已经解决了多种正确的做事方式。
我不是100%肯定是一件好事。

正如我说的那样,我有点想将Pipe and Do块移出主要语言。

Do-block有很多非常有用的用例,但令我有些恼火的是,他们必须使用第一个输入作为函数,并不总是很适合多调度原理(pandas /后缀data.map(f).sum() D样式UFCS,我知道它很流行,但我认为它不能与多个调度有效地结合使用。

可能很快就会弃用管道,然后将其留给DSL中使用的包(例如Pipe.jl)使用。

朱莉娅似乎已经解决了多种正确的做事方式。
我不是100%肯定是一件好事。

这与我们是否可以严格执行社区范围的样式指南有关。 到目前为止,我们在这里没有做太多事情,但是对于长期的软件包互操作性,一致性和可读性,我认为随着社区的发展,这将变得越来越重要。 如果您是唯一会阅读您的代码的人,请发疯并做您想做的任何事情。 如果不是这样,则为了保持一致性,有必要权衡可读性(根据您自己的观点)稍差一些。

@tkelman @oxinabox
我还没有找到一个明确的理由,为什么不应该将它包含在语言中,或者甚至不应该包含在“核心”软件包中。 [例如:基地]
就个人而言,我认为使|>成为宏可能是答案。
也许喜欢某事? (我不是Julia的高级程序员!)

macro (|>) (x, y::Union(Symbol, Expr))
    if isa(y, Symbol)
        y = Expr(:call, y) # assumes y is callable
    end
    push!(y.args, x)
    return eval(y)
end

在Julia v0.3.9下,我无法两次定义它-一次使用符号,一次使用表达式; 我对Union [有限]理解是使用它会降低性能,所以我猜想这将在我的玩具示例代码中得到纠正。

当然,与此相关的使用语法存在问题。
例如,要运行等效于log(2, 10) ,您必须编写@|> 10 log(2) ,这在这里是不希望的。
我的理解是,您必须能够以某种方式将函数/宏标记为“不可固定的”,这样您就可以这样写: 10 |> log(2) 。 (如果错误,请更正!)
我知道有人为的例子。 我现在想不出一个好人! =)

值得指出的是我的示例中未涵盖的一个领域...
所以例如:

julia> for e in ([1:10], [11:20] |> zip) println(e) end
(1,11)
(2,12)
(3,13)
(4,14)
(5,15)
(6,16)
(7,17)
(8,18)
(9,19)
(10,20)

再次-人为的例子,但希望你明白了!
我做了些摆弄,但截至撰写本报告时,我本人也无法理解如何实现该目标。

2015年6月9日,晚上9:37,H-225 [email protected]写道:

我还没有找到不应该将其包含在语言中的明确原因

这是编程语言设计的错误心态。 问题必须由“为什么?” 而不是“为什么不呢?” 每个功能都需要有一个令人信服的理由来包含它,即使有充分的理由,您也应该在添加任何内容之前要三思而行。 你能没有它吗? 有完成同一件事的其他方法吗? 是否存在与现有功能相比更好,更通用或更正交的功能不同变化? 我并不是说这个特定的想法不可能发生,但是有一个比“为什么不这样做”更好的理由呢? 并列举了一些比普通语法更好的示例。

问题必须由“为什么?” 而不是“为什么不呢?”

+ 1_000_000

确实。
请参阅此相当知名的博客文章
每个功能都以-100点开始。
需要进行重大改进才能使语言值得添加。

FWIW,Pyret(http://www.pyret.org/)在几个月前进行了确切的讨论。 该语言支持“炮弹”符号,其最初的功能与人们使用|>提议的方式很相似。 在Pyret,

[list: 1, 2, 3, 5] ^ map(add-one) ^ filter(is-prime) ^ sum() ^ ...

因此,炮弹符号简化为在函数中添加参数。

不久之后,他们便确定该语法过于混乱。 为什么不带任何参数调用sum() ? 等等。最终,他们选择了一种优雅的替代方法:

[list: 1, 2, 3, 5] ^ map(_, add-one) ^ filter(_, is-prime) ^ sum() ^ ...

这具有更明确的优势,并将^运算符简化为简单函数。

是的,这对我来说似乎更合理。 它比咖喱还要灵活。

@StefanKarpinski我有点困惑。 您的意思是说要比链接更灵活(而不是简化)吗? 毕竟,Pyret的解决方案是仅使用curring,这比链接更为笼统。

也许,如果我们稍微修改|>语法(我真的不知道实现起来有多困难,也许它与|>冲突),我们可以设置一些灵活且易读的内容。

定义类似

foo(x,y) = (y,x)
bar(x,y) = x*y

我们会有:

randint(10) |_> log(_,2) |> sum 
(1,2) |_,x>  foo(_,x)   |x,_>   bar(_,2) |_> round(_, 2) |> sum |_> log(_, 2)

换句话说,我们将有一个像|a,b,c,d>这样的运算符,其中abcd将获得返回值最后一个表达式(按顺序),并在下一个表达式内的占位符中使用它。

如果|>内没有变量,它将按现在的方式工作。 我们还可以设置一个新的标准符号: f(x) |> g(_, 1)将获得f(x)返回的所有值,并与_占位符关联。

@samuela ,我的意思是,使用currying时,您只能省略尾随参数,而使用_方法,则可以省略任何参数并获得匿名函数。 即给定f(x,y)使用curry来执行f(x)以获取执行y -> f(x,y)的函数,但是使用下划线可以为同一件事执行f(x,_)f(_,y)得到x -> f(x,y)

虽然我喜欢下划线语法,但对于它“捕获”多少个周围表达式的问题,我仍然不满意。

如果一个函数返回多个结果该怎么办? 是否必须将元组传递到_位置? 还是有一种语法可以将其拆分? 可能是一个愚蠢的问题,如果是这样,请原谅!

@StefanKarpinski啊,我明白你的意思了。 同意

@ScottPJones的明显答案是允许使用ASCII艺术箭头:
http://scrambledeggsontoast.github.io/2014/09/28/needle-announce/

@simonbyrne这看起来比在Fortran IV中用打孔卡编程要糟糕得多,就像我年轻的无用之年一样! 只是想知道某些语法(例如_1,_2等)是否可能允许分开多次收益,或者这只是我的愚蠢想法?

@simonbyrne太好了。 将其实现为字符串宏将是一个了不起的GSoC项目。

为什么不带任何参数调用sum()?

我认为隐式参数也是do表示法中比较混乱的事情之一,因此,如果我们也可以为此使用相同的约定,那将是很好的选择(尽管我意识到这要困难得多,因为它已经融入了语言)。

@simonbyrne您认为这样做不可能明确吗? 如果是这样,那么我认为值得打破的事情(当前的do表示法),如果可以使其变得更合理,更通用并且与链接保持一致。

@simonbyrne是的,我完全同意。 我了解当前do表示法的动机,但我强烈认为它不能证明语法体操的合理性。

@samuela关于map(f,_)与仅map(f)。 我同意某些神奇的减法操作会造成混淆,但是我确实认为map(f)应该存在。 不需要,糖只需要添加一个简单的映射方法即可。
例如

map(f::Base.Callable) = function(x::Any...) map(f,x...) end

即map使用一个函数,然后返回一个对可迭代(或多或少)的事物起作用的函数。

更一般地说,我认为我们应该倾向于具有其他“便捷”方法的函数,而不是某种习惯,即|>总是将数据映射到第一个参数(或类似参数)。

同样,可能会有一个

type Underscore end
_ = Underscore()

以及一般约定,即函数应该/可以具有在某些参数中使用下划线的方法,然后返回使用较少参数的函数。 我不太相信这是一个好主意,因为需要为每个带有n个参数的函数添加2 ^ n个方法。 但这是一种方法。 我想知道是否有可能不必显式添加这么多方法,而是挂接到方法查找中,以便如果有任何参数为Underscore类型,则返回适当的函数。

无论如何,我绝对认为拥有仅接受一个可调用对象并返回一个可调用对象的map和filter版本是有意义的,使用Underscore的东西可能可行,也可能不可行。

@帕特里克博尔德
我想像x |> map(f, _) => x |> map(f, Underscore()) => x |> map(f, x)一样,这是实现map(f, _)的最简单方法,对吗? -将_设为您要为其编程的特殊实体吗?

虽然,我不确定这是否比朱莉娅自动推断它(大概使用|>语法)更好,而不是必须自己编程。

另外,关于您的map提案-我有点喜欢。 确实,对于当前的|> ,这将非常方便。 不过,我想这将是简单最好只实现自动推理x |> map(f, _) => x |> map(f, x)呢?

@StefanKarpinski很有道理。 还没想到那样。

我说过的任何内容都不会与|>绑定。 我对_是例如向<添加方法,例如:

<(_::Underscore, x) = function(z) z < x end
<(x, _::Underscore) = function(z) x < z end

但是我再次认为,除非有一种自动添加适当方法的方法,否则这将是一个痛苦。

同样,带有下划线的东西是分开的,即如上所述添加了便捷方法来进行映射。 我确实认为两者都应该以某种形式存在。

@patrickthebold这种带有用户定义的下划线类型的方法等在实现函数时会给程序员带来重大且不必要的负担。 必须列出所有2 ^ n个

f(_, x, y) = ...
f(x, _, y) = ...
f(_, _, y) = ...
...

会很烦人,更不用说优雅了。

另外,我假设您对map会为map(f)提供一种变通方法,并带有mapfilter类的基本功能,但总的来说,它会遇到相同的问题复杂性问题作为手动强调方法。 例如,对于func_that_has_a_lot_of_args(a, b, c, d, e)您将必须经过繁琐的过程才能输入每个可能的“ currying”

func_that_has_a_lot_of_args(a, b, c, d, e) = ...
func_that_has_a_lot_of_args(b, c, d, e) = ...
func_that_has_a_lot_of_args(a, b, e) = ...
func_that_has_a_lot_of_args(b, d, e) = ...
func_that_has_a_lot_of_args(a, d) = ...
...

即使您这样做了,调用函数时仍然会面临大量的歧义: func_that_has_a_lot_of_args(x, y, z)引用x=a,y=b,z=cx=b,y=d,z=e等的定义,等等? Julia可以使用运行时类型信息在它们之间进行区分,但是对于非专业人员来说,阅读源代码将是完全不清楚的。

我认为正确完成下划线的最好方法是将其简单地合并到语言中。 毕竟,这将是对编译器的非常直接的更改。 每当下划线出现在函数应用程序中时,只需将其拉出即可创建lambda。 几周前,我开始研究实现这一点,但是不幸的是,我认为接下来的几周我没有足够的空闲时间来解决这个问题。 对于熟悉Julia编译器的人来说,使事情正常进行可能只需要一个下午。

@samuela
您能否弄清楚“将其拉出以创建lambda”是什么意思? - 我很好奇。 我也想知道如何实现。

@帕特里克博尔德
啊-知道了大概您可以使用这样的东西: filter(_ < 5, [1:10]) => [1:4]吗?
我个人认为filter(e -> e < 5, [1:10])更容易阅读; 更加一致-隐含的含义更少,尽管我同意了您的意思,但更为简洁。

除非您有一个能真正发挥作用的例子?

@samuela

另外,我想您对map的主张会为map(f)提供一种变通方法,该语法具有诸如map和filter的基本功能,但总的来说,它会遇到与手动下划线方法相同的复杂性问题。

我并不是建议一般只在mapfilter以及可能在其他一些看起来很明显的地方进行此操作。 对我来说,这就是map工作方式:接受一个函数并返回一个函数。 (肯定是Haskell所做的。)

会很烦人,更不用说优雅了。

我认为我们对此表示同意。 我希望有一种方法可以向语言中添加一些内容来处理方法调用,其中某些参数的类型为Underscore。 经过进一步思考,我认为可以归结为具有特殊字符的字符会自动扩展为lambda,或者具有具有特殊字符的类型会自动扩展为lambda。 两种方式我都没有强烈的感觉。 我可以看到这两种方法的优缺点。

@ H-225是的,下划线只是语法上的方便。 不确定它有多普遍,但Scala肯定有。 我个人喜欢它,但我认为这只是其中的一种。

@ H-225好吧,在这种情况下,我认为一个引人注目的相关示例就是函数链接。 不必写

[1, 2, 3, 5]
  |> x -> map(addone, x)
  |> x -> filter(isprime, x)
  |> sum
  |> x -> 3 * x
  |> ...

一个人可以简单地写

[1, 2, 3, 5]
  |> map(addone, _)
  |> filter(isprime, _)
  |> sum
  |> 3 * _
  |> ...

我发现自己在不知不觉中不断在支持它的语言中使用这种下划线语法(或某些轻微的变体),并且仅意识到当转换为不支持它的语言时,它是多么有用。

据我所知,目前至少有3.5种库/方法试图解决Julia中的此问题:Julia的内置|>函数,Pipe.jl,Lazy.jl和0.5的Julia内置的do记号在精神上相似。 不要抨击这些库或方法中的任何一个,但是如果朱莉娅支持下划线,则可以大大简化其中的许多库或方法。

@samuela,如果您想使用此想法的实现,可以尝试FunctionalData.jl,其中的示例如下所示:

<strong i="7">@p</strong> map [1,2,3,4] addone | filter isprime | sum | times 3 _

最后一部分显示了如何将输入传递给第二个参数(默认为参数1,在这种情况下,可以省略_ )。 反馈非常感谢!


编辑:以上内容只是简单地重写为:

times(3, sum(filter(map([1,2,3,4],addone), isprime)))

它使用FunctionalData.map和filter而不是Base.map和filter。 主要区别是参数顺序,第二个区别是索引约定(请参阅文档)。 在任何情况下,都可以通过反转参数顺序来简单地使用Base.map。 @p是一个非常简单的重写规则(从左到右成为从内到外,再加上对简单currying的支持: <strong i="17">@p</strong> map data add 10 | showall

showall(map(data, x->add(x,10)))

Hack可能会介绍类似这样的内容: https$$ ,这对于Julia来说是不可行的( $已经太重载了)。

FWIW,我真的很喜欢Hack的解决方案。

我也喜欢它,我的主要保留意见是,我仍然有点像terser lambda表示法,可能会在变量/插槽中使用_ ,并且最好确保它们不会冲突。

不能使用__吗? 您正在考虑的lambda语法是什么? _ -> sqrt(_)

当然可以。 该语法已经可以使用,更多的是不需要箭头的语法,因此您可以沿map(_ + 2, v)的行写东西,真正的问题是_周围有多少表达式

Mathematica是否没有类似的匿名论证系统? 怎么做
他们处理那些论点的范围?
2015年11月3日,星期二,上午9:09 Stefan Karpinski [email protected]
写道:

当然可以。 该语法已经可以使用,更多的是关于
不需要箭头,因此您可以沿线写一些东西
map(_ + 2,v)的问题,真正的问题是周围有多少
_所属的表达式。

-
直接回复此电子邮件或在GitHub上查看
https://github.com/JuliaLang/julia/issues/5571#issuecomment -153383422。

https://reference.wolfram.com/language/tutorial/PureFunctions.html ,显示
#符号是我一直在想的。
2015年11月3日,星期二,上午9:34,乔纳森·马尔默[email protected]写道:

Mathematica是否没有类似的匿名论证系统? 怎么做
他们处理那些论点的范围?
2015年11月3日,星期二,上午9:09 Stefan Karpinski [email protected]
写道:

当然可以。 该语法已经可以使用,更多的是关于
不需要箭头,因此您可以沿线写一些东西
map(_ + 2,v)的问题,真正的问题是周围有多少
_所属的表达式。

-
直接回复此电子邮件或在GitHub上查看
https://github.com/JuliaLang/julia/issues/5571#issuecomment -153383422。

Mathematica使用&进行定界。

与其执行像更短的lambda语法一样的常规操作(可以使用任意表达式并返回匿名函数),我们可以通过将可接受的表达式限制在函数调用中以及将可接受的变量/插槽限制在整个参数中来解决定界符问题。 这将为我们提供一个非常干净的多参数curring语法_替换了整个参数,所以语法可能是最少的,直观的和明确的。 map(_ + 2, _)将转换为x -> map(y -> y + 2, x) 。 无论如何,您想要lambafafy的大多数非函数调用表达式可能更长一些,并且更友好地适合->do 。 我确实认为在可用性与通用性之间进行权衡是值得的。

@durcan ,听起来很有希望–您能详细说明一下规则吗? 为什么第一个_保留在map的参数内,而第二个消耗整个map表达式? 我不清楚“将可接受的表达式限制到函数调用中”的含义,也不清楚“将可接受的变量/槽位限制于整个参数”的含义是什么...

好的,我认为我已经读过Dylan的一些文档,但我不得不怀疑map(_ + 2, v)有效,而map(2*_ + 2, v)不起作用。

还有一个非常挑剔的业务,这意味着_ + 2 + _将意味着(x,y) -> x + 2 + y_ ⊕ 2 ⊕ _将意味着y -> (x -> x + 2) + y因为+*是当前唯一解析为多参数函数调用而不是成对关联操作的运算符。 现在,可以争辩说应该解决此不一致问题,尽管这似乎需要_parser_判断哪些运算符是关联的,哪些不是,这似乎很糟糕。 但是我认为,任何需要知道解析器将a + b + c解析为单个函数调用还是嵌套调用的方案都可能有些疑问。 也许infix符号应该特别处理? 但是,不,这也令人感到腥。

是的,您需要权衡取舍。 一方面,考虑到我们当前的某些解析选择,长字符串运算符是最麻烦的语法(尽管很难根据语言的当前语义来对语言功能的语义进行指责)。 另一方面,长函数调用是它的优势。 例如,我们可以将您的问题重写为:

2*_ |> 2+_ |> map(_, v)

无论如何,我认为infix问题不会妨碍使用干净的无分隔符选项。 这对于大多数普通的函数调用确实很有帮助,这是当前的问题。 如果您愿意,可以使用一个可选的定界符来帮助解决这种特殊的歧义(在这里,我为该角色窃取了& ):

_ ⊕ 2 ⊕ _    # y -> (x -> x + 2) + y
_ ⊕ 2 ⊕ _ &  # (y , x) -> x + 2 + y

到目前为止,这是最好的建议,但我没有被全部卖掉。 很明显,当函数调用是显式的时,发生了什么,但是当函数调用由infix语法隐式时,则不清楚。

我想将这种方法更多地看作是一种灵活而通用的curring,而不是简短而甜美的lambda(即使那样,我们仍然可以使用可选的定界符来达到目标​​)。 我会喜欢更完美的东西,但是如果不添加更多象征性的声音(这个问题的对立面),我不确定如何到达那里。

是的,除了infix以外,我都喜欢。 该部分可能是可修复的。

好吧,在中缀位置中出现缩影可能是语法错误:

map(+(*(2, _), 2), v)      # curry is OK syntax, but obviously not what you wanted
map(2*_ + 2, v)            # ERROR: syntax: infix curry requires delimitation
map(2*_ + 2 &, v)          # this means what we want
map(*(2,_) |> +(_,2), v)   # as would this

我猜这也可能是一个警告。

称这种骂,使我感到困惑和错误。

当然,我猜这更像是部分函数应用程序(可以选择成为匿名参数的lambda)。 撇开名字,有什么想法吗?

我正在考虑这样的事情:

  • 如果_作为函数调用表达式的任何参数单独出现,则该函数调用将被匿名函数表达式替换,该函数使用与_参数一样多的参数,其主体为_参数的原始表达式按顺序替换为lambda参数。
  • 如果_出现在其他位置,则周围的表达式(但不包括箭头优先级或任何周围的括号(但不包括方括号或花括号))将被匿名函数替换,该匿名函数接受与_一样多的参数。 _实例按顺序替换为lambda参数。

例子:

  • f(_, b)x -> f(x, b)
  • f(a, _)x -> f(a, x)
  • f(_, _)(x, y) -> f(x, y)
  • 2_^2x -> 2x^2
  • 2_^_(x, y) -> 2x^y
  • map(_ + 2, v)map(x -> x + 2, v)
  • map(2_ + 2, v)map(x -> 2x + 2, v)
  • map(abs, _)x -> map(abs, x)
  • map(2_ + 2, _)x -> map(y -> 2y + 2, x)
  • map(2_ - _, v, w)map((x, y) -> 2x - y, v, w)
  • map(2_ - _, v, _)x -> map((y, z) -> 2y - z, v, x)
  • map(2_ - _, _, _)(x, y) -> map((z, w) -> 2z - w, x, y)
  • _x -> x
  • map(_, v)x -> map(x, v)
  • map((_), v)map(x -> x, v)
  • f = _f = x -> x
  • f = 2_f = x -> 2x
  • x -> x^_x -> y -> x^y
  • _ && _(x, y) -> x && y
  • !_ && _(x, y) -> !x && y

唯一开始出现争议的地方是有条件的-这些例子有点奇怪。

这仍然有些古怪和无原则,并且有一些极端的情况,但是我正在走向某个地方。

这让我感到一个非常糟糕的主意,如果您使用其他编程语言,Julia中的几乎所有语法都非常熟悉。 人们在这样看语法糖时根本不知道保存几个字符的“好处”是什么。

令我不高兴的例子:

  • 2v[_]x -> 2v[x] (好)
  • 2f(_)2*(x -> f(x)) (不太好)
  • _ ? "true" : "false"(x -> x) ? "true" : "false"
  • _ ? _ : 0(x -> x) ? (y -> y) : 0

我认为这里发生了更深层次的事情-有一些语法位置的概念,其中一个功能对象“有意义”-并且您想扩展到其中最接近的一个。 “想要”一个功能对象的经典位置是另一个功能的一个参数,但是赋值的右边是可以说“想要”一个功能对象的另一个位置(或者更准确地说,是更喜欢以功能为功能的主体)。

也许可以,但是关于do-block语法可以(并且曾经)提出相同的论点,我认为总体上来说它是非常成功和有用的。 这与更好的矢量化语法紧密相关。 它也不是史无前例的-Scala以类似的方式使用_ ,而Mathematica类似地使用#

我认为您还可以提出以下理由:_unfamiliar_选择
本质上是使用多个调度而不是单调度点运算符
迫使决定对代词参数使用简洁的语法
恢复人们熟悉的SVO命令。

2015年11月17日,星期二,下午12:09 Stefan Karpinski [email protected]
写道:

也许可以,但是关于do-block可以(并且曾经)提出相同的论点。
总体而言,我认为语法非常成功和有用。
这与更好的矢量化语法紧密相关。 也不是
这是史无前例的-Scala以类似的方式使用_,而Mathematica使用#
同样。

-
直接回复此电子邮件或在GitHub上查看
https://github.com/JuliaLang/julia/issues/5571#issuecomment -157437223。

这在C ++中也存在,尤其是在Boost中有多个库解决方案,它们使用_1, _2, _3作为参数(例如_1(x, y...) = x_2(x, y, z...) = y等),其局限性在于能够调用,例如fun(_1)x -> fun(x) ,必须显式地使fun与库兼容(通常通过宏调用,以使fun接受“ lambda类型”作为参数)。

我真的很想在Julia中使用这种简洁的lambda符号。
关于将2f(_)减为2*(x -> f(x))是否有必要按照“如果第一个规则适用于f(_) ,递归地评估f(_)扮演_ ,这还允许例如f(g(_))x -> f(g(x)) ,而“括号规则”使停在所需的级别,例如f((g(_)))f(x->g(x))

为此,我非常喜欢“ terse lambda符号”这个名称。 (比使用咖喱好得多)。

如果您传递的是多参数lambda,我真的希望_1_2_3的明确性。 总的来说,我经常发现在相同范围内重用变量名可能会造成混乱……并且在同一表达式中将_作为x和y似乎令人困惑。

我发现相同的简洁scala _ -syntax引起了一些混乱(请参阅scal中_的所有用法)。

此外,您通常想做:

x -> f(x) + g(x)

或类似的内容,如果以下操作无效,我会感到惊讶:

f(_) + g(_)

另外,您可能希望切换参数的顺序:

x, y -> f(y, x)
f(_2, _1)  # can't do with other suggested _ syntax

我认为语法允许显式匿名参数编号( _1_2_3 ...等)会很好,但是主要问题仍然存在:什么时候您才将部分应用的函数提升为简洁的lambda? 拉姆达身体到底是什么? 我可能会因为明确(带有分隔符)而不是隐式使用某种复杂的升级规则而出错。 什么应该

foo(_1, _1 + _2  + f(_1, v1) + g(_2, v3), _3 * _2, v2) + g(_4, v4) +
 f(_2, v2) + g(_3, v5) + bar(_1, v6)

是什么意思? 使用定界符(我将使用λ ),事情会更加清楚:

λ(foo(_1, λ(_1 + _2)  + λ(f(_1, v1) + g(_2, v3)), _3 * _2, v2) + g(_4, v4)) + 
λ(f(_2, v2) + g(_3, v5) + bar(_1, v6))

这显然是MethodError: + has no method matching +(::Function, ::Function) ,但是至少我可以从它的编写方式看出来。

我确实认为@StefanKarpinski可能会

这绝对是简洁与普遍性和易读性之间的权衡。 当然,引入比现有的->简洁的东西没有意义。 但是我也认为分隔符似乎是值得的。

也许不够简洁,但是捕获_参数的->前缀版本呢? 例如

(-> 2f(_) + 1)

我想前缀形式的优先级应该很低。 在某些情况下,这实际上可能允许省略括号,例如

map(->_ + 1, x)

现在我正在实施https://github.com/JuliaLang/julia/issues/5571#issuecomment -157424665

作为宏,它将转换行中所有此类出现。
棘手的部分是实现优先级。

在接下来的12小时内,我可能不会完成任务,因为现在是时候了
(也许在接下来的24小时内,但我可能不得不离开)
无论如何,一旦完成,我们就可以使用它。

来自https://github.com/JuliaLang/julia/issues/5571#issuecomment -157424665的一个奇怪的问题

  • f(_,_)x,y -> f(x,y) (这是合理的)
  • f(_,2_) →??

    • f(_,2_)x,y -> f(x,2y) (合理)

    • f(_,2_)x-> f(x,y->2y) (我认为规则所暗示的内容以及我的原型所产生的内容)

但是我不确定我是否正确。

这是我的原型。
http://nbviewer.ipython.org/gist/oxinabox/50a1e17cfb232a7d1908

实际上,它肯定无法通过某些测试。

无法在当前AST层中考虑括弧-通常(总是?)已经解决了这些问题。

我认为仍然足够玩

R中magrittr的一些规则可能有用:

如果链以开头。 ,它是一个匿名函数:

. %>% `+`(1)

与function(x)x + 1相同

链接有两种模式:
1)通过插入作为第一个参数以及出现的任何点来链接。
2)仅链接到出现的点。

默认模式是模式1。但是,如果点本身作为要链接的函数的参数而出现,则magrittr会针对链中的该步骤切换到模式2。

所以

2 %>% `-`(1) 

是2-1

1 %>% `-`(2, . )

也是2-1

模式2也可以通过括在方括号中来指定:

2 %>% { `-`(2, . - 1) }

将与2-(2-1)相同。

还要注意的一点是,能够在模式1和模式2之间进行智能切换几乎可以完全解决以下问题:朱莉娅对于将可能链接到第一位置的参数不太一致。 我也忘记了要注意的是,方括号可以允许评估一段代码。 这是magrittr手册中的示例:

虹膜%>%
{
n <-样本(1:10,大小= 1)
H <-head(。,n)
T <-tail(。,n)
rbind(H,T)
}%>%
概要

目前,这只是一个半成品的想法,但是我想知道是否存在一种方法,可以通过修改Tuple构造函数以使缺失的值返回a,从而同时解决“ terse lambda”和“ dummy variable”问题。返回一个元组而不是元组的lambda? 因此, (_, 'b', _, 4)将返回(x, y) -> (x, 'b', y, 4)

然后,如果我们巧妙地更改函数调用的语义,使得foo(a, b)表示“将foo应用于元组(a, b)或者如果参数是一个函数,则应用foo到函数返回的元组”。 这将使foo(_, b, c)(1)等于apply(foo, ((x) -> (x, b, c))(1))

我认为这仍然不能解决infix表示法的问题,但是我个人对仅适用于带括号的函数调用的简洁lambda感到满意。 毕竟,如果绝对必要,可以始终将1 + _改写+(1, _)

@jballanc但是,元组的构造和函数的应用是两个截然不同的概念。 至少,除非我对朱莉娅语义的理解存在严重缺陷。

@samuela我的意思是foo(a, b)相当于foo((a, b)...) 。 也就是说,即使在实践中从未构造过元组,也可以在概念上将函数的参数视为元组。

@我已尝试通读此讨论,但是对我来说已经太久了,无法跟踪所讲的所有内容-如果在此重复的次数过多,抱歉。

我只想对|>做为do “魔术”的补充进行表决。 据我所知,最简单的方法是让它表示

3 |> foo == foo(3) # or foo() instead of just foo, but it would be nice if the parentheses were optional
3 |> foo(1) == foo(1, 3)
3 |> foo(1,2) == foo(1,2,3)

换句话说, a |> f(x)对_last_参数起作用,而f(x) do; a; end对_first_起作用。 这将立即使其与mapfilterallany等兼容。 ,而不增加范围限定_参数的复杂性,并且鉴于已经存在的do语法,我认为这不会给代码读者带来不合理的概念负担。

我使用这样的管道运算符的主要动机是收集管道(请参阅#15612),我认为这是一种非常强大的构造,并且正在以多种语言获得发展(表明这既是人们想要的功能,又是一种功能)。他们会明白的)。

@malmaud不错! 我喜欢这已经是可能的:D

但是,这两个变体之间的可读性差异确实很大:

# from Lazy.jl
@> x g f(y, z)

# if this became a first-class feature of |>
x |> g |> f(y, z)

我认为主要的可读性问题是,没有视觉线索可以说明表达式之间的边界在哪里- x gg f(x,的空格会显着影响代码的行为,但是空格f(x, y)不会。

由于@>>已经存在,将这种行为添加到|>中的行为是否可行?

(我不想在运算符的符号上花费过多的精力,因此,不要仅在符号的问题上就忽略该提议。但是,我们可以注意到,“管道”似乎是对此的自然定义(参见术语“收集管道”),并且例如F#已经为此使用了|> ,尽管由于F#函数与Julia函数的不同,当然它的语义也略有不同。)

是的,我肯定在可读性方面同意。 使|>像您在0.5中描述的那样行为在技术上没有挑战,这只是一个设计问题。

同样,有可能使Lazy.jl的@>>宏解析函数链接到|>

嗯然后,我将开始为Lazy.jl进行PR,但这并不意味着我不希望它不在0.5 :)我认为我对Julia解析器和但是,除非我得到一些广泛的指导,否则如何更改|>的行为以对此进行帮助。

我不认为我在此线程中提到过,但是我还有另一个链接包ChainMap.jl。 它总是替换为_,并有条件地插入第一个参数。 它还尝试集成映射。 参见https://github.com/bramtayl/ChainMap.jl

所以我们目前的各种努力清单等等
我认为值得人们检查一下(理想情况是在发表意见之前,但有)
它们都略有不同。
(我尝试按时间顺序订购)。

配套

非包装原型

有关:


也许应该将其编辑为顶级帖子之一。

更新日期:2020-04-20

这不是真正尝试实现部分应用程序的尝试,而是更多的类型系统实验,但这是一个奇怪的尝试: https :

用法:

include("partial_underscore_generated.jl")
using GeneratedPartial

const sub = partialize(-)
sub(_,2)(1) == 1-2
sub(_,_)(1,2) == 1-2
sub(_,__)(1)(2) == 1-2
sub(__,_)(2)(1) == 1-2 #hehehe

# or
<strong i="8">@partialize</strong> 2 Base.:+ # evily inserts methods in + and allows partializations for 2 arguments
(_+2)(1) == 1+2

# fun:
sub(1+_,_)(2,3) == sub(1+2,3)
sub(1+_,__)(2)(3) == sub(1+2,3)
(_(1)+_)(-,1) == -1+1

# lotsafun:
appf(x::Int,y::Int) = x*y
appf(f,x) = f(x)
<strong i="9">@partialize</strong> 2 appf

appf(1+_,3)(2) == appf(1+2,3)
appf(?(1+_),3) == appf(x->(1+x), 3)
appf(?sub(_,2),3) == appf(x->x-2,3) # I made a method *(::typeof(?),::PartialCall), what of it!!?

# wooooooooooooooooooooooooooooooooo
const f = sub
f(_,f(_,f(_,f(_,f(_,f(_,f(_,f(_,f(_,_)))))))))(1,2,3,4,5,6,7,8,9,10) == f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,10)))))))))
f(_,f(__,f(___,f(____,f(_____,f(______,f(_______,f(________,f(_________,__________)))))))))(1)(2)(3)(4)(5)(6)(7)(8)(9)(10) == f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,10)))))))))

# this answers Stefan's concern (which inspired me to make this hack in the first place)
#
#    const pmap = partialize(map)
#    map(f(_,a),   v) == map(x->f(x,a), v)
#    pmap(?f(_,a), v) == map(x->f(x,a), v)
#    pmap(f(_,a),  v) == x->map(f(x,a), v)
#
# it adds a few other issues, of course...


当然不是实施方案,但我觉得它很有趣,也许有人可以从中获得一半的好主意。

PS忘了提及@partialize也可用于整数数组文字和范围:

<strong i="15">@partialize</strong> 2:3 Base.:- # partialized for 2 and 3 arguments!

(_-_-_)(1,2,3) == -4
(_-_+_)(1,2,3) == +2

好的,我一直在考虑函数组成,尽管IMO在SISO情况下很清楚,但我想我正在考虑如何在小型链式代码块中使用Julia的MISO(准MIMO?)。

我喜欢REPL。 很多。 它很可爱,很酷,它可以让您像MATLAB或python或shell一样进行实验。 我喜欢从包或文件中获取代码,然后将其复制粘贴到REPL中,甚至包括多行代码。 REPL在每一行都进行评估,并向我显示发生了什么。

在每个表达式之后,它还会返回/定义ans 。 每个MATLAB用户都知道这一点(尽管在这个阶段,这是一个不好的说法!)。 大概大多数Julia用户都曾经看过/使用过它。 在奇怪的情况下,我使用ans来逐块地玩游戏,意识到我想在上面写的内容中再增加一步。 我不喜欢使用它具有破坏性,所以我确实倾向于在可能的情况下避免使用它,但是这里的每一个提议都只处理组成的第一步的返回寿命。

对我来说, _很有魔力,但我知道许多人可能会不同意。 因此,如果我想将包中的代码复制粘贴到REPL中并观看它的运行,并且如果我想要一种看起来并不神奇的语法,那么我可以建议:

<strong i="12">@repl_compose</strong> begin
   sin(x)
   ans + 1
   sqrt(ans)
end

如果函数返回多个输出,则可以在下一行插入ans[1]ans[2]等。 它已经完全适合Julia的MISO模型和Julia的MISO模型的单一级别,并且已经非常符合Julia语法标准,只是没有在文件中。

该宏很容易实现-只需将Expr(:block, exprs...)转换为Expr(:block, map(expr -> :(ans = $expr), exprs) (开始时也是let ans ,也许可能有一个版本可以使一个异常函数使用输入或其他内容?)。 住在基础上是没有必要的(尽管REPL内置在Julia中,并且与此类似)。

无论如何,只是我的观点! 这是一个很长的话题,很长一段时间以来我都没有看过!

它还在每个表达式后返回/定义ans。 每个MATLAB用户都知道这一点(尽管在这个阶段,这是一个不好的说法!)。

实际上,另一个论点是,如果在函数链中使用_ ,那么REPL还应该返回_而不是ans (对我来说,这足以删除魔术”)。

使用_作为语言的“ it value”有很多先例。 当然,这与使用_作为放弃分配的名称和terser lambda的想法相矛盾。

我很确定这在Lazy.jl中以<strong i="5">@as</strong> and begin ...

在对话的开始阶段就放弃了使用.进行链接的想法,但是它具有悠久的历史,并且可以最大程度地减少其他语言采用者的学习难度。 对我来说很重要的原因是

type Track
  hit::Array{Hit}
end
type Event
  track::Array{Track}
end

event.track[12].hit[43]

trackhit是简单数组时,给我事件第12条的第43个匹配

event.getTrack(12).getHit(43)

如果必须动态提供它们,应该给我同样的东西。 我不想说

getHit(getTrack(event, 12), 43)

越深入,情况就越糟。 由于这些都是简单的函数,因此它的论点比函数链接的论点更广(la Spark)。

我之所以写这篇文章,是因为我刚刚了解了Rust的特征,出于相同的原因,这可能是Julia的一个很好的解决方案。 与Julia一样,Rust具有仅数据structs (Julia type ),但是它们也具有impl来将函数绑定到struct 。 据我所知,它是纯语法糖,但它允许使用我上面描述的点表示法:

impl Event {
  fn getTrack(&self, num: i32) -> Track {
    self.track[num]
  }
}

impl Track {
  fn getHit(&self, num: i32) -> Track {
    self.track[num]
  }
}

朱莉娅可能是

impl Event
  function getTrack(self::Event, num::Int)
    self.track[num]
  end
end

impl Track
  function getHit(self::Track, num::Int)
    self.hit[num]
  end
end

上面建议的语法不对self进行任何解释:它只是一个函数参数,因此与多个分派应该没有冲突。 如果要对self进行最小解释,则可以使第一个参数的类型隐式,以便用户不必键入::Event::Track在每个函数中,但不做任何解释的一件好事是,“静态方法”只是impl中的函数,没有self 。 (Rust将它们用于new工厂。)

与Rust不同,Julia在types上具有层次结构。 它还可以在impls上具有相似的层次结构,以避免代码重复。 可以通过使数据type和方法impl层次结构完全相同来构建标准的OOP,但是这种严格的镜像不是必需的,在某些情况下是不希望的。

有一个棘手的问题:假设我在impl命名了函数trackhit impl ,而不是getTrackgetHit ,使他们发生冲突与数组trackhittype 。 然后event.track返回数组或函数吗? 如果立即将其用作函数,这可能有助于消除歧义,但是types也可以容纳Function对象。 也许只是应用一条总括规则:在点之后,首先检查相应的impl ,然后检查相应的type

再三考虑,要避免为概念上相同的对象( typeimpl )有两个“包”,该如何做:

function Event.getTrack(self, num::Int)
  self.track[num]
end

将函数getTrack绑定到类型Event实例,这样

myEvent.getTrack(12)

产生与应用于(myEvent, 12)的函数相同的字节码?

新功能是function关键字后的typename-dot-functionname语法及其解释方式。 如果第一个参数与其绑定的类型相同(或如上所示为隐式),这仍将允许多次分派,类似于Python的self ,并且如果允许,则使用“静态方法”第一个参数不存在或与绑定的类型不同。

@jpivarski您是否有理由认为点语法(通过阅读此线程,有很多缺点)比其他允许链接的结构更好? 我仍然认为创建do但是对于最后一个参数,以某种形式的管道语法(例如|> )支持,将是最好的方法:

event |> getTrack(12) |> getHit(43)

我可以看到类似Rust的方法可能更好的主要原因是它有效地将左侧用作函数的命名空间,因此您可以执行parser.parse之类的操作Julia Base.parse函数。 我赞成同时提供Rust提案和Hack风格管道。

@tlycken不过这是模棱两可的语法,具体取决于优先级。
记住|> vs调用的可能性可能会造成混淆,因为它实际上并没有给出任何提示。
(也没有建议其他几个选项。)

考虑

foo(a,b) = a+b
foo(a) = b -> a-b

2 |> foo(10) == 12   #Pipe Precedence > Call Precedence 
2 |> foo(10) == 8     #Pipe Precedence < Call Precedence   

@oxinabox我实际上并不是在建议它只是“常规”运算符,而是该语言的语法元素; 2 |> foo(10)糖到foo(10, 2)的方式与foo(10) do x; bar(x); end糖到foo(x -> bar(x), 10) 。 这意味着管道优先于调用优先(无论如何,这是最有意义的)。

仅在语法主题上, .|>视觉上不太引人注目,并且当然更标准。 我可以编写流畅的函数链,用.分隔,没有空格(每个字符一个),任何人都可以阅读; 使用|> ,我必须添加空格(每个空格四个字符),这对于大多数程序员来说都是视觉上的颠簸。 类似于shell脚本的|很好,但是由于>不能立即识别。

我是否正确阅读了该线程,针对dot的论点是它是否应该从type获得成员数据还是从impl (我的第一个建议)获得成员函数还是模棱两可的命名空间(我的第二个建议)? 在第二个建议中,必须在type定义之后_ type创建的函数名称空间中定义的函数,以便它可以拒绝在此处遮盖成员数据。

我添加两个名称空间点(我的第二个建议)和|>都可以。 尽管它们都可以用于流畅的链接,但它们在目的和效果上却有很大不同。 但是,如上所述的|>do并不完全对称,因为do要求它填充的参数是一个函数。 如果您说的是event |> getTrack(12) |> getHit(43) ,那么|>适用于非功能类( EventsTracks )。

如果您说的是event |> getTrack(12) |> getHit(43) ,那么|>适用于非功能性( EventsTracks )。

实际上,不-通过将其左操作数作为函数调用的最后一个参数插入,在函数right_上适用于函数。 event |> getTrack(12)getTrack(12, event)因为在右边,而不是在左边。

这将意味着a)优先于函数调用(因为它是对调用的重写),以及b)左右应用程序顺序(使它成为getHit(43, getTrack(12, event))而不是getHit(43, getTrack(12), event) ) 。

但是getTrack's签名是

function getTrack(num::Int, event::Event)

因此,如果event |> getTrack(12)event插入到getTrack's最后一个参数中,则会将Event放入第二个参数,而不是Function 。 我只是用do和第一个参数尝试了等效项,Julia 0.4抱怨该参数需要是一个函数。 (可能是因为do event end被解释为返回event的函数,而不是event本身。)

对我来说,函数链接似乎是与点( . )语法讨论的内容无关的另一个问题。 例如, @jpivarski ,您已经可以完成Julia中Rust中提到的大部分内容,而无需任何新功能:

type TownCrier
  name::AbstractString
  shout::Function

  function TownCrier(name::AbstractString)
    self = new(name)
    self.shout = () -> "HELLO, $(self.name)!"
    self
  end
end

tc = TownCrier("Josh")
tc.shout()                                #=> "HELLO, Josh!"
tc.name = "Bob"
tc.shout()                                #=> "HELLO, Bob!"

在不试图过多破坏对话的情况下,我建议我们真正需要解决的是如何在Julia中进行有效的函数管理。 如果我们有一种好的方法来处理函数,那么有关如何为函数链中的参数指定位置的问题将逐渐消失。 另外,如果可以指定功能主体并在构造时简单地用“ self”命令,则类似于上述的构造将更清洁。

@andyferris我一直在使用Python,我真的很喜欢_引用上一个表达式的结果。 但是它在函数内部不起作用。 如果我们能够在任何地方使用它,那就太好了:在begin块,函数等内部。

我认为这可以完全取代链接。 它对参数顺序没有任何歧义。 例如,

begin
    1
    vcat(_, 2)
    vcat(3, _)
end

# [3, 1, 2]

正如@MikeInnes提到的那样, @_已经可以使用它(尽管最初它不是那样工作的,但ChainMap.jl现在也使用了这种链接)。

希望这可以与点融合一起使用,至少在内部块中

begin
    [1, 2, 3]
    .+(_, 2)
    .*(_, 2)
    .-(10, _)
end

或者,使用@chain_map语法,

begin
    ~[1, 2, 3]
    +(_, 2)
    *(_, 2)
    -(10, _)
end

当前,如果函数是在构造函数中定义的,则存在一种与对象进行函数链接的方法。 例如,函数Obj.times:

type Obj
    x
    times::Function
    function Obj(x)
       this = new(x)
       this.times =  (n) -> (this.x *= n; this)
       this
    end
end

>>>Obj(2).times(3)
Obj(6,#3)

在类型定义之外定义的成员函数(特别是函数)的实现又如何呢? 例如,函数Obj.times将写为:

member function times(this::Obj, n)
     this.x *= n
     return this
end

>>>Obj(2).times(3)
Obj(6,#3)

其中member是成员函数的特殊关键字。
成员函数可以访问对象数据。 稍后,将在对象变量之后使用点来调用它们。
这个想法是通过使用在类型定义外部定义的“成员”函数来重现在构造函数内部定义的函数的行为。

诸如此类的事情是在Rust中使用方法语法完成的。 从概念上讲,它与函数链接不同,尽管它可以使链接看起来像某些OO语言一样。 最好最好分别解决这些问题。

我已阅读此书和一些相关问题,这是我的建议:

基本链接
in1 |> function1
in1 |> function1(|>)

in2 |> function2(10)
in2 |> function2(|>,10)

更多链接:
in1 |> function1 |> function2(10,|>)

链分支和合并:
用分支out1out2分支两次:
function1(a) |out1>
function2(a,b) |out2>

使用分支out1out2
function3(|out1>,|out2>)

那懒惰呢?
我们需要像function!(mutating_var)约定吗?
对于惰性函数,我们可以使用function?() ...

通过正确使用缩进,很容易在视觉上跟踪关联的调用图上的数据依赖性。

我只是在使用现有的|>运算符进行函数链接的模式。 例如,这些定义:
``朱莉娅

过滤

不变的MyFilter {F}
flt :: F
结束

函数(mf :: MyFilter)(源)
过滤器(mf.flt,源)
结束

函数Base.filter(flt)
MyFilter(flt)
结束

采取

不变的MyTake
n :: Int64
结束

函数(mt :: MyTake)(源)
服用(来源,mt.n)
结束

函数Base.take(n)
MyTake(n)
结束

地图

不变的MyMap {F}
f :: F
结束

函数(mm :: MyMap)(源)
地图(mm.f,来源)
结束

函数Base.map(f)
我的地图(f)
结束
enable this to work: 朱莉娅
1:10 |>过滤器(i-> i%2 == 0)|> take(2)|>地图(i-> i ^ 2)|>收集
`` Essentially the idea is that functions like filter return a functor if they are called without a source argument, and then these functors all take one argument, namely whatever is "coming" from the left side of the |> . The |>``然后将所有这些函子链接在一起。

filter等也可以返回一个带一个参数的匿名函数,不确定这些选项中哪个更有效。

在我的示例中,我正在覆盖Base中的map(f::Any) ,我不太了解map的现有定义是什么...

我只是想出了这种模式,而我略带草草的表情并没有显示任何类似的讨论。 人们怎么看? 这可能有用吗? 人们能想到这个缺点吗? 如果这行得通,也许现有的设计实际上足够灵活以实现一个相当全面的链接故事?

这似乎不适用于任意函数,仅适用于已定义MyF的函数?

是的,这仅适用于选择启用的功能。 显然,这不是一个非常通用的解决方案,并且在某种意义上与矢量化是同一个故事,但是,鉴于并非我们希望的所有内容都将其设为1.0,因此这种模式可能使人们不得不诉诸于一大堆场景宏。

本质上,想法是像filter这样的函数在没有源参数的情况下返回函子,然后这些函子都带有一个参数,即|>左侧的“即将出现的”。

这几乎就是Clojure换能器的本质。 明显的区别是Clojure在减速器的概念之上构建了传感器。 简而言之,对集合进行操作的每个函数都可以分解为“映射”函数和“归约”函数(即使“归约”函数只是concat )。 以这种方式表示集合函数的优点是,您可以重新安排执行,以便可以对所有“映射”进行流水线处理(特别是对于大型集合而言)。 因此,换能器只是在调用它们而无需操作集合时返回的这些“映射”的提取。

无需如此复杂。 函数可以选择使用闭包进行循环:

Base.map(f)    = (xs...) -> map(f, xs...)
Base.filter(f) = x -> filter(f, x)
Base.take(n)   = x -> take(x, n)

当然,这不是软件包应该做的事情,因为它更改了所有软件包的这些方法的含义。 像这样零零碎碎地做起来并不是很直观—哪个参数应该优先?

我更喜欢上面讨论过的呼叫站点语法解决方案,将f(a, _, b)降低x -> f(a, x, b) 。 但是,正如上面冗长的讨论所述,这很棘手。

无需如此复杂。 函数可以选择使用闭包

是的,我已经建议以上内容,只是不确定两者之间是否存在性能差异。

哪些参数应优先考虑?

是的,然后我们实际上有像filtertake ,在一种情况下,我们将集合作为第一个参数,将另一个作为最后一个参数...我觉得至少对于类似迭代器的操作,通常可能会对该问题有一个明显的答案。

一旦_是可用的特殊符号

是的,我完全同意有一个更通用的解决方案,可能就是@malmaud

没有性能差异,因为闭包实际上只是生成您用手编写的代码。 但是,由于您只是在进行弯腰,因此您可以编写一个函数为您执行此操作( curry(f, as...) = (bs...) -> f(as..., bs...) )。 那会处理地图和过滤器; 过去也有提议实现curry来实现像curry(take, _, 2)这样的哨兵值。

我来这里是因为我目前正在学习三种语言:D,Elixir和现在的Julia。 在D中,存在统一的函数调用语法,例如在Rust中,在Elixir中,您具有管道运算符。 两者基本上都实现了这里建议的功能链接,我真的很喜欢这两种语言的功能,因为它易于掌握,易于实现,并且可以使使用流和其他类型的数据管道的代码更具可读性。

到目前为止,我只看过有关julia语法的简短教程,但是我立即在Google上搜索了此功能,因为我希望Julia也会有类似的东西。 因此,我认为这是我这方面对此功能要求的+1。

嗨伙计,

请允许我+1此功能请求。 这是非常需要的。 考虑以下Scala语法。

Array(1,2,3,4,5)
  .map(x => x+1)
  .filter(x => x > 5)
  .reduce(_ + _)

使用过Spark或其他基于MapReduce的大数据工具的人们将非常熟悉此语法,并且将以这种方式编写大型而复杂的作业。 甚至相对较古老的R也允许以下情况。

c(1,2,3,4,5) %>%
  {. + 1} %>%
  {.[which(. > 5)]} %>%
  sum

请注意,巧妙地使用代码块来替代适当的功能编程-不是最漂亮,而是功能强大。 在Julia中,我可以执行以下操作。

[1,2,3,4,5] |> 
  _ -> map(__ -> __ + 1, _) |>
  _ -> filter(__ -> __ < 5, _) |>
  _ -> reduce(+, _)

但这是可怕的和丑陋的。 如果我们不能在Scala中使用面向对象的代码,则管道运算符将变得异常重要。 最简单的解决方案是让管道输入_first参数_,除非显式使用了诸如_这样的通配符,但这仅在将map更改为将数据结构作为第一个参数和函数的情况下才有意义作为第二。

还应该有一些等同于Scala的Array(1,2,3).map(_ + 1)以避免过多的_ -> _ + 1和类似的语法。 我喜欢上面将[1,2,3] |> map(~ + 1, _)转换为map(~ -> ~ + 1, [1,2,3])的想法。 感谢您的光临。

对于后者,我们以紧凑的语法[1, 2, 3] .+ 1广播。 诸如此类的用于还原(也许过滤)的东西会非常酷,但似乎是一个很大的要求。

有理由指出,管道和do争夺第一个函数参数。

我会提醒您,我们已经有了新的话题,
不是一个,不是两个,而是五个软件包,它们针对建议的语法提供了julia基本SISO管道功能的扩展。
请参阅以下列表: https :

有理由指出,管道和管道确实会争夺第一个函数参数。

如果要在基础中获得额外的管道功能,则该位置未标有_等。
然后,我认为这会将参数添加到最终位置,而不是第一个位置。
使其更像“假装伪造/部分申请”

我在上面的帖子旨在作为一个简单的示例,旨在说明所讨论的语法问题。

实际上,通常在一个链中使用数百种操作,其中许多是非标准的。 想象一下使用自然语言大数据。 您编写了一系列转换,这些转换采用字符串,过滤出汉字,用空格分隔,过滤出诸如“ the”之类的单词,并通过网络使用的黑盒软件工具将每个单词转换为“标准化”形式服务器,通过另一个黑盒工具附加有关每个单词的普遍程度的信息,每个唯一单词的权重之和,100个其他操作等。

我正在考虑这些情况。 由于规模庞大,在不使用方法调用或管道的情况下进行此类操作非常困难。

我不知道什么是最好的设计,我只是鼓励大家考虑用例,并比目前使用的语法更加精细。

这应该在Juno和Julia 0.6中工作

```{朱莉娅}
使用LazyCall
@lazy_call_module_methods基本生成器
@lazy_call_module_methods迭代器过滤器

使用ChainRecursive
start_chaining()


```{julia}
[1, 2, 3, 4, 5]
<strong i="14">@unweave</strong> ~it + 1
Base.Generator(it)
<strong i="15">@unweave</strong> ~it < 5
Iterators.filter(it)
reduce(+, it)

我对在此问题的评论中看到的一些语法有疑问:
https://stackoverflow.com/questions/44520097/method-chaining-in-julia

@somedadaism ,问题是问题,而不是“广告”堆栈溢出问题。 此外,Julia-people在SO上非常活跃,在https://discourse.julialang.org/上(甚至更是如此)

真是的,这真是多么复杂。 +1代表一些不错的语法。 对我来说,管道的主要用途还用于处理数据(框架)。 想想dplyr。 我个人并不真正在乎默认情况下通过first / last传递,但是我想大多数程序包开发人员都会让他们的函数将数据作为第一个参数接受-可选参数呢? +1之类的

1 |> sin |> sum(2, _)

如前所述,可读性和简单性非常重要。 我不想错过整个dplyr / tidyverse风格的数据分析服务...

我想补充一点,我发现Elixir的多行语法对于管道运算符也非常有用。

1
|> sin
|> sum(2)
|> println

等于println(sum(sin(1),2))

只是为了说明javascript世界中的一个建议。 他们使用?运算符,而不是_~ ,我们已经有了它的含义( _忽略某些内容,而~按位否或制定者)。 鉴于我们当前使用的?与javascript相同,我们也可以将其用于可卷曲的占位符。

这是他们的建议的外观(使用javascript,但在julia中也有效:)

const addOne = add(1, ?); // apply from the left
addOne(2); // 3

const addTen = add(?, 10); // apply from the right
addTen(2); // 12

// with pipeline
let newScore = player.score
  |> add(7, ?)
  |> clamp(0, 100, ?); // shallow stack, the pipe to `clamp` is the same frame as the pipe to `add`.

const maxGreaterThanZero = Math.max(0, ...);
maxGreaterThanZero(1, 2); // 2
maxGreaterThanZero(-1, -2); // 0

总结,因为我因为其他原因开始写一篇。
另请参阅我以前的相关软件包列表注释。

_任何混乱都不会中断,可以在1.x中完成,因为https://github.com/JuliaLang/julia/pull/20328

这全部归结为两个主要选项(除了现状)。
两者(出于所有目的和目的)都可以通过宏来实现以重写语法。

_搞混以创建匿名函数

@StefanKarpinskiTerse Lambdas或类似的语法,其中_ (在RHS表达式中)的存在表示整个表达式是一个匿名函数。

  • 几乎可以通过宏来处理

    • 唯一无法完成的事情是(_)_ 。 这只是身份功能,所以并不重要

    • 这将适用于所有地方,因此不仅对|>有用,而且对紧凑地编写诸如map(log(7,_), xs)log(7, _).(xs)以使每个元素的基数为7的日志很有用。的xs

    • 如果我们正在做任何事情,我个人都赞成这一点。

弄乱|>以使其执行替换

选项包括:

  • 使其RHS像咖喱一样发挥作用

    • 实际上,我认为这很糟糕(尽管也许有一个不间断的版本可以检查方法表。但是我认为那只是令人困惑)

  • 使它使_行为特别(请参阅上面的选项,和/或通过重写来伪造它的各种方法)

    • 一种方法是允许创建infix宏,然后可以编写@|>@并在包中定义所需的方式(一旦https://github.com/JuliaLang/julia/问题/ 11608)

    • 或固有地赋予它们特殊的属性

  • 正如我所说的,我们有大量的宏实现可以做到这一点,请参阅我的相关软件包列表。
  • 有些人还建议对其进行更改,以使其(与所有其他运算符不同)能够在表达式未结束之前就在该行上引起该表达式。 所以你可以写
a
|> f
|>g

而不是当前:

a |>
f |>
g

(在没有包围该块的情况下,不可能用宏来实现。而包围该块已经使其能够正常工作了)

  • 我个人不喜欢这些建议,因为它们使|> (已经是不受欢迎的运算符)成为超级魔术了。

编辑:正如@StefanKarpinski在下面指出的那样,这实际上始终是突破性的变化。
因为有人可能依赖typeof(|>) <: Function
这些更改将使其成为语言语法的一个元素。

奖励选项:永远不会发生的选项:在所有位置添加currying#554

在语言中添加currying为时已晚。
这将是疯狂的突破,到处都增加了许多歧义。
只是非常混乱。

我认为通过这两个选项,它基本上涵盖了所有值得考虑的方面。
我认为没有其他任何有见地的说法(即不算“ +1想要这个”;或者上面的微变量的重复)。

我很想在|> 0.7中弃用

我很想在0.7中弃用|>,以便我们以后可以使用更有用且可能类似于函数的语义来介绍它,我怀疑这对于使管道正常工作是必要的。

该列表上唯一有争议的情况是|>使其右侧假装为currying。
可以将其参数插入第一个或最后一个参数位置或其他固定位置(第2个可能有意义)。
使用_作为要插入参数的标记。

没有其他破坏性的建议提出,任何人都对此含糊其辞地认真对待。
如果该操作还有其他明智而又突破性的定义,我会感到惊讶,
在过去的近四年中,没有人提出过建议。

无论如何,弃用它并不可怕。
使用它的程序包仍然可以通过其中一个宏程序包获得它。

另一个想法可能是使|>保持当前行为,并以不需要使用Shift键的其他名称引入新功能,例如\\ (这不需要甚至现在还解析为运算符)。 我们曾经在Slack上谈论过这一点,但我认为历史可能已被时间束缚。

管道通常以交互方式使用,并且操作员键入的难易程度会影响使用时的“轻度”感觉。 单个字符|也可能很好。

单个字符|也可能很好。

交互式是,但是将它放在.juliarc.jl中就足够了(我已经有很长时间了; -p)

不需要使用shift键

注意,这是一个高度依赖区域的属性。 例如,我的瑞典语键盘已经运送了许多字符进行移位和AltGr组合(相当糟糕),为另外三个字母腾出了空间。

是否有为此目的使用|>传统? [Mathematica](http://reference.wolfram.com/language/guide/Syntax.html)具有//用于后缀功能应用程序,应该易于在大多数键盘中键入,并且可能会可用尚未用于注释(如C ++)或整数除法(如Python)。

带有|的东西与shell脚本有着很好的联系,尽管如果单个|当然可以按位或。 ||是否用于逻辑或? 那么|||呢? 键入三次难以到达的字符并不比键入一次困难。

是否有使用|>达到此目的的传统?

我相信|>的传统源于ML系列语言。 对于操作员,很少有编程语言社区像ML / Haskell社区那样探索这一领域。 几个例子:

要添加到上面的列表中,R使用%>%-尽管该语言已过时,但我认为其管道功能设计得很好。 大括号语法是使之有效的原因之一,它允许编写如下内容

x %>% { if(. < 5) { a(.) } else { b(.) } }

由于使用了end语句,因此Julia中的详细程度会更高。 尽管上面的示例很抽象,但是很多人在编写数据预处理代码时使用相似的语法。 通过讨论任何当前的建议,是否可以通过使用括号来实现类似于上述的目的?

我还鼓励使用管道运算符,该运算符可以直观地表明参数正在向前传递,例如>符号。 这为初学者和不熟悉函数式编程的人提供了有用的提示。

即使|>的拟议用法在语法上与当前的典型用法不兼容,但它们与|>作为运算符也不兼容-因为它们大多数涉及给|>得多强大的功能不只是单纯的中缀功能。 即使我们确定要保留x |> f |> g来表示g(f(x)) ,将其保留为常规运算符也可能会阻止进一步的增强。 虽然将|>更改|> _atypical_用法,因此仍然不被允许–依靠它作为操作员。 打破非典型用法仍在打破,因此在1.x发行版中不允许这样做。 如果我们想用|>做上面的建议,我们需要使用|>语法而不是1.0中的函数。

@StefanKarpinski现在甚至在桌子上都在执行|>语法而不是函数吗? 是否可以及时将其放在桌面上以使其在1.0位置就位?

@StefanKarpinski现在甚至在表上也正在使用|>语法而不是函数? 是否可以及时将其放在桌面上以使其在1.0位置就位?

表格中将其从0.7中弃用并将其从1.0中完全删除。
然后在1.x期间将其带回语法元素。
在那时,这将是一个不间断的变化。

有人需要这样做,但我不认为这是一个非常困难的变化,所以是的,它已经摆在桌面上。

|>会被弃用什么? 在Lazy.jl中实现?

x |> f可以弃用为f(x)

如何弃用l>但同时引入说ll>与当前l>具有相同行为的怎么样?

如果我们只使用弃用项而不进行任何替换,那么实质上依赖当前行为的软件包将没有好的选择。 如果他们在此期间得到的表情不太好,则可以继续其当前设计,但我们仍然保留该选项,以便将来为l>找到一个非常好的解决方案。

这在很大程度上影响了Query and friends生态系统:我创建了一个与R tidyverse中的管道语法非常相似的系统。 整个过程非常全面:它涵盖了当前七种表格文件格式的文件io(还有两种非常接近的格式),所有查询操作(如dplyr)和绘图(相距不远,但我很乐观地认为我们可以拥有像ggplot一样的东西)。 所有这些都基于l>的当前实现...

我应该说,我全都赞成在桌上以l>保留更好的选择。 到目前为止,我所做的一切都可以正常工作,但是我可以轻松地找到一种更好的方法。 但是仅弃用似乎是一个非常激进的步骤,可以从很多包装中抽出地毯。

另一种选择是使x |> f成为f(x)的替代语法。 那会破坏重载|>代码,但允许使用|>进行函数链接的代码继续工作,同时为其他增强功能保持开放状态,只要它们与之兼容即可。

另一种选择是在将来引入新的语法链接语法,但是它必须是当前存在的语法错误,这在目前是一个很小的选择。

另一种选择是在将来引入新的语法链接语法,但是它必须是当前存在的语法错误,这在目前是一个很小的选择。

我的提议不可以吗? 即在julia 1.0中使|>成为语法错误,并使||>等于今天的|> 。 对于julia 1.0,这对于当前使用|>代码来说是一个小麻烦,因为必须切换到||> 。 但我觉得这样还不错,而且可以完全自动化。 然后,一旦有人对|>有了一个好主意,就可以将其重新引入该语言。 到那时,将同时存在||>|> ,并且我假设如果每个人都开始采用|> ||>会逐渐淡出背景。 然后,在几年内,julia 2.0可以删除||> 。 我认为a)不会对julia 1.0时间表的任何人造成任何真正的麻烦,并且b)将所有选项留在桌上,最终为|>提供一个真正好的解决方案。

|>(x, f) = f(x)
|>(x, tuple::Tuple) = tuple[1](x, tuple[2:endof(tuple)]...) # tuple
|>(x, f, args...) = f(x, args...) # args

x = 1 |> (+, 1, 1) |> (-, 1) |> (*, 2) |> (/, 2) |> (+, 1) |> (*, 2) # tuple
y = 1 |> (+, 1, 1)... |> (-, 1)... |> (*, 2)... |> (/, 2)... |> (+, 1)... |> (*, 2)... # args

写很多遍并不容易,但是从左到右并且不使用宏。

function fibb_tuple(n)
    if n < 3
        return n
    end
    fibb_tuple(n-3) |> (+, fibb_tuple(n-2), fibb_tuple(n-1))
end

function fibb_args(n)
    if n < 3
        return n
    end
    fibb_args(n-3) |> (+, fibb_args(n-2), fibb_args(n-1))...
end

function fibb(n)
    if n < 3
        return n
    end
    fibb(n-3) + fibb(n-2) + fibb(n-1)
end

n = 25

println("fibb_tuple")
<strong i="8">@time</strong> fibb_tuple(1)
println("fibb_args")
<strong i="9">@time</strong> fibb_args(1)
println("fibb")
<strong i="10">@time</strong> fibb(1)

println("tuple")
<strong i="11">@time</strong> fibb_tuple(n)
println("args")
<strong i="12">@time</strong> fibb_args(n)
println("fibb")
<strong i="13">@time</strong> fibb(n)
fibb_tuple
  0.005693 seconds (2.40 k allocations: 135.065 KiB)
fibb_args
  0.003483 seconds (1.06 k allocations: 60.540 KiB)
fibb
  0.002716 seconds (641 allocations: 36.021 KiB)
tuple
  1.331350 seconds (5.41 M allocations: 151.247 MiB, 20.93% gc time)
args
  0.006768 seconds (5 allocations: 176 bytes)
fibb
  0.006165 seconds (5 allocations: 176 bytes)

|>(x, tuple::Tuple) = tuple[1](x, tuple[2:endof(tuple)]...)太糟糕了。
|>(x, f, args...) = f(x, args...)需要更多字母但速度更快。

我认为允许subject |> verb(_, objects)类的语法作为verb(subject, objects)表示支持SVO(但Julia的默认值为VSO或VOS)。 但是,Julia支持mutltidipatch,因此主题可以是主题。 我认为我们应该允许(subject1, subject2) |> verb(_, _, object1, object2)样的语法为verb(subject1, subject2, object1, object2)如果我们引入SVO语法。
@oxinabox指出的那样,如果将其掌握为管线,则为MIMO。

如何将(x)f用作f(x)呢?
(x)f(y)可以同时读为f(x)(y)f(y)(x)因此请先选择评估:

(x)f # f(x)
(x)f(y) # f(y)(x)
(x)f(y)(z) # f(y)(z)(x)
(x)(y)f(z) # f(z)(y)(x)
(a)(b)f(c)(d) # f(c)(d)(b)(a)
1(2(3, 4), 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
1 <| (2 <| (3, 4), 5 <| (6, 7), 8 <| (9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10)), but 2 <| (3, 4) == 2((3, 4)) so currently emit error
3 |> 2(_, 4) |> 1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
((3)2(_, 4))1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
(3, 4) |> 2(_, _) |> 1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
((3, 4)2)1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))

这样可以清楚地操作vararg。
但这会破坏没有空格的二进制运算符:

(a + b)+(c + d) # +(c + d)(a + b) == (c + d)(a + b): Error

替代选项:为splatting语法添加另一种情况。 将f ... [x)糖加到(args ...)-> f(x,args ...)

这将启用语法上轻量级(手动)的curring:

#Basic example:
f(a,b,c,d) = #some definition
f...(a)(b,c,d) == f(a,b,c,d)
f...(a,b)(c,d) == f(a,b,c,d)
f...(a,b,c)(d) == f(a,b,c,d)
f...(a)...(b)(c,d) == f(a,b,c,d) # etc etc

# Use in pipelining:
x |> map...(f) |> g  |> filter...(h) |> sum

您什么时候停止咖喱? Julia函数没有固定的对偶。

朱莉娅已经有一个操作员。 我提议的行为将与当前的splat运算符完全相同。

I,e:f ...(x)==(args ...)-> f(x,args ...)是用于制作带有飞溅的lambda的糖。

该定义始终为您提供一个功能对象。 大概您有时想要一个答案。

您可以通过在末尾显式调用该对象来实现。 例如,请注意在我的上一个示例中,在最后一组括号之前没有... f ...(a)...(b)(c,d)== f(a,b,c,d) 。

您还可以使用|>调用返回的函数对象,这使其非常适合管道传输。

@saolof

基本示例:

f(a,b,c,d)=#一些定义
f ...(a)(b,c,d)== f(a,b,c,d)
f ...(a,b)(c,d)== f(a,b,c,d)
f ...(a,b,c)(d)== f(a,b,c,d)
f ...(a)...(b)(c,d)== f(a,b,c,d)#等

将splat与函数链接结合使用时的直觉很不错,但就我而言,它太复杂了。
您在链的某一点进行了多次申请。
在函数链接中,您将逐步完成一个应用程序。

@StefanKarpinski是正确的,您不知道何时停止在自己身上应用函数,并最终将它们应用到更标量的项目上。

-(夹)-

抱歉,这是毫无意义且难以理解的。
请参阅下面的第二个味精以获得更清晰的解释(我希望)。

考虑到Julia的功能已经很强大,我非常喜欢

您甚至可以根据自己的意愿舒适地制作原型:

ctranspose(f) = (a...) -> (b...) -> f(a..., b...)

map'(+)(1:10)

map'(+)'(1:10, 11:20)(21:30)

(+)'(1,2,3)(4,5)

1:10 |> map'(x->x^2) |> filter'(iseven)

我认为,感觉不错。

编辑:这样的感觉也可能是对此进行更多概括的途径。 如果我们可以写map∘(+, 1:10)那么我们可以写map∘(_, 1:10)来首先放置curried参数,而curry操作者确定lambda的范围,从而解决了这种一般计算的最大问题。

嗯,这很聪明,@ MikeInnes。

我也喜欢朱莉娅(Julia)的极端可扩展性在这里的表现。 事实证明,针对功能链的非常广泛的需求的统一解决方案正在滥用ctranspose ...

(说明:我通过此提案获得了1:10 |> map'(x->x^2) |> filter'(iseven) ,因此,我获得了💯%!)

明确地说,我认为我们不应该为此滥用adjoint运算符,但这很好地证明了我们可以使用简洁的函数表示法。

也许我们应该引入一个新的unicode运算符? http://www.fileformat.info/info/unicode/char/1f35b/index.htm

(抱歉...)

我觉得_仍然是制作Lambda的一种更灵活的方法

@bramtayl我认为MikeInnes编辑他的帖子的想法是两者可以共存—如@stevengj的pull请求中的标准下划线将起作用,如上面Mike的想法中那样独立进行计算,并将两者结合也会起作用,允许您使用currying运算符在其中限制_的范围。

知道了

这与LazyCall.jl并没有太大区别

或此处的提案: https :

更严重的是:

需要明确的是,我不认为我们应该为此滥用adjoint运算符

可能是一个声音选择。 但是,我希望表达我的希望,即如果实施了这样的解决方案,则可以给它一个易于键入的运算符。 如果要替换'任何字符要求我在unicode表中查找(或使用支持LaTeX扩展的编辑器),则执行1:10 |> map'(x->x^2)功能的用处就会大大减少。

与其滥用伴随运算符,不如重用splat运算符。

  • 在(线性)管道环境中
  • 在内部,在函数调用中

    • 做之前而不是之后

所以

  • splat可能会导致缺少迭代器arg

一种高阶摔跤(如果有音乐家在场,则
希望它不会动摇太多语言。

1:10
    |> map(...x->x^2)
    |> filter(...iseven)

实施例2

genpie = (r, a=2pi, n=12) ->
  (0:n-1) |>
      map(...i -> a*i/n) |>
      map(...t -> [r*cos(t), r*sin(t)]) 

可以代表

elmap = f -> (s -> map(f,s))

genpie = (r, a=2pi, n=12) ->
  (0:n-1) |>
      elmap(i -> a*i/n) |>
      elmap(t -> [r*cos(t), r*sin(t)]) 

由于讨论已经发展到更高级/更灵活的链接和语法,因此不确定是否属于这里...但是回到开篇,使用点语法的函数链接现在似乎可以进行一些额外的设置。 语法只是结构的点语法以及一流的函数/闭包的结果。

mutable struct T
    move
    scale
    display
    x
    y
end

function move(x,y)
    t.x=x
    t.y=y
    return t
end
function scale(c)
    t.x*=c
    t.y*=c
    return t
end
function display()
    @printf("(%f,%f)\n",t.x,t.y)
end

function newT(x,y)
    T(move,scale,display,x,y)
end


julia> t=newT(0,0)
T(move, scale, display, 0, 0)

julia> t.move(1,2).scale(3).display()
(3.000000,6.000000)

该语法似乎与常规OOP非常相似,但“类方法”的古怪之处是可变的。 不确定性能影响是什么。

@ivanctong您所描述的更像是一个流畅的接口,而不是函数链接。

也就是说,更一般地解决功能链接的问题将带来更多好处,即也可用于流畅的接口。 虽然目前可以在Julia中使用struct成员制作类似流利的界面的东西当然是可能的,但它却让我非常反感Julia的精神和设计美学。

长生不老药的操作方式是,管道运算符始终在左侧作为第一个参数传递,并在之后允许额外的参数,这非常有用,我很乐意看到像"elixir" |> String.ends_with?("ixir")这样的人成为头等公民在朱莉娅。

其他语言将其定义为“统一函数调用语法”
此功能具有多个优点(请参阅Wikipedia),如果Julia支持,那将是很好的。

那么,此时Julia是否有一个流畅的界面?

请向Julia话语讨论论坛发表问题。

为了适应黑客行为(和可疑的判断!?),我为函数占位符的绑定紧密性创建了另一种可能的解决方案:

https://github.com/c42f/MagicUnderscores.jl

https://github.com/JuliaLang/julia/pull/24990所述,这是基于以下观察结果:人们经常希望给定函数的某些插槽紧密绑定_占位符表达式,并且其他人松散地。 MagicUnderscores使此功能可扩展为任何用户定义的功能(这在广播机器的意义上非常重要)。 因此,我们可以拥有

julia> <strong i="12">@_</strong> [1,2,3,4] |> filter(_>2, _)
2-element Array{Int64,1}:
 3
 4

julia> <strong i="13">@_</strong> [1,2,3,4] |> filter(_>2, _) |> length
2

“只是工作”。 (如果有可能将其作为通用解决方案,则@_显然将消失。)

@MikeInnes的一些变化建议似乎可以满足我的需要(通常使用do语法过滤,映射,缩小,枚举,压缩等较长的链)。

c(f) = (a...) -> (b...) -> f(a..., b...)

1:10 |> c(map)() do x
    x^2
end |> c(filter)() do x
    x > 50
end

尽管我无法再让'正常工作,但此方法有效。 它略短于:

1:10 |> x -> map(x) do x
    x^2
end |> x -> filter(x) do x
    x > 50
end

我也想

cmap = c(map)
cfilter = c(filter)
cetc = c(etc)
...

1:10 |> cmap() do x
    x^2
end |> cfilter() do x
    x > 50
end |> cetc() do ...

从1.0开始,您需要使adjoint超载,而不是ctranspose 。 您也可以这样做:

julia> Base.getindex(f::Function, x...) = (y...) -> f(x..., y...)

julia> 1:10 |> map[x -> x^2] |> filter[x -> x>50]
3-element Array{Int64,1}:
  64
  81
 100

如果我们可以超载apply_type那么我们可以得到map{x -> x^2} :)

@MikeInnes我只是偷了

迟来的和轻率的贡献-如何使用左和右curry运算符的组合将数据传递到参数列表中的任何位置:

VERSION==v"0.6.2"
import Base: ctranspose, transpose  
ctranspose(f::Function) = (a...) -> ((b...) -> f(a..., b...))  
 transpose(f::Function) = (a...) -> ((b...) -> f(b..., a...))

"little" |> (*)'''("Mary ")("had ")("a ") |> (*).'(" lamb")

Clojure有一些不错的线程宏。 我们在朱莉娅生态系统中的某个地方有这些吗?

Clojure有一些不错的线程宏。 我们在朱莉娅生态系统中的某个地方有这些吗?

https://github.com/MikeInnes/Lazy.jl

Clojure有一些不错的线程宏。 我们在朱莉娅生态系统中的某个地方有这些吗?

我们至少有10个。
我在线程中进一步发布了一个列表。
https://github.com/JuliaLang/julia/issues/5571#issuecomment -205754539

您可以编辑列表以使用LightQuery代替我的其他两个软件包吗?

由于|>运算符来自elixir,为什么不从他们创建匿名函数的方法之一中汲取灵感呢?
在elixir中,您可以使用&expr定义新的匿名函数,并使用&n捕获位置参数( &1是第一个参数, &2是第二个参数,等等。)
在elixir中,有很多东西要写,(例如,您需要在圆括号前加一个点来调用匿名函数&(&1 + 1).(10)

但是这就是茱莉亚的样子

&(&1 * 10)        # same as: v -> v * 10
&(&2 + 2*&5)      # same as: (_, x, _, _, y) -> x + 2*y
&map(sqrt, &1)    # same as: v -> map(sqtr, v)

所以我们可以更好地使用|>运算符

1:9 |> &map(&1) do x
  x^2
end |> &filter(&1) do x
  x in 25:50
end

代替

1:9 |> v -> map(v) do x
  x^2
end |> v -> filter(v) do x
  x in 25:50
end

请注意,您可以将第2行和第3行替换为.|> &(&1^2).|> (v -> v^2)

与带有_占位符的命题的主要区别在于,这里可以使用位置参数,并且表达式前面的&使占位符的范围显而易见(对于读者和编译器)。

请注意,我在示例中采用了& ,但是使用?_$或其他方式不会改变案件。

Scala将_用作第一个参数, _用作第二个参数,以此类推,这很简洁,但是很快就会耗尽可以应用它的情况(不能重复或反向)参数的顺序)。 它还没有前缀(上面的建议中& ),它使函数与表达式保持歧义,实际上,这是阻止其使用的另一个问题。 作为一名从业者,您最终会将预期的内联函数包装在多余的括号和大括号中,希望可以被识别。

因此,我想说的是,引入这样的语法时,最高优先级是它是明确的。

但是对于参数的前缀, $在shell脚本世界中具有传统。 使用熟悉的字符总是很好。 如果|>来自Elixir,那么也可能是从Elixir那里获得&的论点,因为用户已经在以这种方式思考。 (假设那里有很多以前的Elixir用户...)

这样的语法可能永远无法捕获的一件事是创建一个接受N个参数但使用少于N个参数的函数。主体中的$1$2$3存在3个参数,但是如果您希望将此参数放置在将被4个参数调用的位置(最后一个将被忽略),则没有一种自然的方式来表达它。 (除了为每个N预定义标识函数并用其中一个包装表达式)。这与将其放在|>之后的动机无关,尽管后者只有一个参数。

我扩展了@MikeInnes重载Colon就像函数是数组一样:

struct LazyCall{F} <: Function
    func::F
    args::Tuple
    kw::Dict
end

Base.getindex(f::Function,args...;kw...) = LazyCall{typeof(f)}(f,args,kw)

function (lf::LazyCall)(vals...; kwvals...)

    # keywords are free
    kw = merge(lf.kw, kwvals)

    # indices of free variables
    x_ = findall(x->isa(x,Colon),lf.args)
    # indices of fixed variables
    x! = setdiff(1:length(lf.args),x_)

    # the calling order is aligned with the empty spots
    xs = vcat(zip(x_,vals)...,zip(x!,lf.args[x!])...)
    args = map(x->x[2],sort(xs;by=x->x[1]))

    # unused vals go to the end
    callit = lf.func(args...,vals[length(x_)+1:end]...; kw...)

    return callit
end

[1,2,3,4,1,1,5]|> replace![ : , 1=>10, 3=>300, count=2]|> filter[>(50)]  # == [300]

log[2](2) == log[:,2](2) == log[2][2]() == log[2,2]()  # == true

它比lambdas或线程宏慢得多,但我认为它超级酷:p

为了提醒人们在这里发表评论,请在https://github.com/JuliaLang/julia/pull/24990上进行相关讨论

另外,我鼓励您尝试https://github.com/c42f/Underscores.jl ,它为占位符提供了函数链接友好的_语法实现。 根据您的示例@jpivarski ,您可能会发现它相当熟悉和舒适。

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

相关问题

StefanKarpinski picture StefanKarpinski  ·  3评论

StefanKarpinski picture StefanKarpinski  ·  3评论

helgee picture helgee  ·  3评论

sbromberger picture sbromberger  ·  3评论

i-apellaniz picture i-apellaniz  ·  3评论