-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Function chaining #5571
Comments
The Is the question about exceptions a separate issue? i think the answer is no, aside from wrapping a function body in try..catch. |
The 1.sum(2) example is trivial (I also prefer sum(1,2)) but it's just to demonstrate that a function isn't owned per se by that type ex. 1 can be passed to a function with the first parameter being a Real, not just to functions that expect the first parameter to be an Int. Edit: I might have misunderstood your comment. Dot functions will be useful when applying certain design patterns such as the builder pattern commonly used for configuration. ex.
The exceptions I was just referring to is due to functions returning non-deterministic results (which is anyway bad practice). An example would be calling a.sum(2).sum(4) where .sum(2) sometimes return a String instead of an Int but .sum(4) expects an Int. I take it the compiler/runtime is already smart enough to evaluate such circumstances - which would be same when nesting the function sum(sum(1, 2), 4) - but the feature request would require extending said functionality to enforce type constraints on dot functions. |
One of the use cases people seem to like is the "fluent interface". It's sometimes nice in OOP APIs when methods return the object, so you can do things like For me I think that this is better expressed as function composition, but the One option to support this sort of thing would be if Another option would be some sort of You could also shift responsibility for supporting this to library designers, where they could define
so when you don't supply an object it does partial function application (by returning a function of 1 argument) which you could then use inside a normal |
Actually, the definition of On Monday, January 27, 2014, Spencer Russell [email protected]
|
ssfrr I like the way you think! I was unaware of the function composition kmsquire I like the idea of extending the current function composition to allow you to specify parameters on the calling function ex. Thanks for the prompt responses :) |
Actually, @ssfrr was correct--it isn't possible to implement this as a simple function. |
What you want are threading macros (ex. http://clojuredocs.org/clojure_core/clojure.core/-%3E). Unfortunate that @-> @->> @-?>> is not viable syntax in Julia. |
Yeah, I was thinking that infix macros would be a way to implement this. I'm not familiar enough with macros to know what the limitations are. |
I think this works for @ssfrr's compose macro: Edit: This might be a little clearer: 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(:(@compose x |> f |> g(1) |> h('a',"B",d |> c(fred |> names))))
:(h(g(f(x),1),'a',"B",c(d,names(fred)))) |
If we're going to have this |
+1. It's especially important when you are using Julia for data analysis, where you commonly have data transformation pipelines. In particular, Pandas in Python is convenient to use because you can write things like df.groupby("something").aggregate(sum).std().reset_index(), which is a nightmare to write with the current |> syntax. |
👍 for this. (I'd already thought in suggesting the use of the |
Another possibility is adding syntactic sugar for currying, like |
Oooh, that would be a really nice way to turn any expression into a function. Possibly something like Clojure's anonymous function literals, where Aesthetically I don't think that syntax fits very well into otherwise very readable julia, but I like the general idea. To use my example above (and switching to @malmaud's tilde instead of %), you could do
which looks pretty nice. This is nice in that it doesn't give the first argument any special treatment. The downside is that used this way we're taking up a symbol. Perhaps this is another place where you could use a macro, so the substitution only happens within the context of the macro. |
We obviously can't do this with map(f(_,a), v) Which one does this mean? map(f(x->x,a), v)
map(x->f(x,a), v)
x->map(f(x,a), v) They're all valid interpretations. I seem to recall that Scala uses the type signatures of functions to determine this, which strikes me as unfortunate since it means that you can't really parse Scala without knowing the types of everything. We don't want to do that (and couldn't even if we wanted to), so there has to be a purely syntactic rule to determine which meaning is intended. |
Right, I see your point on the ambiguity of how far to go out. In Clojure the whole expression is wrapped in In Julia is it idiomatic to use _ as don't-care value? Like To solve that I think we'd need macro with an interpolation-like usage:
but again, I think it's getting pretty noisy at that point, and I don't think that I think this would be a good application of an infix macro. As with #4498, if whatever rule defines functions as infix applied to macros as well, we could have a |
Ya, I like the infix macro idea, although a new operator could just be introduced for this use in lieu of having a whole system for inplace macros. For example, |
Folks, it all really looks ugly: |> ||> etc. In Scala it's probably the worst thing - they have so much operators like ::, :, <<, >> +:: and so on - it just makes any code ugly and not readable for one without a few months of experience in using the language. |
Sorry to hear you don't like the proposals, Anton. It would be helpful if you made an alternative proposal. |
Oh sorry, I am not trying to be unkind. And yes - critics without proposals Unfortunately I am not a scientist constructing languages so I just do not |
I like the phrase "scientist constructing languages" - it sounds much more grandiose than numerical programmers sick of Matlab. I feel that almost every language has a way to chain functions - either by repeated application of I'll reiterate support for Spencer's proposal - |
I also think that is the best proposal here, main problem being that it seems to preclude defining |
Just to note, Analogously, in Julia a library developer can already support chaining with That would seem to cause problems if you want your function to support variable number of args, however, so having an operator that could perform the argument stuffing would be nice. @JeffBezanson, it seems that this operator could be implemented if there was a way to do infix macros. Do you know if there's an ideological issue with that, or is just not implemented? |
Recently, Of course, in a few months, someone will ask for On Thursday, February 6, 2014, Spencer Russell [email protected]
|
right, I definitely wouldn't want this to be a special case. Handling it in your API design is actually not that bad, and even the variable arguments limitation isn't too much of an issue if you have type annotations to disambiguate.
I think this behavior could be handled by a The infix macro idea is attractive to me in the situation where it would be unified with declaring infix functions, which is discussed in #4498. |
Why Julia creators are so much against allowing objects to contain their own methods? Where could I read more about that decision? Which thoughts and theory are behind that decision? |
@meglio a more useful place for general questions is the mailing list or the StackOverflow |
Just chiming in, to me the most intuitive thing is to have some placeholder be replaced by the @as _ begin
3+3
f(_,y)
g(_) * h(_,z)
end would be expanded to: g(f(3+3,y)) * h(f(3+3,y),z) You can think of the expression on the previous line "dropping down" to fill the underscore hole on the next line. I started sketching a tiny something like this last quarter in a bout of finals week procrastination. We could also support a oneliner version using @as _ 3+3 |> f(_,y) |> g(_) * h(_,z) |
@porterjamesj, I like that idea! |
I agree; that is pretty nice, and has an appealing generality.
|
The way elixir does it where the pipe operator always passes in the left-hand side as the first argument and allows extra arguments afterward, has been pretty useful, I would love to see something like |
Other languages define it as Uniform Function Call Syntax. |
So is there a fluent interface to Julia at this point? |
Please post questions to the Julia discourse discussion forum. |
In a fit of hacking (and questionable judgement!?) I've created another possible solution to the tightness of binding of function placeholders: https://github.com/c42f/MagicUnderscores.jl As noted over at #24990, this is based on the observation that one often wants certain slots of a given function to bind an julia> @_ [1,2,3,4] |> filter(_>2, _)
2-element Array{Int64,1}:
3
4
julia> @_ [1,2,3,4] |> filter(_>2, _) |> length
2 "just work". (With the |
Some variation @MikeInnes suggestion would seem adequate for my needs (usually long chains of filter, map, reduce, enumerate, zip etc. using c(f) = (a...) -> (b...) -> f(a..., b...)
1:10 |> c(map)() do x
x^2
end |> c(filter)() do x
x > 50
end This works, although I can't get 1:10 |> x -> map(x) do x
x^2
end |> x -> filter(x) do x
x > 50
end Also I guess one could just do 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 ... |
As of 1.0 you'll need to overload 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 If we could overload |
@MikeInnes I just stole that |
A late and slightly frivolous contribution -- how about piping data to any location in the argument list using a combination of left and right curry operators:
|
Clojure has some nice threading macros. Do we have those in the Julia ecosystem somewhere? |
|
we have at least 10 of them. |
Can you edit the list to have LightQuery instead of the other two packages of mine? |
Since the But here's what it could look like in julia &(&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) So we can use the 1:9 |> &map(&1) do x
x^2
end |> &filter(&1) do x
x in 25:50
end instead of 1:9 |> v -> map(v) do x
x^2
end |> v -> filter(v) do x
x in 25:50
end note you can replace line 2 and 3 by The main difference with the propositions with Note that I have taken |
Scala uses So I'd say that the highest priority when introducing a syntax like this is that it be unambiguous. But as for prefixes for arguments, One thing that a syntax like this can probably never capture is creating a function that takes N arguments but uses fewer than N. The |
I extended the @MikeInnes trick of overloading getindex, using 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 It is much slower than lambdas or threading macros, but I think its super cool :p |
To remind people commenting here, do have a look at relevant discussion at #24990. Also, I'd encourage you to try out https://github.com/c42f/Underscores.jl which gives a function-chaining-friendly implementation of |
This seems to be a morass of ideas, better suited now to discourse (since github search and pagination is awful), or the sub-issues it has spawned (such as #24990) |
Let's say, we have couple of functions named as
For the sake of simplicity, we can assume My question is How can we create a pipeline in Julia to call these 3 functions in order? I tried the code below. But it didn't work. How can we make it work? Thanks in advance for your help and suggestions. |
Would it be possible to allow calling any function on Any so that the value is passed to the function as the first parameter and the parameters passed to the function call on the value is added afterwards?
ex.
Is it possible to indicate in a deterministic way what a function will return in order to avoid run time exceptions?
The text was updated successfully, but these errors were encountered: