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

Alternative syntax for `map(func, x)` #8450

Closed
kmsquire opened this Issue Sep 23, 2014 · 283 comments

Comments

Projects
None yet
@kmsquire
Member

kmsquire commented Sep 23, 2014

This was discussed in some detail here. I was having trouble finding it, and thought it deserved its own issue.

@quinnj

This comment has been minimized.

Show comment
Hide comment
@quinnj

quinnj Sep 23, 2014

Member

+1

Member

quinnj commented Sep 23, 2014

+1

@toivoh

This comment has been minimized.

Show comment
Hide comment
@toivoh

toivoh Sep 23, 2014

Member

Or func.(args...) as syntactic sugar for

broadcast(func, args...)

But maybe I'm the only one who would prefer that?
Either way, +1.

Member

toivoh commented Sep 23, 2014

Or func.(args...) as syntactic sugar for

broadcast(func, args...)

But maybe I'm the only one who would prefer that?
Either way, +1.

@ihnorton

This comment has been minimized.

Show comment
Hide comment
@ihnorton

ihnorton Sep 23, 2014

Member

👎 If anything, I think Stefan's other suggestion of f[...] has a nice similarity to comprehensions.

Member

ihnorton commented Sep 23, 2014

👎 If anything, I think Stefan's other suggestion of f[...] has a nice similarity to comprehensions.

@johnmyleswhite

This comment has been minimized.

Show comment
Hide comment
@johnmyleswhite

johnmyleswhite Sep 23, 2014

Member

Like @ihnorton, I'm also not super fond of this idea. In particular, I dislike the asymmetry of having both a .+ b and sin.(a).

Member

johnmyleswhite commented Sep 23, 2014

Like @ihnorton, I'm also not super fond of this idea. In particular, I dislike the asymmetry of having both a .+ b and sin.(a).

@quinnj

This comment has been minimized.

Show comment
Hide comment
@quinnj

quinnj Sep 23, 2014

Member

Maybe we don't need special syntax. With #1470, we could do something like

call(f::Callable,x::AbstractArray) = applicable(f,x) ? apply(f,x) : map(f,x)

right? Perhaps this would be too magical though, to get auto-map on any function.

Member

quinnj commented Sep 23, 2014

Maybe we don't need special syntax. With #1470, we could do something like

call(f::Callable,x::AbstractArray) = applicable(f,x) ? apply(f,x) : map(f,x)

right? Perhaps this would be too magical though, to get auto-map on any function.

@JeffBezanson

This comment has been minimized.

Show comment
Hide comment
@JeffBezanson

JeffBezanson Sep 23, 2014

Member

@quinnj That one line summarizes my greatest fears about allowing call overloading. I won't be able to sleep for days.

Member

JeffBezanson commented Sep 23, 2014

@quinnj That one line summarizes my greatest fears about allowing call overloading. I won't be able to sleep for days.

@JeffBezanson

This comment has been minimized.

Show comment
Hide comment
@JeffBezanson

JeffBezanson Sep 23, 2014

Member

Not yet sure it's syntactically possible, but what about .sin(x)? Is that more similar to a .+ b?

I think [] is getting way too overloaded and will not work for this purpose. For example, we'll probably be able to write Int(x), but Int[x] constructs an array and so cannot mean map.

Member

JeffBezanson commented Sep 23, 2014

Not yet sure it's syntactically possible, but what about .sin(x)? Is that more similar to a .+ b?

I think [] is getting way too overloaded and will not work for this purpose. For example, we'll probably be able to write Int(x), but Int[x] constructs an array and so cannot mean map.

@johnmyleswhite

This comment has been minimized.

Show comment
Hide comment
@johnmyleswhite

johnmyleswhite Sep 23, 2014

Member

I would be onboard with .sin(x).

Member

johnmyleswhite commented Sep 23, 2014

I would be onboard with .sin(x).

@StefanKarpinski

This comment has been minimized.

Show comment
Hide comment
@StefanKarpinski

StefanKarpinski Sep 23, 2014

Member

We'd have to claw back some syntax for that, but if Int(x) is the scalar version then Int[x] is reasonable by analogy to construct a vector of element type Int. In my mind the opportunity to make that syntax more coherent is actually one of the most appealing aspects of the f[v] proposal.

Member

StefanKarpinski commented Sep 23, 2014

We'd have to claw back some syntax for that, but if Int(x) is the scalar version then Int[x] is reasonable by analogy to construct a vector of element type Int. In my mind the opportunity to make that syntax more coherent is actually one of the most appealing aspects of the f[v] proposal.

@JeffBezanson

This comment has been minimized.

Show comment
Hide comment
@JeffBezanson

JeffBezanson Sep 23, 2014

Member

How does making f[v] syntax for map make the syntax more coherent? I don't understand. map has a different "shape" than the current T[...] array constructor syntax. What about Vector{Int}[...]? Wouldn't that not work?

Member

JeffBezanson commented Sep 23, 2014

How does making f[v] syntax for map make the syntax more coherent? I don't understand. map has a different "shape" than the current T[...] array constructor syntax. What about Vector{Int}[...]? Wouldn't that not work?

@quinnj

This comment has been minimized.

Show comment
Hide comment
@quinnj

quinnj Sep 23, 2014

Member

lol, sorry for the scare @JeffBezanson! Haha, the call overloading is definitely a little scary, every once in a while, I think about the kinds of code obfuscation you can do in julia and with call, you could do some gnarly stuff.

I think .sin(x) sounds like a good idea too. Was there consensus on what to do with multi-args?

Member

quinnj commented Sep 23, 2014

lol, sorry for the scare @JeffBezanson! Haha, the call overloading is definitely a little scary, every once in a while, I think about the kinds of code obfuscation you can do in julia and with call, you could do some gnarly stuff.

I think .sin(x) sounds like a good idea too. Was there consensus on what to do with multi-args?

@jakebolewski

This comment has been minimized.

