Variable scope
Here's an interesting language design problem.
When compiling Kap code, whenever you reference a variable that does not exist that variable ends up being locally declared in the scope where it was seen.
Foo ⇐ {
a ← 1 ⍝ a is declared as local in the function Foo
a+2
}
Foo 0
3
a
Error at: 8:5: Variable not assigned: default:a
Now, let's do the following:
a ← 10
Foo ⇐ {
a ← 1 ⍝ a now refers to the root level instance
a+2
}
Foo 0
3
a
1
This is all fine for statically compiled code, but Kap is an interactive environment. As such, the code gets recompiled all the time, interleaved with variable declarations as part of the REPL session. If you accidentally declare a variable in root scope (which could be as simple as just typing the name of a variable) a function may accidentally become global rather than local, just because you had used that name in the REPL.
Some of this can be mitigated by declaring your functions in a different namespace, but real-life experience has recently shown that this is not sufficient. I ended up with a race condition in some parallel code, because what I thought was a local variable ended up being global. Everything worked fine in testing, but when parallelising the code the result had random errors. It took a while before I realised the problem was in the Kap code rather than the Kap engine.
Another solution (and the solution I went with in the example mentioned above) is to explicitly declare the variables as local. The syntax to do this in Kap is quite cumbersome:
Foo ⇐ {
declare(:local a)
a ← 1
a+2
}
This works, but clearly not the ideal syntax to have to use every time you want to protect against accidental scope issues.
Possible solutions
There are a few different approaches to solve this, broadly broken down into two categories: – Require declarations for locals – Require access to globals to be explicit
Required declarations for locals
In this model, every variable reference which has not been declared local will be global. This is not too dissimilar from what APL has traditionally done. In other words, the following would be valid, and return 10:
Foo ⇐ {
xyz ← 10
}
Foo 0
xyz
For dfns to not be overly annoying to use, one would need some really concise way of declaring local variables, preferably just a single character. Perhaps something like this:
Foo ⇐ {
⊞xyz ← 10
xyz + 1
}
Prefixing the symbol ⊞ before the the first use of a variable declares it as local. If the symbol is not present, the variable would always be declared in the root scope.
Explicit globals
The opposite of the above idea is to make all variable references local by default. I personally prefer this idea much more. The following is an example:
The following would return 16. Without the special symbol, it would be a compilation error because abc would be local and have not been given a value. This is the same as what Kap does today, only that the ⊞ symbol makes it explicit.
The drawback of this approach is that when using Kap from the REPL, accessing variables declared interactively is reasonably common. The following would not work anymore without adding a ⊞:
foo ← 1234
{⍵+foo}¨ someValue