# If b = a, when do changes in a affect b?

**URL:** https://discourse.julialang.org/t/if-b-a-when-do-changes-in-a-affect-b/61828
**Category:** New to Julia
**Created:** [May 26, 2021, 1:46am UTC](https://discourse.julialang.org/t/if-b-a-when-do-changes-in-a-affect-b/61828 "2021-05-26T01:46:35Z")
**Posts on this page:** 1
**Showing post:** 16

<div class="post-metadata">

### Author: ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)
#### Post date: [May 27, 2021, 6:16am UTC](https://discourse.julialang.org/t/if-b-a-when-do-changes-in-a-affect-b/61828/16 "2021-05-27T06:16:14Z")

</div>

> [@matthias314](#):
>
> Maybe this carries this thread too far away, but what is the rationale behind these different behaviours?

From my understanding, there’s no explicit rationale, but this behaviour is a consequence of how “it all works”. Let’s go through it.

**1. What does `const` mean?**

At its core, `const` is a promise to the compiler that the value of a variable won’t change and it’s a binding guarantee that the type of that same variable can’t change. You can see this for yourself:

```julia
julia> const a = 1                                                                                   
1                                                                                                    
                                                                                                     
julia> typeof(a)                                                                                     
Int64                                                                                                
                                                                                                     
julia> a = 2                                                                                         
WARNING: redefinition of constant a. This may fail, cause incorrect answers, or produce other errors.
2                                                                                                    
                                                                                                     
julia> a                                                                                             
2                                                                                                    
                                                                                                     
julia> a = 3.0                                                                                       
ERROR: invalid redefinition of constant a                                                            
Stacktrace:                                                                                          
 [1] top-level scope                                                                                 
   @ REPL[5]:1                                                                                       
                                                                                                     
julia> typeof(a)                                                                                     
Int64                                                                                                

```

Julia lets you change the value of `a` (but warns that this may affect existing/compiled code), but not its type.

**2. Functions and `const`ness**

In julia, every function has its own type:

```julia
julia> sin(1)                    
0.8414709848078965               
                                 
julia> cos(1)                    
0.5403023058681398               
                                 
julia> typeof(sin), typeof(cos)  
(typeof(sin), typeof(cos))       
                                 
julia> typeof(sin) == typeof(cos)
false                            

julia> isconst(Base, :sin), isconst(Base, :cos)
(true, true)                                   

```

As a consequence of functions having their own type and functions being constant, you quite literally can’t assign a new function (with a different method table, one being !== to the old one) to a binding that already is `const` and pointing to a function, since the type of the binding would have to change.

What if we could do that, i.e. that binding _wasn’t_ `const`? Well, the compiler wouldn’t have the guarantee that the variable/function didn’t change implementation under its nose, while compiling/running other code. It would have to insert more general lookups for _every_ call, even those that don’t change, instead of being able to cache and inline existing compiled code. As you can imagine, this kills performance and a whole bunch of optimizations that rely on code being inlined, unrolled and subsequently eliminated/replaced by faster equivalents.

**3. What about other bindings?**

If we now introduce a new non-const binding `g` like you’ve done, we’ve basically got the same situation as described above. At any point, `g` might point to a new function, thus change its type or it might point to no function at all (though it would still be callable, since everything in julia is callable). So when code uses `g`, the compiler _has_ to insert checks for each access, simply because it might change type! It’s the same reason any global you use should be `const` in the first place. Because of this restriction though, you can’t add methods to the function that `g` is pointing to (let’s call that `f`), since `g` is not `f` at all, it just gives a very unstable direction that, at the moment, points to `f`.

If you now make `g` a constant in the first place, the trouble goes away - it can’t readily be distinguished from `f`, since its type can’t change and functions (or rather, the mathod table associated with the type of the function) only have one instance (otherwise we’d be back at non-constness and lookups everywhere). Thus, it’s possible to create new methods for “`f`” (which is also “`g`”).

**4. How does local scope play into this?**

In your local scope example, `f(x) = x` and `g(x, y) = x+y` are two inner functions. In order to be able to use those inside of `a`, julia does a little trick: it moves those two functions outside of `a` and compiles them as part of the dependency chain of compiling `a`. In order to avoid name clashes with functions outside of `a`, they’re given a generated name, which is what’s being used in `a` internally.

```julia
julia> module Example                                                       
       f(x,y) = x*y                                                          
       
       function a()                                                          
           f(x) = x                                                          
           g = f                                                             
           g(x, y) = x+y                                                     
           return g
       end                                                                   
       end                                                                   
Main.Example                                                                
                                                                             
julia> names(Example, all=true)                                             
11-element Vector{Symbol}:                                                   
 Symbol("#a")                                                                
 Symbol("#eval")                                                             
 Symbol("#f")                                                                
 Symbol("#f#1") # I'm generated!
 Symbol("#g#2") # and so am I!
 Symbol("#include")                                                          
 :Example                                                                   
 :a                                                                          
 :eval                                                                       
 :f                                                                          
 :include                                                                    

```

But here’s something you might not have expected:

```julia
julia> x = Example.a()                                     
(::Main.Example.var"#g#2") (generic function with 1 method)

```

Why is there only one method? Let’s investigate with `@code_lowered`:

```julia
julia> @code_lowered Example.a()           
CodeInfo(                                   
1 ─ f = %new(Main.Example.:(var"#f#1"))
│ g = f                               
│ g = %new(Main.Example.:(var"#g#2"))
└── return g                            
)                                           

```

So our intuition was correct - both function definitions are hoisted out of `a` as regular anonymous functions (which are implemented as structs with no fields in this case, since there’s no captured state) and in place of the function we return an instance of that anonymous function (which has the method table attached). Since `g`-the-function doesn’t have anything to do with `f`-the-function and the variable `g` is not a constant (which don’t exist in local scope anyway), it’s just a regular rebinding of a variable and no method is “added” to `f`-the-function at all.

* * *

All in all, it’s important to remember that `=` is _not_ mathematical equivalence, it’s an assignment.

---

_[View the full topic](https://discourse.julialang.org/t/if-b-a-when-do-changes-in-a-affect-b/61828)._
