8 ms·
It's about as fun and easy to implement a simple thing that looks like a forth as it is to implement a simple thing that looks like a lisp. But that's where the
by eatonphil 5y ago
It's about as fun and easy to implement a simple thing that looks like a forth as it is to implement a simple thing that looks like a lisp. But that's where the ease of forth ends for me.
I find forth basically impossible to read even when, at best, authors leave a comment on basically every 1-2 words. Writing it is pretty hard too.
In contrast it's about as easy to be productive in a lisp (CL or Scheme) as it is in Python or JavaScript (give or take useful 3rd party libraries).
Even if they're from the same time period and are interesting for being not very Algol-like, I don't think it makes a lot of sense to discuss Forth and Lisp in the same conversation. They were both improvements on very different things.
- voxl 5y agoThis is just personal preference. I find lisp impenetrable and parenthetical soup. But I can read pipelined function calls (in e.g. OCaml or Haskell) easily.
- eatonphil 5y agoMy point is that Forth's general goal is to be a higher-level assembly language for embedded systems. 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.
- Karrot_Kream 5y agoAt high levels (meaning you've built up some nice Forth words), I disagree. The main difference is that most Lisps and Schemes have a GC, so you're not in charge of allocating/freeing memory like you would in Forth. That doesn't stop Forth words from implicitly referring to a variable that actually contains memory allocation addresses and hiding the actual underlying details of the allocation. 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.
- eatonphil 5y agoYeah I can see that but, to make a comparison, just because you can build up high level functions in assembly doesn't make it a comparable language to Python/JavaScript.
- Karrot_Kream 5y agoI think Forth is unique in that you actually can be both high-level and low-level productive in a single "system", but I can also see the perspective that high-level Forth is more of just a concatenative language than a high level Forth as such. Regardless I think Forth's power is that it can be both a very low level language or a high level runtime.
- K0balt 5y agoI would beg to differ that that is the “goal” of forth, but I do think that there is truth to the statement in terms of niches best served when used by most programmers. 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.
- eatonphil 5y agoYeah I tried to clarify. I am not an authority on their literal goal. I was trying to describe the result when you look at the systems overall in terms of implementation and use; what they make easy.
- int_19h 5y agoJust to confuse things further, both CL and Forth give you control over parsing (reader macros in CL, direct access to input for immediate words in Forth). So you can make a more structured language out of Forth if you really want to, and you can get rid of parentheses in Lisp if they bother you, with enough effort. (But, of course, it's less effort to just use a different language that has it out of the box.)
- jacquesm 5y ago> My point is that Forth's general goal is to be a higher-level assembly language for embedded systems. 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) 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.
- eatonphil 5y ago> It isn't rare at all to see Forth as the bootstrap language for bringing up new machines or architectures for instance. Oh yeah I blanked on this. As an observer I've seen Forth used here even more so than in embedded systems.
- jedimastert 5y ago> I find lisp impenetrable and parenthetical soup 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/ https://shaunlebron.github.io/parinfer/>
- kazinator 5y agoEven if so, a compiler can read your Lisp code and find errors that are run-time problems in Forth. $ 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.
- rbanffy 5y ago> A defined word which consumes 17 operands from the stack, 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.
- deleted 5y ago[deleted]
- coliveira 5y agoIf you read "Thinking in Forth" you'll discover that Forth can be more readable than other languages, if you write in the style that is supported by the language. Of course, if your goal is to use the same algorithm as in, for example, Java, your code will look terrible.
- eatonphil 5y agoThanks! I have read that book.
- agumonkey 5y agoHow do you feel about haskell point-free or function oriented programming ? Some times it feels a bit like forth (lots of tiny combinator that can feel obscure). Just asking, I'm no expert neither in Forth nor Haskell.. just curious about your opinion
- eatonphil 5y agoToo far into the academic weeds for me to have any real opinion on it, sorry!
- kazinator 5y agoAlso, when we talk about reading code, the reading that matter is this: - how easily can you read incorrect code and find the problem. - how easily can you convince yourself that correct code is correct. x86 assembly language is highly readable: e.g. it's completely unambiguous that "xor eax, eax" clears the eax register, independently of the previous or following line of code. An x86 assembler can tell you that "xor eax" is missing an operand. In Forth, a word can receive insufficient arguments due to something distant in the program, not related to that word. For instance, suppose we push 200 operands onto the stack (o1 o2 o3, ...), followed by words which are supposed to decimate those operands down to one value. Then suppose we remove some operand in the middle, like o17. There will necessarily be a stack underflow. But that underflow will not happen inside that word which operates on o17. That word will cheerfully use o18 as its argument, thinking that it's o17. It is the word which operates on o1 that will bomb; at that spot in the execution when the correct code would pull o1 from the stack, the stack will be empty. In a language with syntax, if we edit an expression tree to randomly remove an operand, such that a function's arity is no longer satisfied, the error will be pin-pointed to that function call, possibly at compile time. Before even running the program your editor can be made to jump there to fix the error. (In Lisp, there is a vaguely analogous problem at read time with parentheses. If you have to close 25 parentheses, but close only 24, it is ambiguous which one of the 25 open parentheses is missing the closing one. The situation is pretty trivial: all parentheses are the same, so it doesn't matter. The reader can just report the location of the leftmost one of those 25 as the likely start of the issue.)
- eatonphil 5y agoGreat points. One thing that might help out with correctness is typed forths. But I haven't played with any of them. And I'm not sure if that solves the readability issue when sometimes you have stack manipulations happen very far ahead or behind relevant code.
- kazinator 5y agoProbably, if you're doing complex calculations with Forth that have many operands, you should favor left-reductions: reducing the stack should be as eager as possible. Don't pile all the operands into the stack all at once followed by a soup of words to decimate them. The code generation algorithm should be: whenever you write down an operand to be pushed into the stack, then if a reduction is thereby made possible, you should add the word to achieve that reduction.