Skip to content

Implement for f.(args...) syntax#31553

Closed
tkf wants to merge 3 commits intoJuliaLang:masterfrom
tkf:for-dot
Closed

Implement for f.(args...) syntax#31553
tkf wants to merge 3 commits intoJuliaLang:masterfrom
tkf:for-dot

Conversation

@tkf
Copy link
Copy Markdown
Member

@tkf tkf commented Mar 30, 2019

This PR implements for f.(args...) syntax as discussed in #19198 and planned in #31088 (comment).

close #19198
close #31088

Implementation detail

  • A new Expr(:fordot, :(broadcasted.(expression))) AST node is introduced to represent for broadcasted.(expression).
  • This is lowered to a chain of broadcasted calls by using the pre-existing code in julia-syntax.scm but skips the outer-most materialize call.
  • for non(dot(call)) is a syntax error at the moment.
  • Lowering manually constructed for non(dot(call)) is an error at the moment.
  • There is no performance specialization in this PR. It is done separately in Faster mapreduce for Broadcasted #31020.

Demo

julia> ex = :(for f.(x))
:(for f.(x))

julia> ex.head
:fordot

julia> ex.args
1-element Array{Any,1}:
 :(f.(x))

julia> Meta.lower(Main, ex)
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─ %1 = Base.broadcasted(f, x)
└──      return %1
))))

julia> :(for f(x))
ERROR: syntax: invalid expression after `for`

julia> nondot = Expr(:fordot, :(f(x)))
:(for f(x))

julia> Meta.lower(Main, nondot)
:($(Expr(:error, "non-dot call after `for`")))

| `a==b` | `(call == a b)` |
| `1<i<=n` | `(comparison 1 < i <= n)` |
| `a.b` | `(. a (quote b))` |
| `a.(b)` | `(. a b)` |
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed a.(b) in "Operators" table (here) and mention it as f.(x) in "Calls" table since it seems the documentation does not match with the implementation:

> (julia-parse "a.(b)")
(|.| a (tuple b))

@stevengj
Copy link
Copy Markdown
Member

Does it support dot operators? e.g. for x .+ sqrt.(y)?

@tkf
Copy link
Copy Markdown
Member Author

tkf commented Mar 30, 2019

Yes

julia> x = .- (1:3)
-1:-1:-3

julia> y = x .^ 2
3-element Array{Int64,1}:
 1
 4
 9

julia> collect(for x .+ sqrt.(y))
3-element Array{Float64,1}:
 0.0
 0.0
 0.0

Its parsing and lowering are tested like

julia/test/syntax.jl

Lines 1846 to 1847 in 1882eae

@test Meta.parse("for .+ x") == Expr(:fordot, :(.+ x))
@test Meta.parse("for .+ g(x)") == Expr(:fordot, :(.+ g(x)))

and

julia/test/syntax.jl

Lines 1865 to 1874 in 1882eae

@testset for valid in [
"for f.(x)"
"for f.(g(x))"
"for .+ x"
"for .+ g(x)"
"for f.(args[1], args[2])"
"for f.(args...)"
]
@test Meta.lower(@__MODULE__, Meta.parse(valid)).head == :thunk
end

Let me know if there are other useful test cases.

@chethega
Copy link
Copy Markdown
Contributor

So the correct use for iterating would be e.g. for i in for (0:10).^2 ... end resp for i = for (0:10).^2 ... end?

The doubling looks weirder than for i = @: (0:10).^2 ... end or for i = @lazy (0:10).^2 ... end.

@antoine-levitt
Copy link
Copy Markdown
Contributor

I would imagine this pattern is less common than reducers (e.g. sum(for a.*b)): if you're going to iterate it, might as well do full iteration: for i=0:10 and then do something with i^2.

@chethega
Copy link
Copy Markdown
Contributor

Fair enough. But Keno's original proposal in #31088 (comment) brought up exactly the point of lowering for i = (1:10).^2 body end into a non-materializing form that currently is spelled for i = for (0:10).^2 body end, at least if I understand this PR correctly.

@tkf
Copy link
Copy Markdown
Member Author

tkf commented Mar 31, 2019

a non-materializing form that currently is spelled for i = for (0:10).^2 body end, at least if I understand this PR correctly

@chethega Yes, that's correct. But this is not a new design. This is a consequence of what was proposed in the triage.

@diegozea
Copy link
Copy Markdown
Contributor

diegozea commented Apr 4, 2019

I feel like it would be better/more explicit a @lazy macro or similar than adding new syntax to something so basic like for

@tkf
Copy link
Copy Markdown
Member Author

tkf commented Apr 4, 2019

I've implemented the macro-based solution in #31088 (I used different macro name but renaming it is quite easy). At this point I think it's up to triage to decide and merge the preferable one.

@StefanKarpinski
Copy link
Copy Markdown
Member

I should mention that the for f.(args...) syntax was not universally loved and there was another syntax that got mentioned on another triage call that was a little strange at first but kind of makes sense: if we use &x to create a value reference and &v[i] to create an array reference and &x.f to create a field reference, then it could make sense to have &f(x) be a reference to the result of a function call, which would be lazy.

@tkf
Copy link
Copy Markdown
Member Author

tkf commented Apr 4, 2019

it could make sense to have &f(x) be a reference to the result of a function call

I think it does not work well if you want to denote Ref by & as well for the arguments of dot calls #27563 (comment). The tricky part here is that we have two "quasiquote/unquote-like" relationships: materialization/laziness and broadcasting/scalarization. Maybe it would be better to discuss #27563 and #19198 together?

@adienes
Copy link
Copy Markdown
Member

adienes commented Dec 24, 2025

given the staleness and the controversy about the design in the first place, I think it's fair to close this

@adienes adienes closed this Dec 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Notation for lazy map

7 participants