-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
RFC: Use @: to construct a broadcasted object #31088
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,7 @@ using .Base.Cartesian | |
using .Base: Indices, OneTo, tail, to_shape, isoperator, promote_typejoin, | ||
_msk_end, unsafe_bitgetindex, bitcache_chunks, bitcache_size, dumpbitcache, unalias | ||
import .Base: copy, copyto!, axes | ||
export broadcast, broadcast!, BroadcastStyle, broadcast_axes, broadcastable, dotview, @__dot__ | ||
export broadcast, broadcast!, BroadcastStyle, broadcast_axes, broadcastable, dotview, @__dot__, @: | ||
|
||
## Computing the result's axes: deprecated name | ||
const broadcast_axes = axes | ||
|
@@ -1217,4 +1217,29 @@ end | |
end | ||
@inline broadcasted(::S, f, args...) where S<:BroadcastStyle = Broadcasted{S}(f, args) | ||
|
||
function _lazy end # only used in `@:` macro; does not have to be callable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does hold the place of a function, though. Either which way, it's necessarily a bit of a strange construct, so I'm not sure it really matters. I don't want to see it exported for exactly that reason. It's odd (but necessary). |
||
# wrap the Broadcasted object in a tuple to avoid materializing | ||
@inline broadcasted(::typeof(_lazy), x) = (x,) | ||
|
||
""" | ||
@: broadcasting_expression | ||
|
||
Construct a non-materialized broadcasted object from a `broadcasting_expression` | ||
and [`instantiate`](@ref) it. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see this as being akin to a generator; that is, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that "non-materialized broadcasted object" is a bit too mouthful. It's nice to have a concise word for it.
By the way, what do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those colons aren't code... it's a (now antiquated) way of writing a chained analogy. For example puppy : dog :: kitten : cat can be read "a puppy is to a dog as a kitten is to a cat." Or in other words, the relationship between a generator and a comprehension is comparable to the relationship between Broadcasted and broadcast. I shouldn't have written it like that — someone would only have reason to know that if they specifically took a particular US college entrance exam before 2005. 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, that's an interesting notation. Thanks for using it 😄 |
||
|
||
# Examples | ||
```jldoctest | ||
julia> bc = @: (1:3).^2; | ||
|
||
julia> bc isa Broadcast.Broadcasted # it's not an `Array` | ||
true | ||
|
||
julia> sum(bc) # summation without allocating an array | ||
14 | ||
``` | ||
""" | ||
macro (:)(ex) | ||
return esc(:($instantiate(($_lazy.($ex))[1]))) | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Super cool syntax! I like the fact that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, that's possible. Maybe better to do less in the macro. Another way is to create a custom wrapper object and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless |
||
|
||
end # module |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -974,6 +974,7 @@ export | |
|
||
@assert, | ||
@__dot__, | ||
@:, | ||
@enum, | ||
@label, | ||
@goto, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe export+document a renamed
_lazy
as well? Hiding this functionality behind a macro only does not seem right. But the macro is really nice syntax!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for reviewing the code!
I think it's better to avoid overly increasing the API surface. I don't see what exporting
Lazy
enables you to do other than what is already possible with@:
. Also, implementingLazy
can be done in a few lines so I don't thinkBase
should export it. (Note that it still makes sense forBase
to export@:
, even though it can be done in a few lines, because it let us use a common syntax across Julia ecosystem.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main advantage of having a macro is nice looking source.
The cost of macros in general is increased complexity: It is not immediately clear what the AST is, what the types are, etc. Hence, readers of code need to either trust the docs (that don't tell you what they do on the AST), read the macro code, or macro-expand. In this specific case, the macro does not do anything complicated, and could be written as a
lazy.(...)
. I am surely not the only one who tries to use macros sparingly, and I think lazy is important enough that it should be accessible to people who preferview
and dot-syntax or even explicitbroadcast
over@view
,@.
and the like.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree on the point that it is ideal to provide non-macro interface for language features whenever appropriate. It certainly makes sense for normal functions. However:
Not every feature in Julia has "normal syntax" equivalent. For example,
@inline
,@inbounds
,@simd
, etc. do not have a straight-forward non-macro syntax.You can always run
@macroexpand
orMeta.@lower
to see what is going on in a given macro (or a specialized syntax). I think macros in Julia are very transparent._lazy
does very unusual thing in the evaluation of the broadcasting expression (because that's its purpose). There has to be something that signals the readers that the evaluation rule is changed, even if they never used this feature. A macro is the best syntax for signaling this because that's what macros do.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough.