Common Lisp and Scheme's general goal is to be a high-level programming language for application development (also PL research in Scheme's case).
By general goal I mean what you could ultimately guess the focus is based on the implementation decisions and how the community uses the implementations.
They both do a good job but in very different situations.
I think Forth certainly starts you out at a level of abstraction only somewhat more rich than an assembler while a Lisp runtime certainly abstracts more away from you, but once a Forth has been built-up, they become similar. Gforth gets a lot of flack in the Forth community for not being minimal, but it's a good example of a feature-rich Forth with support for things like exceptions, filesystems, sockets, and more. The Forth community itself is split into broadly two camps. The minimalist camp, (where Chuck Moore likes to live) which believes in bringing up custom Forths for custom platforms, creates small Forths that try to limit abstraction. The more featureful ANS Forth community tries to focus on a more batteries-included experience and these have more in common with most Common Lisp and Scheme runtimes.
This is strictly speaking not true, even though that is a domain where Forth excels. It isn't rare at all to see Forth as the bootstrap language for bringing up new machines or architectures for instance. And I've seen it used for image processing applications, embedded language inside other products and so on. It's definitely not going to be your language of choice for hacking out the next CRUD app, even though there is no technical reason why you couldn't do that.
Just like Lisp is all about transforming arguments, Forth is all about transforming what's on the stack, and to a certain degree the two are equivalent even though their outward form looks at first glance very different. Forth has an easier and cleaner way to add multitasking to the core language than Lisp does (in my opinion, I'm sure others will disagree).
A more modern implementation of the concepts behind Forth can be found in Factor:
https://en.wikipedia.org/wiki/Factor_(programming_language)
Note that Forth (and other stack languages) are an acquired taste, plenty of people don't like them and a good number of those will simply never get beyond the initial turn-off provided by RPN.
Oh yeah I blanked on this. As an observer I've seen Forth used here even more so than in embedded systems.
(But, of course, it's less effort to just use a different language that has it out of the box.)
Writing in forth is a process of creating a domain specific language to solve the problem at hand, so it tends to get quite claustrophobic as you close in on the goal.
$ cat errors.tl
(defun bad ()
(length (cons 3) x))
$ txr -e '(compile-file "errors")'
errors.tl:2: warning: length: too many arguments: max 1, given 2
errors.tl:2: warning: cons: too few arguments: needs 2, given 1
errors.tl:2: warning: cons: constant expression (cons 3) throws
errors.tl:2: warning: cons: constant expression (cons 3) throws
errors.tl:2: warning: unbound variable x
and this is meeting a fairly low bar; we're not even looking at type checking.In Lisp I can work in a ahead-of-time compilation model just like C, Pascal or Java where I build the source files to object files, and the editor takes me to places that trigger diagnostics like the above.
What matters in readability is is how easily someone versed in that language can spot the error in bad code, and convince him or herself that good code is good, taking into account and advantage all the available tooling.
Forth is implementable in a tiny amount of space, and can produce fast executions. It can be fast when interpreted, and supports a straightforward compilation model. These are worthwhile qualities. Every computer scientist should know about Forth.
Without a doubt, Forth could compile a file and be able to warn that there is a stack underflow. (E.g. by taking advantage of the stack effect annotations, and check them against the actual effect, based on knowing the semantics of words). It won't be able to pinpoint it very precisely though. Without annotations, it is not easy to determine. A defined word which consumes 17 operands from the stack, and produces 9 in their place can be perfectly correct.
It’s been a long while since I last wrote a line of Forth, but issues like these can be countered with some parseable documentation. If the documentation of the word says it consumes 17 operands from the stack and returns 9, the compiler will be able to tell if either code or doc is lying.
I think part of this is the somewhat idiosyncratic indenting style, where closing parens are gathered together instead of their own line like curly brackets in C-style syntax. It ends up being somewhat similar to python, in that if a lisp program is indented "properly" you should be able to ignore the pileup of closing parens that tend to happen at the end of a long expression, and even the open parens, and see the structure for what it is.
I found that Parinfer's introduction helped me a ton in this regard <https://shaunlebron.github.io/parinfer/>