-
-
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
implement local const #5148
Comments
I don't think that the const declaration is doing anything useful inside a function. Maybe users should be warned with a syntax error. |
Currently the difference between In fact current
I agree with the opinion that non-global scope consts are currently misleading. |
This seems like something that could be fairly confusing/unintuitive to a new user (indeed, I think I've seen several posts in julia-users where function foo()
const bar = 1
return bar
end
# equivalent to
function foo()
bar::Int = 1
return bar
end That way the user gets the same |
I would really like to see true const semantics inside functions as well at |
Yeah, I guess there are some subtleties in my proposal that would probably get us in trouble. Like the fact that |
Actually, I probably don't either. I was thinking that at least in local |
In global scope, you might want to reload some code and reassign a constant in the process – as long as this doesn't invalidate generated code, it's fine, so we allow it with a warning. I can't think of any good reason for to change a const binding in local scope, so the best option here would be to just enforce const in local scope, imo. |
I agree. |
Yes, changing the values of constants is not something to encourage :) |
Although an interesting question is whether to enforce it statically or dynamically --- i.e. check that only one assignment actually occurs at run time. |
This is actually something we should have for 1.0 since otherwise code that declares something |
Having
Whatever is decided I think it should be documented in the Julia Manual. |
Additionally we have the following behavior in global scope:
Maybe this is exactly what we want, but I feel that deprecating |
0.7 is basically done; certainly nothing further will be done about this in that timeframe. |
Any updates on this? I want to inline some functions (closures) that are generated by other functions, and this needs them to be declared constant.
|
What makes you think that is the case? |
@yuyichao I have done tests:
Search in the output, you'll see |
There's no change in inlining. Adding a local
|
@yuyichao I don't quite understand what you mean. The code is clearly not getting inlined in your example, or
And it is not what I need anyhow; I need that function to be a constant and optimized as such within the local scope. It is not going to be a global constant, since the generated function (in my real usecase) depends on input:
I want this
|
It is inlined. What I said is basically that your interpretation of the output is wrong.
Using it in
By all mean, it IS a local constant. It is a local variable that is never changed and the compiler had no problem figuring that out at all. In another word, allowing local constant won't make any difference for code that doesn't error when adding it. There are indeed additional optimizations that can be done on GLOBAL contants, but,
Please ask any further questions on discourse. |
I thought this was recorded here, but the main issue preventing this from being implemented is deciding what rule to use for local const. In global scope, constness is enforced dynamically. In local scope, however, we'd want a statically decidable rule. The question is what it should be. One simple option would be to only allow a single syntactic assignment, i.e. whereas this is allowed in global scope it would not be allowed in local scope: if true
const x = 1
else
const x = 2
end This still wouldn't guarantee that assignment occurs on every possible code path, however, as you could still write this: if true
const x = 1
end In such a case |
I am not parser expert to say for sure what is easily/safely doable, but what I would feel that a safe and acceptable rule would be that In general I see that care needs to be also taken when combining
|
At first glance this seems acceptable; if I forget to declare any other variable in a code path, I will run into errors anyway---
My knee-jerk reaction is to allow only a single syntactic assignment, but I can see it being desired to allow the constant to take on a different value depending on the function's arguments or global environment. So I'll take a stab at satisfying this before settling on my instinct. Perhaps, at most one syntactic assignment per syntactic code path, if that's meaningful and possible? Namely, if there is more than one assignment to a certain |
Analysis of code paths is pretty complex and generally something we don't want to bring into these rules. You have to figure out what the basic blocks are and what the dominance graph of the blocks is... it's heavy. One complication is that method definitions like if !@isdefined(f)
const f = make_function(:f)
end
add_method(f, :(...)) If you have a series of these in local scope, then you'd get multiple |
I wonder if we should try to make this existing global expression either more syntactic or more dynamic than it is now. Right now, this:
turns into
which leads to several odd intermediate states. I feel it would be preferable if this instead became:
which simultaneously would define a new const global |
I suppose a counter-argument to the idea of allowing branch-dependent
or using the ternary |
How much performance to we loose if we do this check dynamically (i.e. the same way as global |
Learning more about this issue is making me rofl function always defined despite code path never executing from here: julia> function fn_generator()
if true
f() = 0
else
f(x, y) = x+y
end
end
fn_generator (generic function with 1 method)
julia> name = fn_generator()
(::var"#f#3") (generic function with 2 methods)
julia> name(1,2) # 3??
3 function never defined despite code path always executing from here julia> function fn_generator2()
if false
f() = 0
else
f(x, y) = x+y
end
end
fn_generator2 (generic function with 1 method)
julia> fn_generator2()
ERROR: UndefVarError: `f` not defined the natural extension: julia> function fn_generator3()
if rand(Bool)
f() = 0
else
f(x, y) = x+y
end
end
fn_generator3 (generic function with 1 method)
julia> fn_generator3()
(::var"#f#4") (generic function with 2 methods)
julia> fn_generator3()
ERROR: UndefVarError: f not defined
julia> fn_generator3()
(::var"#f#4") (generic function with 2 methods)
julia> fn_generator3()
(::var"#f#4") (generic function with 2 methods)
julia> fn_generator3()
ERROR: UndefVarError: f not defined function not defined despite code path executing from here julia> @enum Case A B
julia> g = function (case, x)
if case == A
lambda(x) = 2x
elseif case == B
lambda(x) = x^2
end
lambda(x)
end
WARNING: Method definition lambda(Any) in module Main at REPL[5]:3 overwritten at REPL[5]:5.
#4 (generic function with 1 method)
julia> g(A, 5) # 25 .. wait, what?
25
julia> g(B, 5)
ERROR: UndefVarError: `lambda` not defined Thankfully it offers a warning, but it should be an error. function defined only in code path where it was not defined; modified version of this julia> function h(a)
if a
f() = 2
else
f() = 3
f(x) = 4
end
return f
end
WARNING: Method definition f() in module Main at REPL[5]:3 overwritten at REPL[5]:5.
h (generic function with 1 method)
julia> h(true)()
3
julia> h(true)(1)
4
julia> h(false)(1) # hahahaha
ERROR: UndefVarError: f not defined Placing named function declarations inside loops julia> function f1()
g() = 1
for i=1:3
g() = @show(i); g() # reassigns `g` at parent scope for no good reason, where `i` is undefined
end
g
end
WARNING: Method definition g() in module Main at REPL[33]:2 overwritten at REPL[33]:4.
f1 (generic function with 1 method)
julia> f1()()
ERROR: UndefVarError: i not defined
julia> i=3
3
julia> function f1()
i=5; g() = 1
for i=1:3
g() = @show(i); g() # ok so we've defined `i` at local scope, parent scope, and global
end
g
end
WARNING: Method definition g() in module Main at REPL[126]:2 overwritten at REPL[126]:4.
f1 (generic function with 1 method)
julia> f1()() # where is `i` *not* defined!?
ERROR: UndefVarError: i not defined
julia> function f1()
g() = 1
for i=1:3
local g() = @show(i); g() # doesn't reassign `g` at parent scope, works as expected
end
g
end
f1 (generic function with 1 method)
julia> f1()()
i = 1
i = 2
i = 3
1
julia> function f1()
for i=1:3
g() = @show(i); g() # doesn't assign `g` at parent scope; works as expected
end
g
end
f1 (generic function with 1 method)
julia> f1()()
i = 1
i = 2
i = 3
ERROR: MethodError: no method matching g()
julia> function f1()
g = ()->1
for i=1:3
g = ()->@show(i); g() # this is what you wanted anyway, if you wanted to reassign `g` at parent scope
end
g
end
f1 (generic function with 1 method)
julia> f1()()
i = 1
i = 2
i = 3
i = 3
3 If someone's using this design pattern, of declaring named functions within conditional branches in local scopes, and hasn't gotten properly buggered by it yet, they will—it's a time bomb. I am now much more strongly in support of outright disallowing For conditionally-evaluated code, we already have regular variables and anonymous functions; we don't need to be so abusive toward our constants 😝.
I don't think we need to be that restrictive though; it can be useful to precompute some values first before assigning the constant. So by this proposal: let # valid
const b = if x 1 else 2 end
end
let # valid
if x a=1 else a=2 end
const b=a
end
let # error
if x
const b = 1
else
const b = 2
end
end
let b = 2 # error
if x
const b = 1
end
end
let # error
x && (const b = 1)
end
let # valid
const b = x ? 1 : 2
end
let # error
x ? (const b = 1) : (const b = 2)
end
let # error
const b = 1
b = 2
end
let # error
b = 1
const b = 2
end
let # valid, but `c` is local to the innermost `let` statements and so is not used
if x
let; const c = 1 end
else
let; const c = 2 end
end
end
let # valid, but `const b` is local and does not over-write outer scope `b`
b = 1
if x
let; const b = 1 end
else
let; const b = 2 end
end
end
let # valid, but `b` is local and does not over-write outer scope `const b`
const b = 1
if x
let; b = 1 end
else
let; b = 2 end
end
end |
Some of those examples are really bad. |
I keep making mistakes that local
This rule seems fine, and can always be loosened later if desired. |
One issue with that rule is that it's fairly common to have local functions with multiple methods, which is naturally lowered to multiple assignments. |
Multiple local method declarations are not currently lowered to multiple assignments. Instead they become multiple (For example, run |
@StefanKarpinski said a while ago,
I think there's a simple solution: place a mild syntax constraint on the user. I propose this rule: Whenever a local constant is declared within a conditional, the conditional must declare and assign it exactly once. A conditional is considered to declare and assign a constant exactly once if (1) the conditional has an Examples: Valid syntax: let
if p
if q; const a = 1
else const a = 2
end
elseif q; const a = 3
else const a = 4
end
end Invalid syntax (local constant declared in conditional without default let
if p
if q; const a = 1
else const a = 2
end
elseif q; const a = 3
elseif !q; const a = 4 # <-
end
end Invalid syntax (local constant declared in conditional but not declared in every branch) let
if p
if q; const a = 1
else const a = 2
end
elseif q; const a = 3
else println("help") # <-
end
end This rule is reasonably easy to communicate to users, and is likely to be something they'd already follow anyway (and if not, they should). This would be a breaking change if it was implemented at global scope, but you might struggle to find examples that actually break if you did. (I'm not suggesting to implement this at global scope, I just think this would cover like 99.9% of use cases.) |
That seems identical to what #5148 (comment) said, but with somewhat less technical accuracy on how that would be implemented |
If you prefer, I can offer some untested pseudo-code to explain what I mean. It's just building up counts, comparing them to each other, and comparing them to 1:
I'm not showing any reassignment check here, as I'm sure that's a done deal already |
I am wondering why code:
executes and prints 4 without any warning or error, while
raises:
ERROR: invalid redefinition of constant x
Similarly:
prints bbb without warning, while:
prints bbb, but with warning:
Warning: redefining constant x
I think behavior of constants should be consistent in global and function scopes.
My Julia version:
The text was updated successfully, but these errors were encountered: