The thing that's wrong with Forth is it promotes eschewing verbal identification of variables for the sake of positional. This is promoted as good mental exercise, but is just bad language design. As Rich Hickey said, we humans prefer to identify things in terms of keywords, not positions. For any method with more than a couple parameters, kwargs are more convenient than positional arguments or, worse, implicit args on the stack. This is why concatenative languages are doomed to be just a forgotten cul-de-sac off the highway of the history of programming languages.
I frequently see arguments of the form "X is good; Y doesn't have X; therefore all typeof(Y) are doomed". I don't understand. Didn't you just give a criterion for success? Why can't we make a new Y2 that has X?
In this case, it doesn't even seem difficult. I've seen multiple stack-based systems that allow items labels. They don't use them for arg-passing (AFAIR), but I don't see why they couldn't.
I'm not even convinced that positional arguments are fatal for language design. Most popular languages today lack named arguments. They're usually only named on the callee side. I'm sure we've all run across a C function call that looked like foo(a, b, c, d, e, f, g, h), and cursed the author under our breath, and then counted out to the argument number we wanted to change.
Languages succeed or fail for many reasons, but I don't think I've ever seen "calling convention" be the deciding factor (no pun intended).
In the more general sense of concatenative (not necessarily stack-based) languages, Unix pipelines are a common example, and they don't seem to be doomed.
> concatenative languages are doomed to be just a forgotten cul-de-sac off the highway of the history of programming languages.
I gotta disagree. I've been working with Joy the last couple of years and I think it's possibly a "silver bullet".
Interestingly, it sidesteps a lot of the problems with stack manipulation by means of flexible combinators that permit various kinds of higher-order stack/expression manipulation.
Maybe the only problem here is that Forth only really properly supports integers (and floats on a separate stack).
If your language didn't come bundled with Vector support you can bet that adding two vectors wouldn't be terribly pretty either. Or at least it wouldn't merely be "+"
One can imagine "Forth with a typed Stack" where this would be as simple as "+".
Also - Forth can do named identifiers. It's just not the goto technique.
Many Forth developers do not like using named parameters AKA local variables. That said, they certainly exist, so if that is your only objection to Forth then there are easy ways to solve it.
All variables in Forth are local variables. They're just, traditionally, statically allocated, like a static variable in a C program. Their scope extends from their declaration to wherever the wordlist they're defined in stops being visible, or until another variable of the same name is defined, which means their scope usually does not extend to the end of your program.
I think the spec's choice to term stack-allocated variables "locals" (for what we call "automatic" variables in C) is a bad choice, because it confuses the issue further. When we refer to "local" or "global" variables, normally we are referring to scope, which is a question of over what part of a program a name maps to a particular location (or, in functional languages, sometimes a value). But what we're talking about here is extent or lifetime: during what period of time is a particular location allocated. These are clearly not unrelated concerns, since a name cannot usefully map to an unallocated location, and the same static occurrence of a name can map to multiple different locations when that name is bound on different binding contours, which cannot happen when the name is bound in the global scope; but these two concerns are also not identical, and I think it is important to distinguish them.
In Scheme, for example, the scoping rules are the same block-scoping rules we're familiar with from Pascal and C; but the extent of all Scheme values is unlimited — they can continue to exist until the end of the program, regardless of which subroutines have returned. This is also true in the ML family (OCaml, Haskell) and the abundant crop of modern Scheme-descended languages, including JS, Perl5, Python, Lua, and Ruby, but not in C++ or Rust. (Most, though, don't follow Scheme's mind-bending lead in having first-class continuations with unlimited extent, though Ruby does.)
So, what the Forth-2012 locals word set provides is names with dynamic or automatic extent: they refer to values that exist during a particular activation of a particular function. It also provides local scoping for names for them, but this is a much less significant change.
We can certainly quibble about the names given in a standard, but "local" is the term the Forth specification uses, so that's what I'm going with.
> All variables in Forth are local variables.
That's true in some sense (that is, all variables must be in scope to refer to them). However, I think that statement is a little misleading.
Traditional Forth variables often do have scope that extends to the end of your program (and possibly beyond). E.g., if your program says:
VARIABLE FOO
Then that's essentially a global variable in other languages; any function can read/fetch from foo (FOO @) or write/store to FOO (new-value FOO !). Indeed, you can read/write them after your program has completed.
Forth locals do not have this extent; they disappear when the function encompassing them returns. I note that Forth locals are allowed to be stored on the return stack, so that is one way to implement them. Thus, Forth locals are basically like "auto" in other languages like C.
> Then that's essentially a global variable in other languages; any function can read/fetch from foo (FOO @) or write/store to FOO (new-value FOO !)
This is the point where I disagree. Only functions statically within the scope of FOO --- that is, where FOO is visible when they are defined --- can read or write (or call) FOO, unless you pass them a pointer to it. If you write the following function in GCC, we have a similar situation with the variable m:
int (*adder(int n))(int)
{
static int m;
int rv(int p) { return m + p; }
m = n;
return rv;
}
m is visible to the inner function rv, because it is within its scope. It retains its value after adder returns, because it is statically allocated and thus has unlimited extent. (Static allocation is the only way to get unlimited extent in C or Forth, since they don't have garbage collection; woe unto those who attempt to use this adder function in almost any real situation!) But this does not mean that m is a global variable. On the contrary, it is a local variable lexically scoped to the body of adder. A similar situation obtains for traditional Forth variables, it's just that the scopes are not block-structured. But neither are they usually the rest of your program.
Several of the problems of global variables in other programming languages are absent or less serious with Forth variables. As I mentioned in the other thread, non-preemptive multitasking avoids the problem of another concurrent thread overwriting the variable except at well-defined (if implicit) yield points, and because each thread has its own dictionary, ordinary variables like FOO can be thread-local. The fact that Forth variables must be declared, and a Forth variable's lexical scope is suspended when another variable of the same name is declared, means that you don't have the collision problem you have in C, where if two libraries both define a global variable called "errnum", it causes a link error, or under some circumstances, both libraries use the same variable. (The wordlist system, providing separate namespaces for separate libraries, also helps with this.) Because recursion doesn't happen implicitly in Forth, and you have a stack you can explicitly save values on, statically-allocated variables don't prevent reentrancy in Forth as they do in old versions of FORTRAN, or as they do if you use them in C.
So, yes, Forth "locals" are basically like auto variables in other languages like C, in that they are allocated in the same way. But, in C local variables are implicitly auto, and in Forth they aren't; and in C making a variable non-auto is usually a bad idea, and in Forth it isn't.
Forth-2012's local variable support includes support for {: ... :}. A "|" within this separates parameters (which are loaded from passed parameters) from local variables. If I understand the spec correctly, it looks like you could do this:
Right, but even without the Forth-2012 LOCALS word set, you can and often should say something like
0 value var1 0 value var2 0 value var3
: my-name to var3 to var2 to var1 var1 var2 + var3 * ;
Maybe in this case you would just say
: my-name >r + r> * ;
or
: my-name -rot + * ;
but what I mean is that when you find that it's not obvious what's on the stacks, you can always just stick things in variables until it becomes obvious, just like you'd do in C or Python.
A dangerous temptation with Forth is to use the stacks whenever you'd use local variables in other programming languages. A little bit of that can be good, but it only takes four or five variables before it becomes completely unreadable. It never becomes impossible, because with >r and r> you already have a Turing machine, and with dup and swap you can arbitrarily splice its tape, but it very quickly becomes a Turing tarpit. Instead, if you don't have {: :}, and maybe even if you do, your default replacement for local variables in other languages should be traditional Forth variables, even though they're statically allocated. The reasons to studiously avoid global variables in other programming languages are a lot weaker or inapplicable for traditional Forth variables.