Skip to content
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

RFE: syntactic sugar f(x) .= 3+4x for vectorized function definition #30520

Closed
alhirzel opened this issue Dec 27, 2018 · 7 comments
Closed

RFE: syntactic sugar f(x) .= 3+4x for vectorized function definition #30520

alhirzel opened this issue Dec 27, 2018 · 7 comments
Labels
broadcast Applying a function over a collection speculative Whether the change will be implemented is speculative

Comments

@alhirzel
Copy link
Contributor

New to Julia, so please have patience if I am missing something in this suggestion.

Since the .= operator is used for in-place assignment, it does not currently make sense to define a function using this operator. That is to say, the statement f(x) .= 3+4x would not have any meaning. The statement and variations such as f .= x -> 3+4x indeed trigger strange errors.

I think it would be elegant to hijack the .= operator when the LHS is a function definition or RHS is a lambda; my hijack proposal is to make f(x) .= 3+4x and/or f .= x -> 3+4x into f(x) = @. 3+4x or something similar. This is less in-line with the behavior of .= as an "in-place assignment" operator, and more in-line with the dot as a broadcast operator.

@antoine-levitt
Copy link
Contributor

The standard way to do this kind of things in julia is to define functions for scalars, then vectorize: f(x) =2x+3;f.(X) if X is an array. Does this not do what you want?

@xanfus
Copy link

xanfus commented Dec 27, 2018

Thinking: "Limit argument types to Array{Any,N} by dot-before-equal-sign. Which syntactic sugar could be assigned to execute f(x::Scalar)? Sole "@" macro!"
Measures taken wouldn't improve readability for me.
Anyway, code can be reprocessed with some macro just to translate your new definitions to current standard, @alhirzel .

@stevengj
Copy link
Member

stevengj commented Dec 27, 2018

Defining "vectorized" functions (rather than using explicit "dot calls" f.(array)) is actively frowned on these days. The advantage of dot calls is that they fuse with other dot calls into a single loop.

That is, if you define a vectorized (elementwise) function f(array), then if you do 2 .* f(array) .+ 1 your f(array) call will allocate a temporary array and perform a separate loop. Whereas if you define only f(scalar) then 2 .* f.(array) .+ 1 or @. 2f(array) + 1 would allocate only a single array and fill it in with a single loop.

See also #17302 — we used to have a special macro for defining vectorized methods, which was removed for precisely this reason.

@stevengj stevengj added the speculative Whether the change will be implemented is speculative label Dec 27, 2018
@alhirzel
Copy link
Contributor Author

Thank you for the info. I did not previously have this clear of a picture. If the Julian style is to define scalar functions and promote, then I agree the .= operation for creating broadcasted functions is unmotivated. I wondered how one would keep track of which functions are "internally dotted" and which ones aren't, but if I summarize: this is resolved by assuming you broadcast at the vector call and all functions operate on scalars until you see a dot. If you want to avoid "along-the-way allocations", this is done by convention, and you assume there is sensible or no internal broadcasting for functions when you call them.

The only gap is behavior of the .= operator for function definition or lambdas. I don't know if it would be in best style to error out, but it is not intuitive what this operator would do from the "in-place assignment" perspective when applied to function arguments (as in the f(x) .= 3x+4 example). Any thoughts on what this should do? Here is a specific REPL session that is unintuitive IMO:

julia> f(x) .= 3x+4
ERROR: UndefVarError: f not defined
Stacktrace:
 [1] top-level scope at none:0

julia> f(x) = 3x+4
f (generic function with 1 method)

julia> f(x) .= 5x+6
ERROR: UndefVarError: x not defined
Stacktrace:
 [1] top-level scope at none:0

julia> f .= x -> 5+6
ERROR: MethodError: no method matching size(::typeof(f))
Closest candidates are:
  size(::BitArray{1}) at bitarray.jl:70
  size(::BitArray{1}, ::Any) at bitarray.jl:74
  size(::Core.Compiler.StmtRange) at show.jl:1561
  ...
Stacktrace:
 [1] axes at ./abstractarray.jl:75 [inlined]
 [2] materialize!(::Function, ::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{0},Nothing,typeof(identity),Tuple{Base.RefValue{getfield(Main, Symbol("##3#4"))}}}) at ./broadcast.jl:759
 [3] top-level scope at none:0

@stevengj
Copy link
Member

stevengj commented Dec 29, 2018

f(x) .= 3x+4 currently means broadcast!(identity, f(x), 3x+4). That is,it calls broadcast! and writes the output to the result of calling f(x).

This is actually potentially useful behavior if f(x) is some special array-allocation function. For example:

foo(x) = sort!(myallocate(length(x)) .= x .+ 1)  # several in-place operations on result of myallocate(n)

(Changing this into a new kind of function definition would be breaking, and hence cannot happen in Julia 1.x.)

@mbauman
Copy link
Sponsor Member

mbauman commented Dec 31, 2018

With your other example, f .= x->…, that's doing broadcasted assignment of the anonymous function into f, which must already exist as a mutable collection. That's why it's erroring in your example — you're trying to assign into a function that you've already defined! .= is different from straight = assignment as the left-hand-side must already exist and so it doesn't introduce a new binding. It just modifies something… so you're able to put any expression there and it'll modify it.

Given that we all agree that a "vectorized function definition" isn't really needed and that we have a defined meaning here, I think we can close this.

@mbauman mbauman closed this as completed Dec 31, 2018
@mbauman mbauman added the broadcast Applying a function over a collection label Dec 31, 2018
@alhirzel
Copy link
Contributor Author

Yep agree on closing; thank you for answering my questions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
broadcast Applying a function over a collection speculative Whether the change will be implemented is speculative
Projects
None yet
Development

No branches or pull requests

5 participants