Currently, variables do not require explicit declarations; a variable is created when they get assigned for the first time. One drawback of this approach is that you can accidentally assign variables in outer scopes when you intend to make a new variable, like
i = 10
{
...
# in a deeply-nested scope
{
i = 100
}
...
}
This can be a nuisance but most of the time it is not too problematic.
What is problematic, though, is that this makes introducing new builtin variables hard. For instance, today there is no builtin variable called foo, so foo = lorem always creates a new variable. But if a builtin variable foo is to be introduced, the semantics of foo = lorem changes to modifying the builtin variable, which likely has some unintended side effect.
Hence I would like to make variable declarations explicit: use let for readonly variables and var for read-write variables. The syntax will simply be:
let x = foo # creates readonly variable x
var y = bar # creates read-write variable y
Since this is a far-reaching and mechanical change, a script should be provided for rewriting code to use the new syntax. Timeline:
Edit: as of 2020-08-17, the plan has been changed to support the new syntax in 0.15.0 and remove the old syntax in 0.16.0.
The proposed declarator for immutable variables has also been changed to val, instead of let.
What would be the behavior if you don't declare a variable, and just assign to it like today? Would it be an error?
I think that's a good change, also because it makes shadowing of variables possible.
I agree - I also think adding read-only variables can eliminate a lot of errors.
@zzamboni Yes. In 0.13 it will be an error.
To be clear, the syntax for assigning existing variables is not changing.
So this change was slower than expected :)
It seems to also make sense to attach synchronization semantics to the declaration as well. The var qualifier would make the variable an atom, while there is no such need for let-qualified names because they are read-only.
Why let? I'd think const would be more readily understood. Someone might be confused into thinking that the difference between let and var is similar to javascript – or Lispers might imagine that let is a bit like Lisp's let.
Also your comment about synchronization semantics and atoms are a bit too cryptic for me to understand. Which is not a problem, of course … unless you want feedback on the proposal.
@hanche fair point about let being potentially confusing. However, I'd also like to avoid const since it's used really inconsistently across languages.
I actually like Scala's val keyword for immutable binding. The only problem I see is that val are var are just a bit too similar, especially when you pronounce them.
Re synchronization semantics, I mean the way a variable is declared can affect the implementation of its, for example, assignment. A variable declared with var will automatically have a mutex guarding it so that assignment and use appear atomic, whereas a variable declared with let, val, or whichever marker that makes it immutable doesn't need to.
Just so I understand it better, do you mean to say that a statement like n = (+ $n 1) will be atomic?
And what about more complex scenarios, e.g.,
var n = 2
fn n-plus-one { + $n 1 }
n = (n-plus-one)
will the last line there be atomic too? You can imagine ever more complex situation, replacing n-plus-one by a convoluted computation that just happens to update $n, perhaps via a longer chain of intermediaries.
Oh, and I didn't know that const is being used inconsistently. The only inconsistent thing about it that I can think of, is related to mutable or immutable data structures. In elvish, since data structures are immutable, constant values will be _really_ constant, in that even their parts can't be changed.
Edit: I had a look at the wikipedia article on const. If you start looking at how C and its children treat the const keyword, that is a whole different kettle of fish indeed. But I don't think that should dissuade you from using the keyword in Elvish. (But if you want to start declaring explicit types in the language, I could see that it would be a concern.)
That said, I like val too, though this is the first time I hear of it. I wouldn't worry too much about the similarity with var, unless you imagine trying to transmit an elvish program vocally over a bad phone line. Or you could go slightly more verbose and use value.
@hanche n = (+ n 1) won't be atomic, unfortunately. We are not really talking about any kind of advanced synchronization here, just a basic memory safety mechanism that ensures that concurrent read and write to $n won't corrupt the memory.
This came to my attention because @xiaq asked on the IM channels this question:
what do y'all think of changing the syntax of setting a variable from just variable = value to set variable = value?
I'm not a fan of let, val, or const as a keyword to define a readonly variable. I'd prefer either a readonly keyword or an option to var; e.g., var &readonly x = y. In fact, the latter might be preferable in the long term because it allows introducing some type safety via additional options. For example:
var &number n
n = 1 # is okay since the value is a number
n = abc # throws a wrong type exception
There is also ambiguity with respect to how introducing set interacts with this proposal for declaring variables. Specifically, what happens if you do set x = y prior to doing var x.
I like the idea of a &readonly option instead of a separate keyword. For sure I know that I will never remember whether it's let or var that creates a readonly variable :D
I think I like the option too. But var &readonly seems too much like an oxymoron to me: var says variable, and then you proceed to declare it non-variable. Better then to use let, as let &readonly does not create the same cognitive dissonance.
Agree, either that or a separate keyword but with a more explicit naming, like readonly as suggested by @krader1961.
Moving this to the 0.16.0 milestone now that var and set are implemented, although the legacy syntax is not yet deprecated.
How about functions? Say you want to define mutually recursive functions. Currently, this may be done as follows:
fn bar { }
fn foo […]{ … code that calls bar … }
fn bar […]{ … code that calls foo … }
One could now replace the first line by var bar~. Interestingly, at the moment this works, even though $nil is supposed to be an illegal value for bar~. (It still raises an error if you try calling it.) I don't know if this was the intention.
But I wonder if it might be an idea to allow the form fn bar, without any function following? This could be equivalent to something like
fn bar [@_]{ fail 'Undefined function' }
The idea is that declaring a function in this way makes the intention more clear.