Open jkrumbiegel opened 3 years ago
fill(filler::Function, ...)
which fills A
with repeated invocations of filler()
, i.e. your proposed @fill(val, ...)
would be equivalent to fill(()->val, ...)
.Yes, this functionality would be great. But why call it @fill
? How should one remember or notice that the macro version @fill
does something different to fill
? That invites only bugs that are hard to track down. Macro versions of functions usually do the same without subtle differences (see @code_warntype
vs. code_warntype
).
P.S.: Why does it need to be a macro at all?
@thofma By the time fill
is called, its arguments have already been evaluated, just once. Although I agree that @fill
doesn't fit the pattern elsewhere very closely. It's possible that fill
could call copy
on them, which in cases like fill(Int[], 5)
would be the same as the macro version, although fill(rand(), 5)
would differ.
One problem with fill(::Function, size...)
is that not every callable is a function, although I see that for example get!(default::Union{Function, Type}, h::Dict{K,V}, key::K)
doesn't take f::Any
. Another is that currently this would make an array of functions, even if that's not a very useful thing.
I think you were trying to explain this above @mcabbott , but what would a macro version be able to do that a higher-order function version, taking a thunk (e.g. () -> LAxis(scene)
), couldn't?
Seems to me that every case could be covered by letting fill(f, args...)
take a thunk f = ()-> ...
(or even letting f
take, say, the array index at that element; but then it seems very close in spirit to something like map
, albeit with array output).
As @thchr suggested(If I understand your words correctly), an array version ntuple
meets the requirements:
julia> function mapfill(f, inds...)
map(CartesianIndices(inds)) do idx
f(idx.I...)
end
end
mapfill (generic function with 1 methods)
julia> mapfill((i, j)->Int[], 2, 3)
2×3 Array{Array{Int64,1},2}:
[] [] []
[] [] []
That wasn't so clear, sorry. I agree that a version which takes a function would be pretty natural, and would serve the same role as the macro proposal.
But it seems awkward to make this a method of fill
, since right now fill(() -> rand(), 3)
does do something, even if it's not so useful. I can't think why you'd want an array of the same function, but maybe someone does. If you decided it was OK to break that, it would still need to be fill(::Function, size...)
rather than accepting any other callable struct. Unlike say map
, but like get!
which similarly takes a zero-argument function just as a way to delay execution, so perhaps that's not so bad. (And IIRC this get!
replaced a macro @get!
.)
Another possibility is to make a method of collect
. Right now collect(Float64, 1:3)
works with types, but not with functions. Base.collect(f, xs...) = map(Base.splat(f), Iterators.product(xs...))
would let you write collect((_...) -> LAxis(scene), 1:3, 1:4)
.
The point of my proposal is mostly that it's quite convenient to have a short syntax for this. Of course you can do the same with higher order functions, or list comprehensions as I wrote in my post above. I personally think @fill
is fine, because obviously the macro version would do something related to but different from the non-macro function. I find the difference similar to edit
vs @edit
I think the
fill
function could use an accompanying@fill
macro, because I relatively often want to fill an array of a specific size with the result of an expression evaluated for every entry.This came up recently in a Discourse post, where someone was suprised
fill(Int[], 5)
gives a vector with five times the same empty Int Vector.One use-case I often encounter is in MakieLayout, where I'd like to generate a matrix of LAxis objects. They are obviously not all supposed to be the same. So a common thing to write is:
I propose the syntax:
Here is a preliminary implementation: