Macros offered by @Henrique_Becker and @rdeits are great solutions to this problem. Discourse allows selecting only one solution, so I decided to do it differently.
@pauljurczak, the version @tomerarnon wrote is the right way to do it. It is slightly slower, so if you want blazing speed (I find this hardly necessary, but I do not know your use case) you could write a macro that expands to the tedious code you write in foo, nevermind, I wrote it, it is ugly as hell, my first non-trivial macro, and will probably break if you do anything unexpected, but it is the same as writing that tedious code by hand.
julia> using MacroTools
julia> macro fill(lhs, rhs)
@capture rhs [elements__]
quote
begin
@assert firstindex($(esc(lhs))) == 1 && length($(esc(lhs))) == $(length(elements))
$([:($(esc(lhs))[$i] = $(esc(element))) for (i, element) in enumerate(elements)]...)
$(esc(lhs))
end
end
end
@fill (macro with 1 method)
XD, I have always shied away from writing macros because I already failed at it many times, but while I was writing the answer I was thinking “this really should be a macro, it really should not be that hard, I should be able to write something that, in fact it would be a good learning experience for me”, so I ended up writing it more because I decided it was the time for me to finally try to learn macros again than because I really think it is a must have.
It probably have a lot of problems in it, however. That values.args seems something that could break easily (it does not handle, for example, passing a name of a tuple variable in the local scope, it needs to be the tuple itself in the @assign line).
I think you’re missing an escape on the right-hand side of each assignment. You have $v which should be $(esc(v)). You can see why this matters if you test in a function:
julia> function test!(x)
a = 1
@assign x [a, 2, 3, 4, 5]
end
test! (generic function with 1 method)
julia> test!(x)
ERROR: UndefVarError: a not defined
The missing esc means you end up trying to read a variable named a in global scope, not the local one with that name.
Adding esc fixes it:
julia> macro assign(vector, values)
code = :(begin end)
for (i, v) in enumerate(values.args)
code = :($code; $(esc(vector))[$i] = $(esc(v)))
end
return code
end
@assign (macro with 1 method)
julia> function test!(x)
a = 1
@assign x [a, 2, 3, 4, 5]
end
test! (generic function with 1 method)
julia> test!(x)
5
I agree that using .args directly is sketchy. I really like using @capture from MacroTools.jl` for this, since you can unpack exactly the syntax you expect (e.g. a list or a tuple) and get an error for any other syntax.
@Henrique_Becker@rdeits: I had a sneaky suspicion that a macro is the way to handle this case, but I didn’t want today to be the day I wrote the first one.
There was a tiny error in your macro, here is corrected version:
macro fill(lhs, rhs)
@capture rhs [elements__]
quote
begin
@assert firstindex($(esc(lhs))) == 1 && length($(esc(lhs))) == $(length(elements))
$([:($(esc(lhs))[$i] = $(esc(element))) for (i, element) in enumerate(elements)]...)
$(esc(lhs))
end
end
end
Ahh, it escaped the x that did not exist inside the macro and just happened to work because it was the right variable name, not the lhs/rhs arguments themselves.
This kind of works but then suddenly very strange bug appears that makes me avoid writing macros when possible. Seem like my distributed and parallel processing teacher said: the problem with distributed programming is that it has all problems/bugs of non-distributed programming, plus the problems/bugs that only happen with distributed programming. The same applies to macros, .
using BenchmarkTools
buf = [0, 0, 0, 0]
function foo(buf)
buf[1] = 3
buf[2] = 1
buf[3] = 4
buf[4] = 1
nothing
end
function baz(buf)
buf .= (3, 1, 4, 1)
nothing
end
function bar(buf)
v = (3, 1, 4, 1)
for i in eachindex(v)
@inbounds buf[i] = v[i]
end
nothing
end
@btime foo($buf)
@btime baz($buf)
@btime bar($buf)
Is buf this small in the actual use case, or is this just a simplified example?
If the actual buf is large, then storing the initialization data as code will flush the code cache of the CPU, making subsequent function calls slower.
In that case it might be better to go with something like
const ibuf = [3,1,4,1]
function bat(buf)
buf .= ibuf
nothing
end
The timing is very unstable on my fastest PC when I measure functions one by one in REPL (fixed CPU frequency, turbo off), but the ranking is the same. I’m surprised bar is faster than foo. I even tried:
function foo(buf)
@inbounds buf[1] = 3
@inbounds buf[2] = 1
@inbounds buf[3] = 4
@inbounds buf[4] = 1
nothing
end
and I was going to write it didn’t help, but after executing as a file with above foo, I’ve got: