Julia: const does nothing when used with local variables

Created on 14 Dec 2013  Â·  24Comments  Â·  Source: JuliaLang/julia

I am wondering why code:

    function f()
      const x = "aaa"
      x = 4
      return x
    end
    println(f())

executes and prints 4 without any warning or error, while

    const x = "aaa"
    x = 4
    println(x)

raises:
ERROR: invalid redefinition of constant x

Similarly:

    function f()
      const x = "aaa"
      x = "bbb"
      return x
    end
    println(f())

prints bbb without warning, while:

    const x = "aaa"
    x = "bbb"
    println(x)

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:

    Julia Version 0.2.0
    Commit 05c6461 (2013-11-16 23:44 UTC)
    Platform Info:
    System: Windows (x86_64-w64-mingw32)
    WORD_SIZE: 64
    BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY)
    LAPACK: libopenblas
    LIBM: libopenlibm
lowering

Most helpful comment

This is actually something we should have for 1.0 since otherwise code that declares something const but changes would break. In other words, going from ignoring const to respecting it is a breaking change. At a minimum, we should not allow const in local scope at all in 1.0 and then we can at least introduce a working version of this feature in 1.x without breaking anything.

All 24 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 local and const inside a function is that const forces you to assign a value to a variable.
At least consts should be explained more clearly in Julia Language Documentation.
Function was only one example. The same problem is also with other constructs introducing new scope, for instance:
for i in 1:5
const x = "abc"
x = i
println(x)
end

In fact current const behavior is the following:
1) in global scope: fixes variable type, warns upon change of value (so in fact what consts in global scope do is to bind type to a variable);
2) in other scopes: only forces assignment of a value on definition.

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 const is used inside scopes). Perhaps using const inside a scope should be equivalent to

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 const behavior inside scopes.

I would really like to see true const semantics inside functions as well at
some point. Besides consistency, it makes it easier to write correct code
in some cases.

Yeah, I guess there are some subtleties in my proposal that would probably get us in trouble. Like the fact that :: is like inserting calls to convert within the scope body while const is more of a type-assert in global scope. Though I guess there are uses of :: that are purely type-assert, right? When it's used on assignment? I guess I'm realizing I may not fully understand _all_ the subtle differences between const in global scope vs. :: as a type declaration in scope blocks vs. :: as a type assert with assignments.

Actually, I probably don't either. I was thinking that at least in local
scope, const should actually mean constant, but now I remember that there
has been talk about allowing to change global consts as long as they keep
their type.

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 const but changes would break. In other words, going from ignoring const to respecting it is a breaking change. At a minimum, we should not allow const in local scope at all in 1.0 and then we can at least introduce a working version of this feature in 1.x without breaking anything.

Having const deprecated on local variables I have the following questions:

  • syntax const global x = 1 and also global const x = 1 are both allowed in global scope; they are equivalent to const x = 1 if I understand it correctly; yes? The question is do we still want to allow all three forms in global scope?
  • syntax const global x = 1 and also global const x = 1 are both allowed in local scope - they define (or update if it was existing and constant and the type is correct - possibly with a warning) a global const x; I think this is OK, but the question is if we want to allow both orders of const and global keywords?

Whatever is decided I think it should be documented in the Julia Manual.

Additionally we have the following behavior in global scope:

julia> local x = 1
1

julia> const local y = 1
┌ Warning: Deprecated syntax ``const` declaration on local variable`.
â”” @ nothing none:0
1

Maybe this is exactly what we want, but I feel that deprecating local in global scope would be the cleanest approach (I understand that in global scope adding local has no effect). If we do not then local const x = 1 will become an error even if executed in global scope where using const is allowed.

See issue #10472. I don't think this is a high priority.

Agreed. I did not know about that issue. The things mentioned in my two posts above are all corner cases that probably do not matter much in practice.

However, I wanted to keep track of them as maybe some day core devs will be less overloaded 😄.

@StefanKarpinski are there plans to resolve this before v0.7?

It is already deprecated:

julia> function f()
           const x = 1
       end
┌ Warning: Deprecated syntax ``const` declaration on local variable` around REPL[20]:2.
â”” @ nothing REPL[20]:2
f (generic function with 1 method)

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.

function preCompute(input)
# do heavy computations
  @inline function fastFunc(x)
    # use the precomputed values and be fast
    ...
  end
  return fastFunc