Show comment
Hide comment
@jakebolewski

jakebolewski Sep 23, 2014

Member

👎. Saving a couple of characters compared to using higher order functions I don't think is worth the cost in readability. Can you imagine a file with .func() / func.() and func() interspersed everywhere?

Member

jakebolewski commented Sep 23, 2014

👎. Saving a couple of characters compared to using higher order functions I don't think is worth the cost in readability. Can you imagine a file with .func() / func.() and func() interspersed everywhere?

@JeffBezanson

This comment has been minimized.

Show comment
Hide comment
@JeffBezanson

JeffBezanson Sep 23, 2014

Member

It seems likely we'll remove the a.(b) syntax anyway, at least.

Member

JeffBezanson commented Sep 23, 2014

It seems likely we'll remove the a.(b) syntax anyway, at least.

@kmsquire kmsquire changed the title from Use `func.(x)` as syntactic sugar for `map(func, x)` to Alternative syntax for `map(func, x)` Sep 23, 2014

@kmsquire

This comment has been minimized.

Show comment
Hide comment
@kmsquire

kmsquire Sep 23, 2014

Member

Wow, talk about stirring up a bees nest! I changed the name to better reflect the discussion.

Member

kmsquire commented Sep 23, 2014

Wow, talk about stirring up a bees nest! I changed the name to better reflect the discussion.

@JeffBezanson

This comment has been minimized.

Show comment
Hide comment
@JeffBezanson

JeffBezanson Sep 23, 2014

Member

We could also rename 2-argument map to zipWith :)

Member

JeffBezanson commented Sep 23, 2014

We could also rename 2-argument map to zipWith :)

@ihnorton

This comment has been minimized.

Show comment
Hide comment
@ihnorton

ihnorton Sep 23, 2014

Member

If some syntax is really necessary, how about [f <- b] or another pun on comprehensions inside the brackets?

(@JeffBezanson you're just afraid that someone is going to write CJOS or Moose.jl :) ... if we get that feature, just put it in the Don't do stupid stuff: I won't optimize that section of the manual)

Member

ihnorton commented Sep 23, 2014

If some syntax is really necessary, how about [f <- b] or another pun on comprehensions inside the brackets?

(@JeffBezanson you're just afraid that someone is going to write CJOS or Moose.jl :) ... if we get that feature, just put it in the Don't do stupid stuff: I won't optimize that section of the manual)

@StefanKarpinski

This comment has been minimized.

Show comment
Hide comment
@StefanKarpinski

StefanKarpinski Sep 23, 2014

Member

Currently writing Int[...] indicates that you are constructing an array of element type Int. But if Int(x) means converting x to Int by applying Int as a function then you could also consider Int[...] to mean "apply Int to each thing in ...", oh which by the way happens to produce values of type Int. So writing Int[v] would be equivalent to [ Int(x) for x in v ] and Int[ f(x) for x in v ] would be equivalent to [ Int(f(x)) for x in v ]. Of course, then you've lost some of the utility of writing Int[ f(x) for x in v ] in the first place – i.e. that we can statically know that the element type is Int – but if enforce that Int(x) must produce a value of type Int (not an unreasonable constraint), then we could recover that property.

Member

StefanKarpinski commented Sep 23, 2014

Currently writing Int[...] indicates that you are constructing an array of element type Int. But if Int(x) means converting x to Int by applying Int as a function then you could also consider Int[...] to mean "apply Int to each thing in ...", oh which by the way happens to produce values of type Int. So writing Int[v] would be equivalent to [ Int(x) for x in v ] and Int[ f(x) for x in v ] would be equivalent to [ Int(f(x)) for x in v ]. Of course, then you've lost some of the utility of writing Int[ f(x) for x in v ] in the first place – i.e. that we can statically know that the element type is Int – but if enforce that Int(x) must produce a value of type Int (not an unreasonable constraint), then we could recover that property.

@JeffBezanson

This comment has been minimized.

Show comment
Hide comment
@JeffBezanson

JeffBezanson Sep 23, 2014

Member

Strikes me as more vectorization/implicit-cat hell. What would Int[x, y] do? Or worse, Vector{Int}[x]?

Member

JeffBezanson commented Sep 23, 2014

Strikes me as more vectorization/implicit-cat hell. What would Int[x, y] do? Or worse, Vector{Int}[x]?

@StefanKarpinski

This comment has been minimized.

Show comment
Hide comment
@StefanKarpinski

StefanKarpinski Sep 23, 2014

Member

I'm not saying it's the best idea ever or even advocating for it – I'm just pointing out that it doesn't completely clash with the existing usage, which is itself a bit of a hack. If we could make the existing usage part of a more coherent pattern, that would be a win. I'm not sure what f[v,w] would mean – the obvious choices are [ f(x,y) for x in v, y in w ] or map(f,v,w) but there are still more choices.

Member

StefanKarpinski commented Sep 23, 2014

I'm not saying it's the best idea ever or even advocating for it – I'm just pointing out that it doesn't completely clash with the existing usage, which is itself a bit of a hack. If we could make the existing usage part of a more coherent pattern, that would be a win. I'm not sure what f[v,w] would mean – the obvious choices are [ f(x,y) for x in v, y in w ] or map(f,v,w) but there are still more choices.

@jakebolewski

This comment has been minimized.

Show comment
Hide comment
@jakebolewski

jakebolewski Sep 23, 2014

Member

I feel that a.(b) is hardly used. Ran a quick test and it is used in only in 54 of the ~4,000 julia source files in the wild: https://gist.github.com/jakebolewski/104458397f2e97a3d57d.

Member

jakebolewski commented Sep 23, 2014

I feel that a.(b) is hardly used. Ran a quick test and it is used in only in 54 of the ~4,000 julia source files in the wild: https://gist.github.com/jakebolewski/104458397f2e97a3d57d.

@JeffBezanson

This comment has been minimized.

Show comment
Hide comment
@JeffBezanson

JeffBezanson Sep 23, 2014

Member

I think it does completely clash. T[x] has the "shape" T --> Array{T}, while map has the shape Array{T} --> Array{S}. Those are pretty much incompatible.

To do this I think we'd have to give up T[x,y,z] as a constructor for Vector{T}. Plain old array indexing, A[I] where I is a vector, can be seen as map(i->A[i], I). "Applying" an array is like applying a function (of course matlab even uses the same syntax for them). In that sense the syntax really works, but we would lose typed-vector syntax in the process.

Member

JeffBezanson commented Sep 23, 2014

I think it does completely clash. T[x] has the "shape" T --> Array{T}, while map has the shape Array{T} --> Array{S}. Those are pretty much incompatible.

To do this I think we'd have to give up T[x,y,z] as a constructor for Vector{T}. Plain old array indexing, A[I] where I is a vector, can be seen as map(i->A[i], I). "Applying" an array is like applying a function (of course matlab even uses the same syntax for them). In that sense the syntax really works, but we would lose typed-vector syntax in the process.

@johnmyleswhite

This comment has been minimized.

Show comment
Hide comment
@johnmyleswhite

johnmyleswhite Sep 23, 2014

Member

I kind of feel like debating syntax here distracts from the more important change: making map fast.

Member

johnmyleswhite commented Sep 23, 2014

I kind of feel like debating syntax here distracts from the more important change: making map fast.

@JeffBezanson

This comment has been minimized.

Show comment
Hide comment
@JeffBezanson

JeffBezanson Sep 23, 2014

Member

Obviously making map fast (which, by the way, needs to be part of a fairly thorough redesign of the notion of functions in julia) is more important. However going from sin(x) to map(sin, x) is very significant from a usability perspective, so to really kill vectorization the syntax is quite important.

Member

JeffBezanson commented Sep 23, 2014

Obviously making map fast (which, by the way, needs to be part of a fairly thorough redesign of the notion of functions in julia) is more important. However going from sin(x) to map(sin, x) is very significant from a usability perspective, so to really kill vectorization the syntax is quite important.

@nalimilan

This comment has been minimized.

Show comment
Hide comment
@nalimilan

nalimilan Sep 23, 2014

Contributor

However going from sin(x) to map(sin, x) is very significant from a usability perspective, so to really kill vectorization the syntax is quite important.

Fully agreed.

Contributor

nalimilan commented Sep 23, 2014

However going from sin(x) to map(sin, x) is very significant from a usability perspective, so to really kill vectorization the syntax is quite important.

Fully agreed.

@toivoh

This comment has been minimized.

Show comment
Hide comment
@toivoh

toivoh Sep 23, 2014

Member

I agree with @JeffBezanson that f[x] is pretty much irreconcilable with the current typed array constructions Int[x, y] etc.

Member

toivoh commented Sep 23, 2014

I agree with @JeffBezanson that f[x] is pretty much irreconcilable with the current typed array constructions Int[x, y] etc.

@toivoh

This comment has been minimized.

Show comment
Hide comment
@toivoh

toivoh Sep 23, 2014

Member

Another reason to prefer .sin over sin. is to finally allow to use e.g. Base.(+) to access the + function in Base (once a.(b) is removed).

Member

toivoh commented Sep 23, 2014

Another reason to prefer .sin over sin. is to finally allow to use e.g. Base.(+) to access the + function in Base (once a.(b) is removed).

@kmsquire

This comment has been minimized.

Show comment
Hide comment
@kmsquire

kmsquire Sep 23, 2014

Member

When a module defines its own sin (or whatever) function and we want to use that function on a vector, do we do Module..sin(v)? Module.(.sin(v))? Module.(.sin)(v)? .Module.sin(v)?

Member

kmsquire commented Sep 23, 2014

When a module defines its own sin (or whatever) function and we want to use that function on a vector, do we do Module..sin(v)? Module.(.sin(v))? Module.(.sin)(v)? .Module.sin(v)?

@StefanKarpinski

This comment has been minimized.

Show comment
Hide comment
@StefanKarpinski

StefanKarpinski Sep 23, 2014

Member

None of these options really seem good anymore.

Member

StefanKarpinski commented Sep 23, 2014

None of these options really seem good anymore.

@binarybana

This comment has been minimized.

Show comment
Hide comment
@binarybana

binarybana Sep 23, 2014

Contributor

I feel like this discussion misses the meat of the problem. Ie: when mapping single argument functions to containers, I feel like the map(func, container) syntax is already clear and succinct. Instead, it is only when dealing with multiple arguments that I feel we might could benefit from better syntax for currying.

Take for example the verbosity of map(x->func(x,other,args), container), or chain a filter operation to make it worse filter(x->func2(x[1]) == val, map(x->func1(x,other,args), container)).

In these cases, I feel a shortened map syntax would not help much. Not that I think these are particularly bad, but a) I don't think a short-hand map would help much and b) I love pining after some of Haskell's syntax. ;)

IIRC, in Haskell the above can be written filter ((==val) . func2 . fst) $ map (func1 other args) container with a slight change in the order of arguments to func1.

Contributor

binarybana commented Sep 23, 2014

I feel like this discussion misses the meat of the problem. Ie: when mapping single argument functions to containers, I feel like the map(func, container) syntax is already clear and succinct. Instead, it is only when dealing with multiple arguments that I feel we might could benefit from better syntax for currying.

Take for example the verbosity of map(x->func(x,other,args), container), or chain a filter operation to make it worse filter(x->func2(x[1]) == val, map(x->func1(x,other,args), container)).

In these cases, I feel a shortened map syntax would not help much. Not that I think these are particularly bad, but a) I don't think a short-hand map would help much and b) I love pining after some of Haskell's syntax. ;)

IIRC, in Haskell the above can be written filter ((==val) . func2 . fst) $ map (func1 other args) container with a slight change in the order of arguments to func1.

@rfourquet

