Julia's assignment behavior differs from Fortran?

Please consider the possibility that it is just unfamiliar, and after a few months or years of programming, you may appreciate Julia’s scoping rules as very well-designed.

It is hard to say more without a specific example, but you may just be fighting the language here by not writing idiomatic code. Again, just give it time.

5 Likes

6 posts were merged into an existing topic: Julia’s loop scope behavior differs from Fortran?

LeandroM, I’m flabbergasted that this program you wrote produces that result. It’s like there’s no sense of causality. So x = y creates some kind of life-long bond between the variables (within the function), so that if 500 lines later, you update the value of y, then the value of x gets silently updated too?? I can’t believe that.

1 Like

Told ya. :slight_smile:

That is how things work in every dynamically typed language. x = y means that the name x is bound now to the value that was associated with the name y. The “previous” x is lost forever. If you want that the elements of x are associated with the values that are assigned to the elements of y, you need to say that explicitly:

for i in 1:length(x)
  x[i] = y[i]
end

(and there are compact forms of expressing that, as x .= y).

2 Likes

Assignment is naming. That’s it. Just remember that and you’ll be in good shape. When you say x = Vector{Float64}(undef, 3), you’re saying to Julia that you want to use the name x for that undef vector.

When you later say x = y you’re saying to Julia that you have a better use for the name x — it’ll now be a name for whatever y was at that point. You could later decide you have a better use for the name y and it’ll not change x.

When you say x[1] = 1.0 you’re doing indexed assignment — which is wholly distinct from naming. It’s changing the first value of whatever x means.

17 Likes

This is exactly what would have happened with the equivalent Python, Java, or C code (for the old pointer arrays).

You notice you say there is no link of causality and then you are able to find the link immediately, no?

Please see my post why you should look at variables as labels not boxes.

3 Likes

Not just dynamically typed languages, it’s the same thing in C and C++ and Java and C# and Scala, etc. If you have double *x, *y; and later you do x = y what happens? It causes x to point to the same memory as y. If you modify that memory it is visible through both references (because it’s the same memory). Fortran is very much the odd man out here in that x = y does more than change what object x points at, it implicitly does a massive copy. The vast majority of languages do not have that kind of semantics for assignment of arrays. Matlab and R do have copy semantics for array assignment (but not other kinds of objects), probably because they were influenced by Fortran, but they are unusual in this.

13 Likes

To be fair, the C++ equivalent would probably be Vector which does copy the array. C++ and their possibility of redefining how attribution works for each different class also seems like an odd duck.

7 Likes

So, what should I expect this function to do?

function abc()
    xOld = 1
    xNew = xOld + 1
    xOld = xNew
    println("xOld = ",xOld,", xNew = ",xNew)
    return
end

By the previous logic, xNew is bound to xOld+1. But then xOld is bound to xNew, so that means xOld must equal xOld+1. So either there’s a contradiction, or xOld and xNew get ratcheted up to infinity together.

But if you run the program, you just get xOld = xNew = 2, which is what an old Fortran programmer would have expected. So I’m confused.

You can try it and see. Note that this example is different as there is no mutation of any objects, only assignment. The array example involves mutation of arrays.

2 Likes

I’m afraid I don’t understand the difference how x=y is mutation but xOld=xNew is assignment. Does the equal (=) command mean something different for arrays than for numbers?

Numbers always “copy” in julia. There is no concept of mutating a number because numbers are not mutable structs.

1 Like

Think more along the lines of eager evaluation then lazy evaluation. The expression on the right hand side is evaluated before it is assigned to the label on the left hand side.

2 Likes

There’s never any mutation with a plain x=y thing. It’s just naming and values. Names on the left, values on the right.

Just like how we use natural languages (like English), a particular thing can have no names, one name, or many names. The names you use for a particular thing can change over time and can be different depending upon which room you’re in (scope).

5 Likes

You are still thinking of variables as boxes, please look at the post I linked above.

No. xNew is bound to the value resulting from xOld+1 which is 2. xNew is not a closure which will always recompute its value when the variables used to define it are changed. What happened previously happened because you were dealing with a mutable object (a Vector/Array). You used two variables/labels to refer to the same object, and you changed inside the mutable object. So it reflected in the two variables because they are just names for the same object that now is different than it previously was.

3 Likes

They’re the same — they’re both assignment. It’s the y[1] = 1.0 and y[1] = 2.0 bit that’s mutation. Without that, arrays and scalars behave the same. Example:

function example_no_mut_scalar()
    xOld = 1
    xNew = 2
    xOld = xNew
    println("xOld = ",xOld,", xNew = ",xNew)
    return
end

function example_no_mut_array()
    xOld = [1]
    xNew = [2]
    xOld = xNew
    println("xOld = ",xOld,", xNew = ",xNew)
    return
end

They work the same way:

julia> example_no_mut_scalar()
xOld = 2, xNew = 2

julia> example_no_mut_array()
xOld = [2], xNew = [2]

The example you were surprised by involves mutation of an array, which cannot be done in the scalar case because integers are not mutable.

function example_mut_scalar()
    xOld = 1
    xNew = 2
    xOld = xNew
    xNew[1] = 1 # not actually possible
    println("xOld = ",xOld,", xNew = ",xNew)
    return
end

function example_mut_array()
    xOld = [1]
    xNew = [2]
    xOld = xNew
    xNew[1] = 1
    println("xOld = ",xOld,", xNew = ",xNew)
    return
end

These behave differently, but only in that mutating a scalar is an error:

julia> example_mut_scalar()
ERROR: MethodError: no method matching setindex!(::Int64, ::Int64, ::Int64)

julia> example_mut_array()
xOld = [1], xNew = [1]

Bottom line: no matter what kind of object you’re dealing with when you do x = y then x points at the same object as y.

10 Likes

Yes and no. As @StefanKarpinski pointed out, it is not the type that matters, but if it is x = y or x[i] = y. x = y is defined by the language it (creates a new name x if there was not one and) just binds the value to that name in that scope. x[1] = 2 is a completely different animal, it lowered to (setindex!(x, 2, 1); 2) (i.e., call the function setindex! for the appropriate type of x and return the value passed to it, in this example it is 2), you can see more typing ?setindex! in the Julia REPL.

1 Like

To clarify, x = y means the same thing for all values regardless of their kind. However x = y and x[i] = y are totally different.

4 Likes

I appreciate all of these detailed explanations, guys, but I’m still not confident that I know what’s going to happen in a given program. I think I’m going to have to program, print out, and pray.

2 Likes

I have been there. Fortran is so simple that we have to learn even about what they are talking about in these answers. Mutable? Immutable? Labels? x[i]=1 is a function call? It is fun if you take it the easy way.

2 Likes