Add @scalar macro to Base?

Would it be useful if Base exported an @scalar macro? The usage of @scalar would be to declare that a struct behaves as a scalar for broadcasting. So this

@scalar struct A end

would be re-written to this:

struct A end
Base.broadcastable(x::A) = Ref(x)

This would probably encourage package authors to declare structs as scalars when they are meant to be scalars. The advantage of that would be that in many places we could change our code from this

foo.(Ref(A), [1, 2])

to this:

foo.(A, [1, 2])
4 Likes

The problem with macros before struct is that they don’t combine, the next one (Base.@kwdef, or one from a package) may get an AST that it cannot parse. Because of this, I think it would be better to have a macro like

@scalar A

but perhaps with more expressive name.

3 Likes

I guess it could be

@broadcastable_scalar A

but at that point it’s not much better than

Base.broadcastable(x::A) = Ref(x)
1 Like

As an aside, FWIW, Mason Protter posted an example somewhere of constprop working with

Base.broadcastable(x::A) = (x,)

but not with

Base.broadcastable(x::A) = Ref(x)
2 Likes

There’s a problem though with using (x,) generically:

julia> 1 .+ (1,)
(2,)

julia> 1 .+ Ref(1)
2

This is because as far as broadcast is concerned, (1,) is a 1D container whereas Ref(1) is a 0D container. This is rarely if ever a problem in real-world broadcasting code, but can cause some trickyness if you make it the default.

3 Likes

Seems we should add a 0d struct container for this.

1 Like

Yeah, I had a PR where I had hoped we could just make Some work for this since it was already in Base, but it was decided to not be a great idea, but we still dont have a great alternative https://github.com/JuliaLang/julia/pull/35778

1 Like

isn’t this just another special case workaround for lack of traits or multiple inheritance?

No, not really. Broadcast is already a trait based system. We’re using traits here.