-
Notifications
You must be signed in to change notification settings - Fork 63
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
Interpolate more in rule helpers and fix escaping of @non_differentiable
#325
Conversation
Codecov Report
@@ Coverage Diff @@
## master #325 +/- ##
==========================================
+ Coverage 89.76% 89.83% +0.06%
==========================================
Files 13 13
Lines 469 472 +3
==========================================
+ Hits 421 424 +3
Misses 48 48
Continue to review full report at Codecov.
|
Wow, that's great, thanks for looking into this! I will take a detailed look later today. For now just a tip (I think from @oxinabox) I've found very useful when debugging macros: |
src/rule_definition_tools.jl
Outdated
function ($ChainRulesCore.rrule)($(esc_primal_sig_parts...)) | ||
$(__source__) | ||
return ($primal_invoke, $pullback_expr) | ||
return ($(esc(primal_invoke)), $(pullback_expr)) | ||
end |
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.
Since this is now escaped correctly, there shouldn't be a need to interpolate something like ChainRules
anymore. That was just my proposed quick fix, since we didn't handle escaping correctly before.
src/rule_definition_tools.jl
Outdated
@@ -165,7 +165,7 @@ function scalar_frule_expr(__source__, f, call, setup_stmts, inputs, partials) | |||
return @strip_linenos quote | |||
# _ is the input derivative w.r.t. function internals. since we do not | |||
# allow closures/functors with @scalar_rule, it is always ignored | |||
function ChainRulesCore.frule((_, $(Δs...)), ::typeof($f), $(inputs...)) | |||
function ($ChainRulesCore.frule)(($(esc(:_)), $(Δs...)), ::$(typeof)($f), $(inputs...)) |
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.
No need to escape _
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.
As mentioned in https://github.com/JuliaDiff/ChainRulesCore.jl/pull/325/files#r604881501, the main intention was to obtain a (somewhat) cleaner output. I don't mind changing it though.
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.
Seems like _
getting mangled by macro hygiene is something that we should fix in Base. It definitely shouldn't get turned into a GlobalRef
. Mind opening an issue?
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.
Ok, JuliaLang/julia#40280 should fix this, so I think we don't need to escape it here.
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, basically looks good too me.
Once you have addressed the comments to your satisfaction merge and tag a release
src/rule_definition_tools.jl
Outdated
@@ -88,8 +88,8 @@ macro scalar_rule(call, maybe_setup, partials...) | |||
############################################################################ | |||
# Final return: building the expression to insert in the place of this macro | |||
code = quote | |||
if !($f isa Type) && fieldcount(typeof($f)) > 0 | |||
throw(ArgumentError( | |||
if !($f isa $Type) && $(fieldcount)($(typeof)($f)) > 0 |
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.
How do you feel about not using brackets that are redudent?
On the one hand less visually noisy, on the other hand less consistent with other symbols interpolated in
if !($f isa $Type) && $(fieldcount)($(typeof)($f)) > 0 | |
if !($f isa $Type) && $fieldcount($typeof($f)) > 0 |
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 got rid of all the interpolations again: #325 (comment)
src/rule_definition_tools.jl
Outdated
if !($f isa Type) && fieldcount(typeof($f)) > 0 | ||
throw(ArgumentError( | ||
if !($f isa $Type) && $(fieldcount)($(typeof)($f)) > 0 | ||
$(throw)($(ArgumentError)( |
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.
TIL throw
can be overwritten. It isn't a language keyword, just a built in function.
Though there seems like some inconsistency in what we are interpolating in.
In that we are interpolating in throw
, but not !
or >
, or isa
.
All of which i think would be at least as common to shadow as throw
(and still incredibly rare)
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.
Same here, now macro hygiene takes care of all these things (the only downside being the messier output of @macroexpand
).
test/rule_definition_tools.jl
Outdated
# all that matters is that the following don't error, since they will resolve at | ||
# parse time | ||
using ChainRulesCore: ChainRulesCore | ||
using ChainRulesCore: @scalar_rule, @non_differentiable |
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.
BlueStyle doesn't have a rule on this, but I think that it should.
And that submodules within another file should be indented.
JuliaDiff/BlueStyle#85
Espeically in this case when there are so many of them nested, that i am having trouble following exactly what is going on.
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 fixed the indentation.
test/rule_definition_tools.jl
Outdated
fixed(x) = :abc | ||
@non_differentiable fixed(x) | ||
|
||
# check name collision |
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.
# check name collision | |
# check name collision between a primal input called `kwargs` and the actual keyword args |
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 added your suggestion (unfortunately I missed it initially and couldn't commit the suggestion on Github anymore then).
src/rule_definition_tools.jl
Outdated
# Manually defined kw version to save compiler work. See explanation in rules.jl | ||
function (::Core.kwftype(typeof(ChainRulesCore.rrule)))(kwargs::Any, rrule::typeof(ChainRulesCore.rrule), $(primal_sig_parts...)) | ||
return ($(_with_kwargs_expr(primal_invoke)), $pullback_expr) | ||
function (::$(Core.kwftype)($(typeof)($(rrule))))($(esc(kwargs))::$(Any), ::$(typeof)($(rrule)), $(esc_primal_sig_parts...)) |
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 am not sure you can actually shadow Core
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.
Again this should be handled by macro hygiene.
test/rule_definition_tools.jl
Outdated
my_id(x) = x | ||
@scalar_rule(my_id(x), 1.0) | ||
|
||
module IsolatedSubmodule |
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.
what is being tested here?
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.
That all frule
and rrule
s can be called without errors - this would detect e.g. if the macro output would try to look up something in ChainRulesCore
in the module the rules were defined in (i.e., in IsolatedModuleForTestingScoping.ChainRulesCore
). To avoid having to import anything from ChainRulesCore in addition to @scalar_rule
and @non_differentiable
, these checks are performed in a separate submodule.
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.
Can we indicate this in the code with a comment?
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.
Done 👍
src/rule_definition_tools.jl
Outdated
) | ||
return esc(@strip_linenos quote | ||
pullback_expr = @strip_linenos quote | ||
function $(esc(propagator_name(primal_name, :pullback)))($(esc(:_))) |
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 changing this as well, it's much clearer now. Similarly here I don't think we need to escape :_
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.
Yeah, escaping :_
ends up with (arguably) cleaner output but makes the implementation a bit annoying to read.
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.
Have you tried this without all the interpolation, just with the changes to how esc
is used? I would prefer being careful with how we use esc
instead of just interpolating everything, since that's really how macro hygiene is intended to be used and I find the interpolations make the source code harder to read. I also wouldn't be too worried about the output of @macroexpand
, as long as stacktraces are good.
I agree, I already started to reduce the numbers interpolations in my local branch since I had the same feeling. I was quite happy about the (arguably) clean output of |
src/rule_definition_tools.jl
Outdated
@@ -356,7 +357,7 @@ function tuple_expression(primal_sig_parts) | |||
else | |||
num_primal_inputs = length(primal_sig_parts) - 1 # - vararg | |||
length_expr = :($num_primal_inputs + length($(esc(_unconstrain(primal_sig_parts[end]))))) | |||
Expr(:call, :ntuple, Expr(:(->), :_, DoesNotExist()), length_expr) | |||
Expr(:call, :ntuple, Expr(:(->), :($(esc(:_))), DoesNotExist()), length_expr) |
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.
We should not rely on this as a fix, since this behavior will likely change soon.
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.
What do you suggest? Using ::Any
instead? Or just :_
? I also noticed that if esc
is removed both in the pullback function signature and this anonymous function, then the macro hygiene will replace both occurrences of _
with the same variable name (something like `var"#249#_").
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.
Ah, in that case I'd go with ::Any
or just something like _tmp
.
@non_differentiable
@non_differentiable
@non_differentiable
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.
Just this small nit plus removing all remaining uses of esc(:_)
and this looks good to go. Sorry for being so picky here, but I am now very happy with this. Macro hygiene is always a bit difficult to get right, since mistakes can show up in subtle ways or only in edgecases. Thanks for sticking with me!
src/rule_definition_tools.jl
Outdated
# Manually defined kw version to save compiler work. See explanation in rules.jl | ||
function (::Core.kwftype(typeof(ChainRulesCore.rrule)))(kwargs::Any, rrule::typeof(ChainRulesCore.rrule), $(primal_sig_parts...)) | ||
return ($(_with_kwargs_expr(primal_invoke)), $pullback_expr) | ||
function (::Core.kwftype(typeof($rrule)))($(esc(kwargs))::Any, ::typeof($rrule), $(esc_primal_sig_parts...)) |
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.
Interpolation of rrule
can be removed?
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.
Done 👍
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.
Awesome, thanks!
Would it be useful to add |
Yes, I think that would make sense, but probably better as a separate PR. |
all good from my side, @simeonschaub and @oxinabox are far more knowledgeable than me anyway, I wanted to review to learn really |
The PR interpolates
all types and functions that are not provided by the usermore types and functions in the AST in the macros@scalar_rule
and@non_differentiable
. This fixes the following bug:Additionally, not the whole output of
@non_differentiable
is escaped anymore, fixing a possible name collision for arguments of namekwargs
:Macro output on the master branch:
Macro output with this PR:
This fixes #320.
Edit: Updated after the suggestions by the reviewers were incorporated.