I idea is relative simple, most languages lack it, but it's awesome and quite easy to be integrated I guess...
This example is obvious: ((x,y) -> x + y)(1,2)
When calling with only one parameter as ((x,y) -> x + y)(1)
it raises a wrong number of arguments error. When a function is called with too few arguments it should instead return a anonymous function expecting the a missing one and executing it. So ((x,y) -> x +y)(1)
should return (a -> ((x,y) -> x +y)(1,a)) )
(x,y) -> x +y is simply +(x,y). So +(1) would become (a -> +(1,a)). This function increments. Incrementing all fields of an array would be map(+(1),[1,2,3,4]).
Of course it would be possible to simply create an anonymous function that increments, but that it would be cool if that happed automagical. Sometimes this aids a strange but useful programming style.
# could become a library function.
function >>=(a,b)
if a
b(a)
end
end
function reportError(category::String, message::String)
# write errors to different files, maybe send emails.
end
# so some stuff that might fail
data=f(data)
>>=(checkData(data),reportError("serve"))
That a way more beautiful than
>>=(checkData(data),(message -> reportError("serve", message)))
as a consequence (if f is a regular function expecting 3 parameters) f(1,2,3)
would be as valid as f(1,2)(3)
or f(1)(2)(3)
This would conflict with multiple dispatch:
f(x, y) = x + y
f(x) = sin(x)
f(pi) # this must be sin(pi), not (y) -> (pi + y)
You might be able to do this sort of partial evaluation with a macro. That macro may already exist.
I think the macro may look something like this:
macro defcurried(name, args, body)
:($name = $reduce((body, arg) -> :($arg -> $body),
:($body),
reverse($args))))
end
function uncurry(fn, args)
reduce((acc, a) -> acc(a), fn, args)
end
@defcurried plus, [x,y], x+y
uncurry(plus, [1, 2])
But I don't think that reverse($args) works...
My suggestion was to return this function just if otherwise there would be a wrong number of arguments error.
If
f(x, y) = x + y
f(x) = sin(x)
f(x,y,z,a,b,c)=sqrt((x-a)^2+(y-b)^2+(z-c)^2)
Then:
f(1) = sin(1)
f(2,3) = 2+3
f(1,2,3) = (a -> f(1,2,3,a))
f(1,2,3)(4)= f(1,2,3,4) = (a -> f(1,2,3,4,a))
and so on.
I'm not sure if that's clever, but i see no conflict with multiple dispatch.
arrgh... This $%@! markdown ...
This definitely isn't going to happen. Even if this could somehow be made to work with multiple dispatch (and I really don't see how it could), it would be incredibly confusing. In a language like Julia, when I call a function with the wrong number of arguments, I want to know that I called a function with the wrong number of arguments. I don't want a function object where I expected a plain old result. This would also immensely complicate method dispatch, which is not acceptable from the implementation end. If you want to curry f(x,y)
, just write y->f(x,y)
and it's very clear what's going on; it also doesn't privilege the arguments based on their ordering 鈥斅爕ou can just as easily write x->f(x,y)
.
For the markdown, you need to leave a blank line before and after your code blocks even when they're in triple backticks.
Yes, no disrespect to currying, but this isn't julia's style.
On the other hand, Julia could adopt an explicit currying mechanism as in Pyret, Scala?, etc. This would circumvent issues with multiple dispatch and still provide an elegant currying experience. For example, if
f(x, y) = ...
f(x) = ...
then f(_, y)
would simply desugar to a -> f(a, y)
and f(x, _)
would desugar to a -> f(x, a)
. Personally, I prefer this currying notation to implicit partial application stuff, since this notation makes everything explicitly clear. Furthermore, there is no ambiguity here as to how dispatch should work.
Currying is extremely helpful when done properly. I think Julia could benefit significantly from it.
The issue with using _
like that is the question of how much of the surrounding expression it consumes. For example, does f(_+2)
mean x->f(x+2)
or f(x->(x+2))
or f((x->x)+2)
? Similarly, why doesn't f(_, y)
mean a->f(a, y)
instead of f(a->a, y)
?
@StefanKarpinski, isn't that just a matter of choosing a convention?
Also, just curious: what would be the benefit of having f(_, y)
map to f(a->a, y)
?
From all the other usages I've seen using this type of currying, I'd say definitely f(x->(x+2))
, so that it can be used like map(2*_, [1,2,3,4])
or filter(_>3, [1,2,3,4,5,6])
. If you think that infix operations may cause confusion* then you can always encourage users to keep it obvious by writing them like
f(+(x,_))
.
I think this is a nice and handy feature to have. It doesn't really scale to complex expressions but I think the point of the underscore is to keep simple things simple and avoid boiler-plate looking anonymous functions where the argument+arrow takes 50%+ of the definition. The do-blocks already solve the multiline anonymous function problem, so it would be a nice complement.
Edit: Also, it would make |>
expressions a lot better looking:
data |> x->map(f, x) |> x->reduce(g, x) |> println
vs
data |> map(f, _) |> reduce(g, _) |> println
Edit2: Just realized my previous pipelining example wasn't very good because you don't need to wrap one argument functions with anonymous ones. Way to make a fool of yourself! haha
*The most confusing example I can think of would be something like _+_+_
where it translates to
x,y,z -> +(x,y,z)
vs _+_-_
where it would translate to z->x,y -> -(+(x,y),z)
. (assuming it would work like Scala's)
Right, I think the standard convention is to simply capture the most immediate parent expression. So, f(2 * _)
desugars to f(x -> 2 * x)
, f(g(h(a, _)))
desugars to f(g(x -> h(a, x)))
, etc. More precisely, the rule would simply identify function application expressions, check if any of the arguments are _
, and then replace the function application expression with a lambda expression.
As @fcard points out, using multiple _
s can be trickier. Scala manages it somehow but I don't think we would lose all that much by only supporting single parameter currying for now. Even just currying one parameter can clean up a lot of code.
I think it should be
v
map(_, _) |> sum(_) |> println(_)
^
f
We could lead the ASCII-art renaissance!
Both Scala's and Pyret's way of dealing with multiple _
s is to, for an function call with n
underscores, make an anonymous function with n
arguments in the order they appear in the function call.
I like it for some situations (like for reduce), but yes, we could live without it.
@timholy What we need is a digital-circuits-style mini-language:
data
|
--------------v
map------->(_,_,_)--------v
f-------------^ (_,_)-----------v
sum---------------------^ (_,_)-------------->EXIT
println------------------------------^
But yeah. Are you saying that because you find that strange looking? I mean, I do too, I just like it better than the alternative :P We could always combine it with the 'pipelining' style proposed elsewhere, so that the _
becomes optional and is only used to position arguments:
data |> map(f) |> sum |> g(x,_,z) |> println
That could be implemented by simply adding the _
syntax and then having it expand before |>
so that a |> f(x,_,z)
becomes a |> y->(f(x,_,z))
becomes (y->(f(x,y,z)))(a)
.
That's pretty awe-inspiring. Maybe we should allow one to write julia code using SVG!
More seriously, yes, I personally find even basic pipelining somewhat dubious, but that's probably just because I've never written code in a language which supports it (other than bash, which is not exactly a great model for convincing me that it adds elegance :smile:). I guess I see why
data |> map(f, _) |> sum |> g(x,_,z) |> println
is slightly easier to read than
println(g(x, sum(map(f, data)), z))
but one can write the latter as
println(g(x,
sum(map(f, data)),
z))
in which case I think the advantage disappears. The number of keystrokes is not dramatically different either way.
Anyway, while there's a part of me that's genuinely reluctant to embrace having too many ways of doing things, I don't care deeply about this issue, I was just having fun with it.
I am not super-fond of pipelining in Julia either, my argument was more like, "hey, given that we already decided to have pipelining, why not have this feature that makes it better?". I find pipelining more useful in other languages that already have a lot of nesting so that you can use it to differentiate 'statement-like' series of transformations. I think that this is a discussion for #5571, though.
Also, its good to have fun, and you did bring up a good point, so its all good in my book. :)
And related, _
does add complexity to the language and encourages a style that is not as prevalent in Julia as in other languages that employ it. I still like and several times wished it was there, but I guess this is the biggest argument against it.
@fcard You have seen Pipe.jl
It does something fairly similar to what you are talking about with _
I'm very keen on pipelines myself. (Though there are definately times where it hinders vs enhances readability)
@oxinabox Yes, Pipe.jl solves the case for pipelines, but it's not a complete implementation of _
, and using macros for the other cases e.g. map(@pipe(2*_), [1,2,3])
, makes it even more verbose than the anonymous function form, so I don't find much use in having a package for this. It seems like a nice package though, I think I will use it for any pipelining needs I may have.
Edit: I particularly liked your specialized use of _
, it seems more useful than the one we would get naturally by having a general _
.
It seems I touched a sensitive subject with the pipeline thing. It's one of the lesser uses I was thinking of for _
, I just thought it was a cool bonus :P
Ah, and for anyone interested in the style of currying proposed originally, due to the call
overloading in 0.4
, it's possible to have a nice syntax for it. A simple (but not the most efficient)* implementation: https://gist.github.com/fcard/a37683ab25561953138a
*Updated with much faster version. I will probably reorganize it into a package or somethin', with maybe some other curry-based functionalities.
As far as I can tell, Pipe.jl is basically just a simple form of currying hacked together. Pipe.jl definitely doesn't fill this hole. If anything, I think its existence is a sign that incorporating currying into Julia _should_ be seriously considered, since people are going through all the trouble of hacking some likeness of it together, even in such a restricted form.
No currying is different from what Pipe.jl is trying to achieve.
Pipe.jl is about redirecting where piped arguments go.
Currying is the replacement of a function that takes multiple arguments, with a function (functor?) that takes 1 argument, and returns a function which takes the next argument etc.
If one wanted to hack in currying, they would be hacking in partial application macros. They wouldn't be thinking about pipe redirection at all.
What you are calling "Explict Currying" is kind of similar, but it creates functions.
Pipe.jl, like the basic pipe operator just rearranged argument position.
I've not used a language with this "explicit currying", though I used F# a lot, which has normal currying -- that is to say all multiple argument functions were curried and so could be partially applied.
I think this normal implicit currying is antithetical (on a philosphical basis at least) to multiple dispatch.
I'm fairly certain what you want right now can be implemented in macros already.
And as such, would be better off in a package (like Pipe.jl) than in the main language.
Its good to keep stuff out of the main language.
(I was stuck awake last night thinking how fcard's joke could actually be implemented in macros.
@oxinabox
Explicit _
currying _can_ be implemented as macros (as I did with 'implicit', and as I probably will do with this one as well if this flops). I just think that it would be much more useful as a language feature.
Like, you _could_ implement infix operations with macros, but who would want to write @infix x + y
every time? Anonymous functions themselves could have been macros, for/while loops, short function definition syntax, pairs, collections, the list goes on.
Some functionalities are indeed just as useful when implemented as macros, others not so much. I would certainly not use _
currying very often if it was one.
I think this normal implicit currying is antithetical (on a philosphical basis at least) to multiple dispatch.
That's true of anonymous functions in general, I suppose. But I don't think it matters that much. This, like anonymous functions are, would be just a convenience feature that doesn't interfere with the usage of multiple dispatch (unlike implicit currying, as discussed above).
Misread that as "explicit currying is antithetical..." :P
I was stuck awake last night thinking how fcard's joke could actually be implemented in macros.
Last year I actually implemented something similar, but with trees instead of circuits. Experiments like these are always fun!
@samuela
Its true that piping can benefit from currying, but they are separate. I personally find Pipe.jl to be an advanced version of piping, rather than a limited version of currying.
Hopefully I am not sounding pushy about this. I really like this type of currying partial application* but I leave entirely to the developers the decision of whether this is useful enough, and fits Julia's style enough, to warrant it being a feature. A very breaking feature at that.
*Got bothered by a functional programmer :P
I'm aware that currying and piping are two very different issues. More precisely, what I meant to convey is that Pipe.jl exists primarily because currying is not provided by Julia. If it were, the usual piping functionality would suffice. In other words, the essence of Pipe.jl is to introduce a currying-like mechanism for the sole use in piping expressions. (Feel free to correct me here if my understanding is still not correct!)
I agree with @fcard here that macros won't really do. Writing @curry f(_, x)
everywhere is a real pain.
I think the real question for this thread should be: is this a conversation worth having now?
Aren't the function type system changes required to make higher-order functions fast vastly more important? Aren't the array changes for 0.5 vastly more important? The question shouldn't be: is this feature worth having? The question should be: is this the specific change that someone should be working on implementing today to achieve the greatest marginal change in Julia's quality? If not, can't we have this conversation in the future, when we actually know more about how Julia's functions will interact with the type system?
@johnmyleswhite That's a good point. I never really thought of this as a priority issue, though, I just wanted to discuss the merits of the feature for later reference, seeing that some interest was sparked. It was certainly not my intention to diverge attention to this, specially so close to the feature freeze. This being a closed issue, I didn't think I was doing that, but if this is the case, I apologize.
(Also, this being just simple syntactic sugar, I don't think that such deep issues such as Julia's type system are involved in this. But I guess that is not very important.)
@samuela How about we continue this conversation somewhere else? :P
@samuela After thinking about it for a bit (or after someone yelling PARTIAL APPLICATION in my ear), this is probably an issue separated from currying anyway. The best thing to do I think would be to come up with a bunch of use cases, a list of merits vs cons, etc, and maybe some time later post an issue here/start a thread in julia-dev about a possible syntax construct for partial application (PARTIA- ahem alright). If you want to discuss, feel free to email me. (we could probably do a few test implementations as well)
@fcard Sounds like a plan! If I get some free time I may take a stab at implementing partial application. I'm not familiar with Julia's internals so it may be quite an adventure.
This isn't as elegant as the original proposal, but you can do partials with something like this, right?
partial = (f, a...) -> (b...) -> f(a..., b...)
(So kind of like python's functools.partial.)
This should allow for, e.g:
adder = (x, y, z) -> x + y + z
partial(adder, 3)(2, 1)
Most helpful comment
I think it should be
We could lead the ASCII-art renaissance!