This comment has been minimized.

Show comment
Hide comment
@rfourquet

rfourquet Sep 24, 2014

Contributor

In elm .func is defined by x->x.func and this is very useful, see elm records. This should be considered before taking this syntax for map.

Contributor

rfourquet commented Sep 24, 2014

In elm .func is defined by x->x.func and this is very useful, see elm records. This should be considered before taking this syntax for map.

@StefanKarpinski

This comment has been minimized.

Show comment
Hide comment
@StefanKarpinski

StefanKarpinski Sep 24, 2014

Member

I like that.

Member

StefanKarpinski commented Sep 24, 2014

I like that.

@StefanKarpinski

This comment has been minimized.

Show comment
Hide comment
@StefanKarpinski

StefanKarpinski Sep 24, 2014

Member

Although field access isn't such a big deal in Julia as in many languages.

Member

StefanKarpinski commented Sep 24, 2014

Although field access isn't such a big deal in Julia as in many languages.

@rfourquet

This comment has been minimized.

Show comment
Hide comment
@rfourquet

rfourquet Sep 24, 2014

Contributor

Yes it feels less relevant here as fields in Julia are kind of more for "private" use. But with the ongoing discussion about overloading field access, that may become more sensible.

Contributor

rfourquet commented Sep 24, 2014

Yes it feels less relevant here as fields in Julia are kind of more for "private" use. But with the ongoing discussion about overloading field access, that may become more sensible.

@nalimilan

This comment has been minimized.

Show comment
Hide comment
@nalimilan

nalimilan Sep 24, 2014

Contributor

f.(x) looks like the less problematic solution, if it wasn't for the asymmetry with .+. But keeping the symbolic association of . to "element-wise operation" is a good idea IMHO.

Contributor

nalimilan commented Sep 24, 2014

f.(x) looks like the less problematic solution, if it wasn't for the asymmetry with .+. But keeping the symbolic association of . to "element-wise operation" is a good idea IMHO.

@rfourquet

This comment has been minimized.

Show comment
Hide comment
@rfourquet

rfourquet Sep 24, 2014

Contributor

If current typed array construction can be deprecated, then func[v...] can be translated to map(func, v...), and the litteral arrays can then be written T[[a1, ..., an]] (instead of current T[a1, ..., an]).

Contributor

rfourquet commented Sep 24, 2014

If current typed array construction can be deprecated, then func[v...] can be translated to map(func, v...), and the litteral arrays can then be written T[[a1, ..., an]] (instead of current T[a1, ..., an]).

@rfourquet

This comment has been minimized.

Show comment
Hide comment
@rfourquet

rfourquet Sep 24, 2014

Contributor

I find sin∘vquiet natural also (when an array v is seen a an application from indexes to contained values), or more simply sin*v or v*[sin]' (which requires defining *(x, f::Callable)) etc.

Contributor

rfourquet commented Sep 24, 2014

I find sin∘vquiet natural also (when an array v is seen a an application from indexes to contained values), or more simply sin*v or v*[sin]' (which requires defining *(x, f::Callable)) etc.

@tkelman

This comment has been minimized.

Show comment
Hide comment
@tkelman

tkelman May 7, 2016

Contributor

I think Viral missed that part of the call. My impression is that multiple people still have reservations about the aesthetics of f.(x) and might prefer either

  1. an infix operator which would be simpler conceptually and in implementation, but we don't have any ascii ones available from what I can see. My earlier idea of deprecating the macro parsing of ~ would require work to replace in packages and it's probably too late to try to do that in this cycle.
  2. or an alternative new syntax that makes it easier to fuse loops and eliminate temporaries. No other alternatives have been implemented to anywhere near the level of #15032, so it's looking like we should merge that and try it out despite remaining reservations.
Contributor

tkelman commented May 7, 2016

I think Viral missed that part of the call. My impression is that multiple people still have reservations about the aesthetics of f.(x) and might prefer either

  1. an infix operator which would be simpler conceptually and in implementation, but we don't have any ascii ones available from what I can see. My earlier idea of deprecating the macro parsing of ~ would require work to replace in packages and it's probably too late to try to do that in this cycle.
  2. or an alternative new syntax that makes it easier to fuse loops and eliminate temporaries. No other alternatives have been implemented to anywhere near the level of #15032, so it's looking like we should merge that and try it out despite remaining reservations.
@JeffBezanson

This comment has been minimized.

Show comment
Hide comment
@JeffBezanson

JeffBezanson May 7, 2016

Member

Yes, I do have some reservations but I can't see a better option than f.(x) right now. It seems better than picking an arbitrary symbol like ~, and I bet many who are used to .* (etc.) could even guess right away what it means.

