Skip to content

Comment on Why Typing Erlang is Hard: Standard Erlangparent

Comments

Just to clarify: Values in elixir are still immutable.

The core team will insist that variables are immutable but bindings are mutable, but in my mind variables are their bindings (there is not a third category distinct from the value and the lexical binding).

I think there is huge debate as to whether this is good or bad - I think though you really really want to have mutable variables in the REPL. In your static code it's trivial to check for variable rebinding and be sure you're not doing it (credo makes this easy to set up)

Variables in Elixir are not mutable, they can just be rebound. Think 'let' in Scheme or ML.

in my mind variables are their bindings (there is not a third category distinct from the value and the lexical binding)

I was responding to this:

The core team will insist that variables are immutable but bindings are mutable

The documentation on the Elixir website refers to it as rebinding, and I don't see any references to bindings being considered mutable.

Rebinding means you've created a new binding with the same name. The original isn't being mutated, it's just not accessible anymore because of the scoping rules.

My point is I don't know what that means. The variable is the binding changing the binding by throwing away the old one is not distinct from mutating it because the binding "isn't a real thing, just conceptual", just as the variable isn't "a real thing"; unlike a value which is actually a thing sitting in a memory slot in your vm. If you can give me an example of where there is a meaningful distinction between the variable and its lexical binding I am happy to be enlightened.

In a scenario where you're just rebinding, you can't do this, for example (pseudo code):

    foo = 12
    for x in xs {
      foo = x + foo
    }
It would always create a new foo binding in the for loop with value (12 + x);

I think using Lisp syntax might help clarify, because it makes the binding scope explicit:

    (let [foo 15]
      (println foo) ; prints 15
      (let [foo 12]
        ; prints 12
        (println foo)
      ; prints 15
      (println foo)))
> unlike a value which is actually a thing sitting in a memory slot in your vm.

A mutable variable is a memory slot that can be mutated, though.

A mutable variable is a memory slot that can be mutated, though.

No, that's a mutable value.

The variable is the lexical representation of a value. If you change which value your variable binds to, you have changed the representation of the variable; you have mutated it. The underlying values in memory have not changed, they are immutable.

I'm not crazy that this is a real definition: https://docs.julialang.org/en/v1/manual/variables/#man-varia...

No, that's a mutable value.
The variable is the lexical representation of a value. If you change which value your variable binds to, you have changed the representation of the variable; you have mutated it. The underlying values in memory have not changed, they are immutable.

I completely agree with you that values and variables are separate things. However, mutable variables are still memory slots that can be mutated, while immutable variables are not. The memory slot of a variable usually lives on the stack, and contains a pointer to a value that lives elsewhere in memory.

I think the difference will be clearer in a language that distinguishes between introducing a variable and setting a variable.

    var foo = "abcd"; // introduce a new variable binding
    foo := "xyz"; // set a variable
Here's an example of what you can do in a language that has mutable variables that you cannot do in a language that only supports shadowing bindings:
    var strings = ["str1", "str2", "str3"];
    var foo = "abcd";
    for (string in strings) {
      foo := string;
    }
    println(foo); // Prints out "str3"
In this case, foo itself is a memory slot that can be mutated, independently of the strings, which are immutable values. foo is a mutable slot in memory that can be updated to contain different pointer values.

Now, if you only have shadowing, you end up with this:

    var strings = ["str1", "str2", "str3"];
    var foo = "abcd";
    for (string in strings) {
      var foo = string;
    }
    println(foo); // Prints out "abcd"
Each iteration of the loop is introducing a new binding with the name foo, but it is not changing or removing the original binding of foo. It's just that within the loop, only the local binding is visible.

I think part of the confusion around Elixir is that it only supports introducing new bindings, but the syntax looks like it's setting the value of a variable.

Another example that will hopefully help clarify:

    var foo = "abcd";
    var bar = "xyz";
    println(bar);
That example is nearly the same as this (and in terms of what the program does, it's identical):
    var foo = "abcd";
    var foo = "xyz";
    println(foo);
The only difference is that in the second case, the first foo binding isn't visible after the second one has been introduced. At least conceptually, they both still exist, but because of scoping rules, there's no way to access the first one.

I believe an example of where binding differs from assignment is if you define a function which references a binding in the outer scope, the create a new binding with the same name in the outer scope, the value inside the function does not change: a = 1 f = fn -> a end f.() # returns 1 a = 2 f.() # still returns 1

in my mind variables are their bindings

This feels like flawed thinking to me. I know what you're saying, but claiming that variables are mutable would lead me to believe we could have things like counters. I would say it's best to think of variables as the memory address of their binding. We are allow to rebind the name. That's how I see it, at least.

That's not at all the traditional definition of variable. A variable is the code-lexical representation of a value in memory. (Think "variable" in algebra, which is the formula-lexical representation of an abstract value).

Anyways we can't have traditional counters, because of 1) lexical scoping rules and 2) value immutability.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.