end
...
let a=2
  f=preCompute(...) 
  [f(i) for i in 1:10^8] # needs const to be inlined
end

and this needs them to be declared constant.

What makes you think that is the case?

@yuyichao I have done tests:

using InteractiveUtils

function genF(x, y)
    @inline function f()
        if rand() <= 0.5
            return x
        else
            return y
        end
    end
    return f
end

const fOK = genF(1111111111, 222222222)
function r2()
    fOK()
end

let
    f = genF(333333, 7777777)
    function r()
        f()
    end


    println(@code_lowered r())
    println("----------")
    println(@code_typed optimize = false r())
    println("----------")
    println(@code_typed r())
    println("----------")
    println(@code_llvm r())

    println("*********************8")

    println(@code_lowered r2())
    println("----------")
    println(@code_typed r2())
    println("----------")
    println(@code_llvm r2())
end

Search in the output, you'll see 1111111111 in the output, but not 333333.

There's no change in inlining. Adding a local const will make no difference here since what you are observing is an irrelevant difference due to GLOBAL constant. If you want to see the same output, just use const global which is still allowed. Otherwise, there's no difference when you actually use f() in the function.

julia> function g()
           f = genF(333333, 7777777)
           return f()
       end

julia> @code_typed g()

@yuyichao I don't quite understand what you mean. The code is clearly not getting inlined in your example, or r() in my example. But it is getting inlined when we use global constants. And it is also clear that julia should be able to inline in these examples.
BTW, using const global seems disallowed:

ERROR: LoadError: syntax: `global const` declaration not allowed inside function around /Users/evar/Base/_Code/uni/stochastic/jo1/inlineloop.jl:25

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:

using DataStructures
function preP(p)
    # https://en.wikipedia.org/wiki/Alias_method
    n = length(p)
    u = p * n
    uover = MutableBinaryMaxHeap{Tuple{Rational{Int},Int}}()
    uunder = MutableBinaryMinHeap{Tuple{Rational{Int},Int}}()
    k = Array{Int}(undef, n)
    function pushu(i, u)
        if u < 1
            push!(uunder, (u, i))
        elseif u > 1
            push!(uover, (u, i))
        end
    end
    for (i, u) in enumerate(u)
        pushu(i, u)
    end
    while ! isempty(uunder)
        (uu, iu) = pop!(uunder)
        if isempty(uover)
            k[iu] = -1 # Mark as invalid. We can also use a dummy default value.
            continue
        end
        (uo, io) = pop!(uover)
        k[iu] = io
        pushu(io, uo - (1 - uu))
    end

    P = let n = n, u = u, k = k
        @inline function x()
            i = rand(1:n)
            @inbounds if rand() <= u[i]
                return i
            else
                return k[i]
            end
        end
    end
    println("Precomputed alias tables for $p")
    return P
end

p = [1 // 10, 3 // 10, 4 // 10, 36 // 1000, 98 // 1000, 5 // 1000]
push!(p, 1 - sum(p))
@assert sum(p) == 1
const P = preP(p)

I want this P function to get inlined. Currently I can only do that with a global constant, and something like this is not possible:

function getDistributionOfP(p)
  local const P = preP(p)
  return prop(freqtable([P() for i in 1:10^8])) # inlining P not currently possible
end

I don't quite understand what you mean. The code is clearly not getting inlined in your example, or r() in my example. But it is getting inlined when we use global constants. And it is also clear that julia should be able to inline in these examples.

It is inlined. What I said is basically that your interpretation of the output is wrong.

BTW, using const global seems disallowed:

Using it in let is allowed. And that's basically the only case this makes sense.

And it is not what I need anyhow; I need that function to be a constant and optimized as such within the local scope.

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,

  1. Those aren't something that you can get with a "local" constant, i.e.

    what you are observing is an irrelevant difference due to GLOBAL constant

  2. It doesn't make much difference if you are just using the function locally, i.e.

    Otherwise, there's no difference when you actually use f() in the function.

  3. You can only take advantage of it unless you are OK with a GLOBAL constant, i.e.

    If you want to see the same output, just use const global which is still allowed

Please ask any further questions on discourse.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

m-j-w picture m-j-w  Â·  3Comments

ararslan picture ararslan  Â·  3Comments

tkoolen picture tkoolen  Â·  3Comments

felixrehren picture felixrehren  Â·  3Comments

iamed2 picture iamed2  Â·  3Comments