One thing I would like to get a better sense of is whether people are ok with replacing existing vectorized definitions with .(. If people don't like it enough to do the replacement, I'd hesitate more.

Member

JeffBezanson commented May 7, 2016

Yes, I do have some reservations but I can't see a better option than f.(x) right now. It seems better than picking an arbitrary symbol like ~, and I bet many who are used to .* (etc.) could even guess right away what it means.

One thing I would like to get a better sense of is whether people are ok with replacing existing vectorized definitions with .(. If people don't like it enough to do the replacement, I'd hesitate more.

@gabrielgellner

This comment has been minimized.

Show comment
Hide comment
@gabrielgellner

gabrielgellner May 7, 2016

Contributor

As a user lurking on this discussion I would VERY much like to use this to replace my existing vectorized code.

I largely use vectorization in julia for readability as loops are fast. So I like using it a lot for exp, sin, etc like has been mentioned previously. As I will already use .^, .* in such expressions adding the extra dot to sin. exp. etc feels really natural, and even more explicit, to me ... especially when I can then easily fold in my own functions with the general notation instead of mixing sin(x) and map(f, x).

All to say, as regular user, I really, really hope this gets merged!

Contributor

gabrielgellner commented May 7, 2016

As a user lurking on this discussion I would VERY much like to use this to replace my existing vectorized code.

I largely use vectorization in julia for readability as loops are fast. So I like using it a lot for exp, sin, etc like has been mentioned previously. As I will already use .^, .* in such expressions adding the extra dot to sin. exp. etc feels really natural, and even more explicit, to me ... especially when I can then easily fold in my own functions with the general notation instead of mixing sin(x) and map(f, x).

All to say, as regular user, I really, really hope this gets merged!

@diegozea

This comment has been minimized.

Show comment
Hide comment
@diegozea

diegozea May 8, 2016

Member

I like more the proposed fun[vec] syntax than fun.(vec).
What do you think about [fun vec]? It's like a list comprehension but with an implicit variable. It could allow to do T[fun vec]

Member

diegozea commented May 8, 2016

I like more the proposed fun[vec] syntax than fun.(vec).
What do you think about [fun vec]? It's like a list comprehension but with an implicit variable. It could allow to do T[fun vec]

@diegozea

This comment has been minimized.

Show comment
Hide comment
@diegozea

diegozea May 8, 2016

Member

That syntax it's free in Julia 0.4 for vectors with length > 1:

julia> [sin rand(1)]
1x2 Array{Any,2}:
 sin  0.0976151

julia> [sin rand(10)]
ERROR: DimensionMismatch("mismatch in dimension 1 (expected 1 got 10)")
 in cat_t at abstractarray.jl:850
 in hcat at abstractarray.jl:875

Member

diegozea commented May 8, 2016

That syntax it's free in Julia 0.4 for vectors with length > 1:

julia> [sin rand(1)]
1x2 Array{Any,2}:
 sin  0.0976151

julia> [sin rand(10)]
ERROR: DimensionMismatch("mismatch in dimension 1 (expected 1 got 10)")
 in cat_t at abstractarray.jl:850
 in hcat at abstractarray.jl:875

@diegozea

This comment has been minimized.

Show comment
Hide comment
@diegozea

diegozea May 8, 2016

Member

Something like [fun over vec] could be transformed at syntax level and maybe it worth to simplify [fun(x) for x in vec] but isn't simpler than map(fun,vec).

Member

diegozea commented May 8, 2016

Something like [fun over vec] could be transformed at syntax level and maybe it worth to simplify [fun(x) for x in vec] but isn't simpler than map(fun,vec).

@diegozea

This comment has been minimized.

Show comment
Hide comment
@diegozea

diegozea May 8, 2016

Member

Syntaxes similar to [fun vec]: The syntax (fun vec) is free and {fun vec} was deprecated.

julia> (fun vec)
ERROR: syntax: missing separator in tuple

julia> {fun vec}

WARNING: deprecated syntax "{a b ...}".
Use "Any[a b ...]" instead.
1x2 Array{Any,2}:
 fun  [0.3231600663395422,0.10208482721149204,0.7964663210635679,0.5064134055014935,0.7606900072242995,0.29583012284224064,0.5501131920491444,0.35466150455688483,0.6117729165962635,0.7138111929010424]
Member

diegozea commented May 8, 2016

Syntaxes similar to [fun vec]: The syntax (fun vec) is free and {fun vec} was deprecated.

julia> (fun vec)
ERROR: syntax: missing separator in tuple

julia> {fun vec}

WARNING: deprecated syntax "{a b ...}".
Use "Any[a b ...]" instead.
1x2 Array{Any,2}:
 fun  [0.3231600663395422,0.10208482721149204,0.7964663210635679,0.5064134055014935,0.7606900072242995,0.29583012284224064,0.5501131920491444,0.35466150455688483,0.6117729165962635,0.7138111929010424]
@stevengj

This comment has been minimized.

Show comment
Hide comment
@stevengj

stevengj May 8, 2016

Member

@diegozea, fun[vec] was ruled out because it conflicts with T[vec]. (fun vec) is basically Scheme syntax, with the multiple-arguments case presumably being (fun vec1 vec2 ...) ... this is pretty unlike any other Julia syntax. Or did you intend (fun vec1, vec2, ...), which conflicts with tuple syntax? Nor is it clear what the advantage would be over fun.(vecs...).

Furthermore, remember that a main goal is to eventually have a syntax to replace @vectorized functions (so that we don't have a "blessed" subset of functions that "work on vectors"), and this means that the syntax needs to be palatable/intuitive/convenient to people used to vectorized functions in Matlab, Numpy, etcetera. It also needs to be easily composable for expressions like sin(A .+ cos(B[:,1])). These requirements rule out a lot of the more "creative" proposals.

Member

stevengj commented May 8, 2016

@diegozea, fun[vec] was ruled out because it conflicts with T[vec]. (fun vec) is basically Scheme syntax, with the multiple-arguments case presumably being (fun vec1 vec2 ...) ... this is pretty unlike any other Julia syntax. Or did you intend (fun vec1, vec2, ...), which conflicts with tuple syntax? Nor is it clear what the advantage would be over fun.(vecs...).

Furthermore, remember that a main goal is to eventually have a syntax to replace @vectorized functions (so that we don't have a "blessed" subset of functions that "work on vectors"), and this means that the syntax needs to be palatable/intuitive/convenient to people used to vectorized functions in Matlab, Numpy, etcetera. It also needs to be easily composable for expressions like sin(A .+ cos(B[:,1])). These requirements rule out a lot of the more "creative" proposals.

@diegozea

This comment has been minimized.

Show comment
Hide comment
@diegozea

diegozea May 8, 2016

Member

sin.(A .+ cos.(B[:,1])) doesn't look so bad after all. This's going to need a good documentation. Is f.(x) going to be documented as .( similar to .+ ?
Can .+ be deprecated in favor of +.?

# Since 
sin.(A .+ cos.(B[:,1]))
# could be written as
sin.(.+(A, cos.(B[:,1])))
# +.
sin.(+.(A, cos.(B[:,1]))) #  will be more coherent.
Member

diegozea commented May 8, 2016

sin.(A .+ cos.(B[:,1])) doesn't look so bad after all. This's going to need a good documentation. Is f.(x) going to be documented as .( similar to .+ ?
Can .+ be deprecated in favor of +.?

# Since 
sin.(A .+ cos.(B[:,1]))
# could be written as
sin.(.+(A, cos.(B[:,1])))
# +.
sin.(+.(A, cos.(B[:,1]))) #  will be more coherent.
@stevengj

This comment has been minimized.

Show comment
Hide comment
@stevengj

stevengj May 8, 2016

Member

@diegozea, #15032 already includes documentation, but any additional suggestions are welcome.

.+ will continue to be spelled .+. First, this placement of the dot is too entrenched, and there's not enough to gain by changing the spelling here. Second, as @nalimilan pointed out, you can think of .( as being a "vectorized function-call operator," and from this perspective the syntax is already consistent.

(Once the difficulties with type-computation in broadcast (#4883) are ironed out, my hope is to make another PR so that a .⧆ b for any operator is just sugar for a call to broadcast(⧆, a, b). That way, we will no longer need to implement .+ etcetera explicitly — you will get the broadcasting operator automatically just by defining + etc. We will still be able to implement specialized methods, e.g. calls to BLAS, by overloading broadcast for particular operators.)

Member

stevengj commented May 8, 2016

@diegozea, #15032 already includes documentation, but any additional suggestions are welcome.

.+ will continue to be spelled .+. First, this placement of the dot is too entrenched, and there's not enough to gain by changing the spelling here. Second, as @nalimilan pointed out, you can think of .( as being a "vectorized function-call operator," and from this perspective the syntax is already consistent.

(Once the difficulties with type-computation in broadcast (#4883) are ironed out, my hope is to make another PR so that a .⧆ b for any operator is just sugar for a call to broadcast(⧆, a, b). That way, we will no longer need to implement .+ etcetera explicitly — you will get the broadcasting operator automatically just by defining + etc. We will still be able to implement specialized methods, e.g. calls to BLAS, by overloading broadcast for particular operators.)

@yuyichao

This comment has been minimized.

Show comment
Hide comment
@yuyichao

yuyichao May 8, 2016

Member

It also needs to be easily composable for expressions like sin(A .+ cos(B[:,1])).

Is it possible to parse f1.(x, f2.(y .+ z)) as broadcast((a, b, c)->(f1(a, f2(b + c))), x, y, z)?

Edit: I see it is already mentioned above... in the comment hidden by default by @github..

Member

yuyichao commented May 8, 2016

It also needs to be easily composable for expressions like sin(A .+ cos(B[:,1])).

Is it possible to parse f1.(x, f2.(y .+ z)) as broadcast((a, b, c)->(f1(a, f2(b + c))), x, y, z)?

Edit: I see it is already mentioned above... in the comment hidden by default by @github..

@stevengj

This comment has been minimized.

Show comment
Hide comment
@stevengj

stevengj May 8, 2016

Member

@yuyichao, loop fusion seems like it should be possible if the functions are marked as @pure (at least if the eltypes are immutable), as I commented in #15032, but this is a task for the compiler, not the parser. (But vectorized syntax like this is more for convenience than for squeezing the last cycle out of critical inner loops.)

Member

stevengj commented May 8, 2016

@yuyichao, loop fusion seems like it should be possible if the functions are marked as @pure (at least if the eltypes are immutable), as I commented in #15032, but this is a task for the compiler, not the parser. (But vectorized syntax like this is more for convenience than for squeezing the last cycle out of critical inner loops.)

@stevengj

This comment has been minimized.

Show comment
Hide comment
@stevengj

stevengj May 8, 2016

Member

Remember that the key goal here is to eliminate the need for @vectorized functions; this requires a syntax at least as general, nearly as convenient, and at least as fast. It doesn't require automated loop fusion, though it is nice to expose the user's broadcast intention to the compiler to open the possibility of loop fusion at some future date.

Member

stevengj commented May 8, 2016

Remember that the key goal here is to eliminate the need for @vectorized functions; this requires a syntax at least as general, nearly as convenient, and at least as fast. It doesn't require automated loop fusion, though it is nice to expose the user's broadcast intention to the compiler to open the possibility of loop fusion at some future date.

@yuyichao

This comment has been minimized.

Show comment
Hide comment
@yuyichao

yuyichao May 8, 2016

Member

Is there any drawback if it does loop fusion too?

Member

yuyichao commented May 8, 2016

Is there any drawback if it does loop fusion too?

@stevengj

This comment has been minimized.

Show comment
Hide comment
@stevengj

stevengj May 8, 2016

Member

@yuyichao, loop fusion is a much harder problem, and it's not always possible even putting aside non-pure functions (e.g. see @StefanKarpinski's exp(log(A) .- sum(A,1)) example above). Holding out for that to be implemented will probably result in it never being implemented, in my opinion — we have to do this incrementally. Start by exposing the user's intention. If we can further optimize in the future, great. If not, we still have a generalized replacement for the handful of "vectorized" functions available now.

Member

stevengj commented May 8, 2016

@yuyichao, loop fusion is a much harder problem, and it's not always possible even putting aside non-pure functions (e.g. see @StefanKarpinski's exp(log(A) .- sum(A,1)) example above). Holding out for that to be implemented will probably result in it never being implemented, in my opinion — we have to do this incrementally. Start by exposing the user's intention. If we can further optimize in the future, great. If not, we still have a generalized replacement for the handful of "vectorized" functions available now.

@stevengj

This comment has been minimized.

Show comment
Hide comment
@stevengj

stevengj May 8, 2016

Member

Another obstacle is that .+ etc. is not currently exposed to the parser as a broadcast operation; .+ is just another function. My plan is to change that (make .+ sugar for broadcast(+, ...)), as noted above. But again, it is much easier to make progress if the changes are incremental.

Member

stevengj commented May 8, 2016

Another obstacle is that .+ etc. is not currently exposed to the parser as a broadcast operation; .+ is just another function. My plan is to change that (make .+ sugar for broadcast(+, ...)), as noted above. But again, it is much easier to make progress if the changes are incremental.

@yuyichao

This comment has been minimized.

Show comment
Hide comment
@yuyichao

yuyichao May 8, 2016

Member

What I mean is that doing loop fusion by proving doing so is valid is hard, so we can let the parser do the transformation as part of the schematics. In the example above, it can be written as. exp.(log.(A) .- sum(A,1)) and be parsed as broadcast((x, y)->exp(log(x) - y), A, sum(A, 1)).

It's also fine if .+ doesn't belong to the same category yet (just like any non boardcasted function call will be put into the argument) and it's even fine if we will only do this (loop fusion) in a later version. I'm mainly asking if having such a schematics is possible (i.e. non-ambiguous) in the parser and if there's any draw back by allowing written vectorized and fuzed loop this way..

Member

yuyichao commented May 8, 2016

What I mean is that doing loop fusion by proving doing so is valid is hard, so we can let the parser do the transformation as part of the schematics. In the example above, it can be written as. exp.(log.(A) .- sum(A,1)) and be parsed as broadcast((x, y)->exp(log(x) - y), A, sum(A, 1)).

It's also fine if .+ doesn't belong to the same category yet (just like any non boardcasted function call will be put into the argument) and it's even fine if we will only do this (loop fusion) in a later version. I'm mainly asking if having such a schematics is possible (i.e. non-ambiguous) in the parser and if there's any draw back by allowing written vectorized and fuzed loop this way..

@yuyichao

This comment has been minimized.

Show comment
Hide comment
@yuyichao

yuyichao May 8, 2016

Member

doing loop fusion by proving doing so is valid is hard

I mean doing that in the compiler is hard (maybe not impossible), especially since the compiler needs to look into the complicated implementation of broadcast, unless we special case broadcast in the compiler, which is likely a bad idea and we should avoid it if possible...

Member

yuyichao commented May 8, 2016

doing loop fusion by proving doing so is valid is hard

I mean doing that in the compiler is hard (maybe not impossible), especially since the compiler needs to look into the complicated implementation of broadcast, unless we special case broadcast in the compiler, which is likely a bad idea and we should avoid it if possible...

@stevengj

This comment has been minimized.

Show comment
Hide comment
@stevengj

stevengj May 9, 2016

Member

Maybe? It's an interesting idea, and doesn't seem impossible to define the .( syntax as "fusing" in this way, and leave it up to the caller to not use it for impure functions. The best thing would be to try it and see if there are any hard cases (I'm not seeing any obvious problems right now), but I'm inclined to do this after the "non-fusing" PR.

Member

stevengj commented May 9, 2016

Maybe? It's an interesting idea, and doesn't seem impossible to define the .( syntax as "fusing" in this way, and leave it up to the caller to not use it for impure functions. The best thing would be to try it and see if there are any hard cases (I'm not seeing any obvious problems right now), but I'm inclined to do this after the "non-fusing" PR.

@yuyichao

This comment has been minimized.

Show comment
Hide comment
@yuyichao

yuyichao May 9, 2016

Member

I'm inclined to do this after the "non-fusing" PR.

Totally agree, especially since .+ isn't handled anyway.

Member

yuyichao commented May 9, 2016

I'm inclined to do this after the "non-fusing" PR.

Totally agree, especially since .+ isn't handled anyway.

@StefanKarpinski

This comment has been minimized.

Show comment
Hide comment
@StefanKarpinski

StefanKarpinski May 9, 2016

Member

I don't want to derail this, but @yuyichao's suggestion gave me some ideas. The emphasis here is on which functions are vectorized, but that's always seems a bit misplaced to me – the real question is which variables to vectorize over, which completely determines the shape of the result. This is why I've been inclined to mark arguments for vectorization, rather than marking functions for vectorization. Marking arguments also allows for functions which vectorize over one argument but not another. That said, we can have both and this PR serves the immediate purpose of replacing the built-in vectorized functions.

Member

StefanKarpinski commented May 9, 2016

I don't want to derail this, but @yuyichao's suggestion gave me some ideas. The emphasis here is on which functions are vectorized, but that's always seems a bit misplaced to me – the real question is which variables to vectorize over, which completely determines the shape of the result. This is why I've been inclined to mark arguments for vectorization, rather than marking functions for vectorization. Marking arguments also allows for functions which vectorize over one argument but not another. That said, we can have both and this PR serves the immediate purpose of replacing the built-in vectorized functions.

@stevengj

This comment has been minimized.

Show comment
Hide comment
@stevengj

stevengj May 9, 2016

Member

@StefanKarpinski, when you call f.(args...) or broadcast(f, args...), it vectorizes over all the arguments. (For this purpose, recall that scalars are treated as 0-dimensional arrays.) In @yuyichao's suggestion of f.(args...) = fused broadcast syntax (which I am liking more and more), I think the fusion would "stop" at any expression that is not func.(args...) (to include .+ etc. in the future).

So, for example, sin.(x .+ cos.(x .^ sum(x.^2))) would turn (in julia-syntax.scm) into broadcast((x, _s_) -> sin(x + cos(x^_s_)), x, sum(broacast(^, x, 2))). Notice that the sum function would be a "fusion boundary." The caller would be responsible for not using f.(args...) in cases where fusion would screw up side effects.

Do you have an example in mind where this would not be enough?

Member

stevengj commented May 9, 2016

@StefanKarpinski, when you call f.(args...) or broadcast(f, args...), it vectorizes over all the arguments. (For this purpose, recall that scalars are treated as 0-dimensional arrays.) In @yuyichao's suggestion of f.(args...) = fused broadcast syntax (which I am liking more and more), I think the fusion would "stop" at any expression that is not func.(args...) (to include .+ etc. in the future).

So, for example, sin.(x .+ cos.(x .^ sum(x.^2))) would turn (in julia-syntax.scm) into broadcast((x, _s_) -> sin(x + cos(x^_s_)), x, sum(broacast(^, x, 2))). Notice that the sum function would be a "fusion boundary." The caller would be responsible for not using f.(args...) in cases where fusion would screw up side effects.

Do you have an example in mind where this would not be enough?

@yuyichao

This comment has been minimized.

Show comment
Hide comment
@yuyichao

yuyichao May 9, 2016

Member

which I am liking more and more

I'm glad you like it. =)

Just yet another extension that probably doesn't belong to the same round, it might be possible to use .=, .*= or similar to solve the in place assignment issue (by making it distinct from the normal assignment)

Member

yuyichao commented May 9, 2016

which I am liking more and more

I'm glad you like it. =)

Just yet another extension that probably doesn't belong to the same round, it might be possible to use .=, .*= or similar to solve the in place assignment issue (by making it distinct from the normal assignment)

@stevengj

This comment has been minimized.

Show comment
Hide comment
@stevengj

stevengj May 9, 2016

Member

Yes, the lack of fusion for other operations was my main objection to .+= etcetera in #7052, but I think that would be resolved by having .= fuse with other func.(args...) calls. Or just fuse x[:] = ....

Member

stevengj commented May 9, 2016

Yes, the lack of fusion for other operations was my main objection to .+= etcetera in #7052, but I think that would be resolved by having .= fuse with other func.(args...) calls. Or just fuse x[:] = ....

@mschauer

This comment has been minimized.

Show comment
Hide comment
@mschauer

mschauer May 9, 2016

Contributor

👍 There are two concept huddled together in this discussion which are in fact quite orthogonal:
the matlab'y "fused broadcasting operations" or x .* y .+ z and apl'y "maps on products and zips" likef[product(I,J)...]and f[zip(I,J)...]. The talking past each other might have to do with that as well.

Contributor

mschauer commented May 9, 2016

👍 There are two concept huddled together in this discussion which are in fact quite orthogonal:
the matlab'y "fused broadcasting operations" or x .* y .+ z and apl'y "maps on products and zips" likef[product(I,J)...]and f[zip(I,J)...]. The talking past each other might have to do with that as well.

@stevengj

This comment has been minimized.

Show comment
Hide comment
@stevengj

stevengj May 9, 2016

Member

@mschauer, f.(I, J) is already (in #15032) equivalent to map(x -> f(x...), zip(I, J) if I and J have the same shape. And if I is a row vector and J is a column vector or vice versa, then broadcast indeed maps over the product set (or you can do f.(I, J') if they are both 1d arrays). So I don't understand why you think the concepts are "quite orthogonal."

Member

stevengj commented May 9, 2016

@mschauer, f.(I, J) is already (in #15032) equivalent to map(x -> f(x...), zip(I, J) if I and J have the same shape. And if I is a row vector and J is a column vector or vice versa, then broadcast indeed maps over the product set (or you can do f.(I, J') if they are both 1d arrays). So I don't understand why you think the concepts are "quite orthogonal."

@mschauer

This comment has been minimized.

Show comment
Hide comment
@mschauer

mschauer May 9, 2016

Contributor

Orthogonal was not the right word, they are just different enough to coexist.

Contributor

mschauer commented May 9, 2016

Orthogonal was not the right word, they are just different enough to coexist.

@stevengj

This comment has been minimized.

Show comment
Hide comment
@stevengj

stevengj May 9, 2016

Member

The point is, though, that we don't need separate syntaxes for the two cases. func.(args...) can support both.

Member

stevengj commented May 9, 2016

The point is, though, that we don't need separate syntaxes for the two cases. func.(args...) can support both.

@stevengj

This comment has been minimized.

Show comment
Hide comment
@stevengj

stevengj May 9, 2016

Member

Once a member of the triumvirate (Stefan, Jeff, Viral) merges #15032 (which I think is merge-ready), I'll close this and file a roadmap issue to outline the remaining proposed changes: fix broadcast type-computation, deprecate @vectorize, turn .op into broadcast sugar, add syntax-level "broadcast-fusion", and finally fuse with in-place assignment. The last two probably won't make it into 0.5.

Member

stevengj commented May 9, 2016

Once a member of the triumvirate (Stefan, Jeff, Viral) merges #15032 (which I think is merge-ready), I'll close this and file a roadmap issue to outline the remaining proposed changes: fix broadcast type-computation, deprecate @vectorize, turn .op into broadcast sugar, add syntax-level "broadcast-fusion", and finally fuse with in-place assignment. The last two probably won't make it into 0.5.

@mschauer

This comment has been minimized.

Show comment
Hide comment
@mschauer

mschauer May 9, 2016

Contributor

Hey, I am very happy and grateful about 15032. I would not be dismissive of the discussion though. For example vectors of vectors and similar objects are still very awkward to use in julia but can sprout like weed as results of comprehensions. Good implicit notation not based on encoding iteration into singleton dimensions has the potential to ease this a lot, for example with the flexed out iterators and new generator expressions.

Contributor

mschauer commented May 9, 2016

Hey, I am very happy and grateful about 15032. I would not be dismissive of the discussion though. For example vectors of vectors and similar objects are still very awkward to use in julia but can sprout like weed as results of comprehensions. Good implicit notation not based on encoding iteration into singleton dimensions has the potential to ease this a lot, for example with the flexed out iterators and new generator expressions.

@stevengj stevengj referenced this issue May 9, 2016

Closed

Vectorization Roadmap #16285

5 of 5 tasks complete
@stevengj

This comment has been minimized.

Show comment
Hide comment
@stevengj

stevengj May 9, 2016

Member

I think this can be closed now in favor of #16285.

Member

stevengj commented May 9, 2016

I think this can be closed now in favor of #16285.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment