News for hackers. Stuff that matters.
Re:Forth: Stack-Manipulation Operators by retrac on Friday May 14, 2021 @05:36PM (#27159973)
It's Forth tradition to make your own slightly incompatible Forth. Honouring that tradition, I suggest an extension to the VM. Either with a few registers, or a second stack. For pointers. That way, memory access to a single or handful of variables becomes a single word. Add things like dereference-and-increment load/store primitives and it becomes very C-like, and a rather nice target for compilers.

(I can't take credit for this idea: https://www.complang.tuwien.ac.at/anton/euroforth/ef08/paper... "Updating the Forth Virtual Machine" by Stephen Pelc for EuroForth 2008, which is probably also not the first time someone suggested this.)

Re:Forth: Stack-Manipulation Operators by lebuffon on Friday May 14, 2021 @07:13PM (#27160653)
Indeed. Chuck Moore, the inventor, added the "A" register to his machine Forth for holding addresses and temporary values to reduce "stackrobatics".
Re:Forth: Stack-Manipulation Operators by kragen on Friday May 14, 2021 @07:23PM (#27160714)
He explained that this was in part due to hardware considerations. I don't really understand why, although unlike stack machines, I can speculate. But then he got to like it.

He also eliminated all the stack operations from machineForth except DUP, DROP, and OVER. Not even SWAP!

Re:Forth: Stack-Manipulation Operators by protomyth on Friday May 14, 2021 @11:47PM (#27162164)
PostScript has the ability to assign values to variables.
Re:Forth: Stack-Manipulation Operators by kragen on Saturday May 15, 2021 @12:09AM (#27162277)
Can you elaborate on how this relates?
Re:Forth: Stack-Manipulation Operators by protomyth on Saturday May 15, 2021 @12:16AM (#27162314)
A more general solution than Chuck Moore's A register but solving the same problem.
Re:Forth: Stack-Manipulation Operators by kragen on Saturday May 15, 2021 @12:57AM (#27162519)
Hmm, I don't think assigning values to variables in PostScript with "def" is solving the same problem or even a related problem as the A register. Instead, it solves the same problem that assigning values to variables in Forth solves.

As I understand it, the A register, introduced in the MuP21, solved some problems having to do with on-die fanout, fanin, and path length that limited the performance of the Novix NC4016 and the Harris RTX 2000. It also allows you to do operations like memset and memcpy without having to fetch and store the pointers to RAM (at fixed addresses!) for every byte/word read or written, which improves performance dramatically, makes the code a lot more readable (especially with its incrementing addressing modes), and makes the code more compact.

None of these are problems that exist in the PostScript world.

You can't use the A register in the MuP21 or F18A as a local variable, because it isn't local; it's shared with whatever subroutines you call and whatever subroutine called you. Moreover, the A register is the only way to read and write memory, so they can't avoid overwriting it if they need to read or write memory.

[I've programmed in PostScript, and I've written a little Forth, but I've never programmed any of the chips mentioned above.]

Re:Forth: Stack-Manipulation Operators by kragen on Friday May 14, 2021 @06:34PM (#27160388)
Interesting! Some way to distinguish pointers from integers is kind of a sine qua non for memory safety; I've been kind of thinking along the lines of Smalltalk object descriptors, where each memory allocation is in a "segment" of its own, into which you can index with integers. That would save you from debugging segfaults. (But then, so does Hindley-Milner typing, and that doesn't cost a bounds-check on every memory access.)

The part that so far puzzles me is how to do this without losing too much of Forth's simplicity.

Pelc's proposal is to add scratch/index registers A and B (notably, not segregating integers from pointers) and base pointers X or LP (the frame pointer) and Y or UP (thread-local data pointer). His motivation seems to be primarily efficiency and clarity rather than memory-safety.

Probably you want at least LP and UP to be callee-saved. If you make LP into an additional stack rather than just a register, then you save yourself a Y> >R on entry to subroutines that allocate stack frames, and half of a R> >Y on exit. (The CPU still has to do the same amount of work, but plausibly you spend less on op dispatch overhead and code space.) Doing the same with the A and B scratch/index registers might make sense, or you could just treat them as caller-saved. At some point restoring individual shallow-bound variables one by one stops being a good tradeoff, and it starts making more sense to put them in a stack frame or instance-variable vector and access them by indexing off a base pointer.

Adding a small number of local-variable registers like this is kind of a pain for hand-programming, though, as opposed to as a compiler target. Instead of having to remember whether the quantity two levels down in the stack is the voltage or the current, you have to remember whether the A register is currently the voltage or the current. Much easier to say 0 VALUE VOLTAGE 0 VALUE CURRENT, or maybe VARIABLE V VARIABLE I, and then your code is always explicit about which one it's expecting. So I doubt Pelc's proposal can be justified on the grounds of clarity, rendering it merely a speed hack.

Re:Forth: Stack-Manipulation Operators by astrobe_ on Friday May 14, 2021 @07:43PM (#27160835)
A strange game. The only winning move is not to play.

If you want memory safety guarantees from the compiler, don't use Forth.

The paradigm in Forth is "correct by design", "fail-fast" and to fight complexity unguibus et rostro.

The paradigm in Forth is to increase the skills of the programmer rather than to increase the skills of the compiler. That's why it is a wonderful language for advanced training, IMO.

Re:Forth: Stack-Manipulation Operators by kragen on Friday May 14, 2021 @08:27PM (#27161119)
This is precisely the kind of self-aggrandizing macho nonsense I was criticizing in https://news.ycombinator.com/item?id=27159383.

1. What should you use if you want memory safety guarantees—whether because you're a novice, because you have better ways to spend your Saturday nights than disassembling core dumps, because you're building a platform for safely running untrusted code written by players who are competing against one another https://news.ycombinator.com/item?id=27160615, or because, given your own fallibility, you think it's the only way to be safe enough from ransomware gangs? Of course you're right that it's not ANS Forth or polyForth or machineForth, but what should it be? What's the lowest price in complexity we can get away with paying for that pearl? Surely we can do better than JavaScript. Or even PostScript.

2. You say the paradigm in Forth is "correct by design", "fail-fast" and to fight complexity uñas y rostro. Well, precisely what I want is to be fail-fast; standard Forth pointer arithmetic isn't, because indexing past the end of an array will just access some other object. That means that instead of failing fast it will usually produce incorrect results and possibly corrupt the data in that other object, producing errors downstream. To fail fast we need bounds-checking, and for bounds checking we need to distinguish pointers from integers.

3. You say, "The paradigm in Forth is to increase the skills of the programmer," which is to say, to be more difficult than necessary. You're free to make your Forth more difficult than necessary if you want, which is an enjoyable activity that will surely make you a better programmer, but that isn't what Forth was designed for. In fact it's close to the opposite of Forth's design principles, which are to fight complexity because complexity makes programming more difficult. That's why Forth programmers in the 01980s had IDEs with virtual memory, incremental recompilation, compile-time metaprogramming, reflection, and multithreading that fit on a floppy disk and could run in 64K of RAM. It's not because F-83 was written by superhumans; it's because Forth's design removed a lot of accidental complexity from the problems, allowing its implementors to focus on the essence.

I mean, you're probably right that most people using Forth today are doing it, like Apollo, not because it is easy but because it is difficult. But that's not what Chuck Moore was looking for, it's not what Elizabeth Rather is looking for, and it's not what I'm looking for either.

Re:Forth: Stack-Manipulation Operators by remexre on Friday May 14, 2021 @09:07PM (#27161321)
> because you're a novice, because you have better ways to spend your Saturday nights than disassembling core dumps

My experience is that with a development methodology that emphasizes early-binding, incremental testing, and aggressive factoring, I just don't have the same kinds of memory issues I do in C. Strings having an explicit length helps a lot, and I suspect stack manipulation having a linear-types-like feeling helps somewhat too, since you think a lot more about "should this be removing this from the stack" than "should I be using this variable again some time, you know, later."

> because you're building a platform for safely running untrusted code written by players who are competing against one another, or because, given your own fallibility, you think it's the only way to be safe enough from ransomware gangs

My aarch64 Forth runs under QEMU, and this is easy to set up, and wiring a PL011 to stdio works great.

If you're just trying to avoid buffer overruns, though (which I suppose meets the latter case but not really the former), I'd consider using a 64-bit Forth with 32-bit pointers and 32-bit offsets, storing the size of the allocation in the first word before the pointer, so you can check the offset in a special TAGGED!/TAGGED@. I suspect you'd still want the normal !/@ for speed, and implement TAGGED!/TAGGED@ in terms of them, though?

Perhaps the MTE extensions would work, but I haven't investigated them (I don't have a laptop whose CPU that supports them yet).

Re:Forth: Stack-Manipulation Operators by kragen on Friday May 14, 2021 @10:55PM (#27161889)
> I just don't have the same kinds of memory issues I do in C.

Interesting! I definitely haven't reached that level—in Forth I have the same kinds of memory problems I have in C, and also parameter-passing bugs and type errors. Is there a codebase you think of as exemplary that I could learn from? Or, maybe better, a screencast of somebody writing some software in the development style? (Interactive testing of new words doesn't leave development artifacts, after all.)

"Much better than C" might still be pretty bad compared to JS, Rust, or OCaml; have you tried them?

> My aarch64 Forth runs under QEMU, and this is easy to set up, and wiring a [UART] to stdio works great.

This is definitely a viable approach that can work in many circumstances! However, each trust domain in QEMU uses 16 megabytes of RAM and roughly a million microseconds of CPU time to start up and shut down, and if you're doing cross-architecture emulation so you can't use kvm, the CPU overhead is typically on the order of 300%. By contrast, each trust domain in Lucet takes about 16 kilobytes of RAM and 50 microseconds of CPU time to start up and shut down https://www.fastly.com/blog/lucet-performance-and-lifecycle, and a new trust domain in a language that implements object-capability security could conceivably take 16 bytes of RAM and 0.1 microseconds. In both cases you can usually come within a few percent of native-code performance—which probably isn't in the cards if every memory access is preceded by a bounds check, as in Valgrind (typically 1000% CPU overhead) or this pseudo-Forth approach. Communications round-trip time might be 1 μs per ping-pong in QEMU and 0.1 μs in the other cases.

So, there are cases where a secure-compiler approach can give you 3–6 orders of magnitude better performance than just using QEMU. If you have a multiplayer game where each player uploads code, you might be able to support a few hundred players on a PC with the QEMU approach, or a few hundred thousand players with the Lucet approach. (Not, probably, all connected over TCP at once.)

> consider using a 64-bit Forth with 32-bit pointers and 32-bit offsets

An interesting idea here is to use a 64-bit system with 64-bit pointers and 32-bit offsets, mapping each allocation 4 gibibytes and change apart. Basically using the high 32 bits of the pointer as an MTE address tag or segment ID. Bloated and probably slow, though, and of course the bounds checking isn't very precise—enough to ensure that you can't index into another allocation, but not enough to catch every off-by-one array indexing bug.

There used to be a fat-pointer bounds-checking version of GCC that replaced pointers with essentially Golang slices, and you could of course implement that kind of scheme on top of a standard Forth, much as you suggest with TAGGED@.

Re:Forth: Stack-Manipulation Operators by remexre on Saturday May 15, 2021 @01:58AM (#27162808)
> Is there a codebase you think of as exemplary that I could learn from?

My "one codebase I keep rewriting" is [0]; I don't consider the artifact as useful as the process, though. Particularly,

> in Forth I have the same kinds of memory problems I have in C, and also parameter-passing bugs and type errors.

I find the memory problems are helped a lot by incremental testing in the REPL, as well as designing in such a way that it's obvious what a word does -- if a word has a surprising side-effect or requirement, that's a good sign some design rethinking is in order. Thinking Forth [1] has some examples of the kinds of redesigns that can be helpful, though as a warning it has strong "grumpy old man" vibes. I find these hilarious, but some don't :) (Some of its design suggestions are also somewhat "off" in my view; not a huge fan of DOER/MAKE, personally.)

I definitely don't often experience parameter-passing bugs (assuming you mean "this takes 3 args, thought it took 2") nor type errors, and I'm actually a bit surprised to hear you do. I suppose I rather religiously stack comment, and write just about every word to have a "static" stack effect. I've also got a shell alias:

    ffd() { rg -FiIN ": $1 (" "${2:-$HOME/Projects/stahl/kernel/src}" };
so I can just quickly do e.g.

    $ ffd nip
    : NIP ( a b -- b ) SWAP DROP ;
    $ ffd allocate-pages
      : ALLOCATE-PAGES ( num-pages -- 0 0 | addr -1 )
I ought to set keywordprg in Vim to that too, then I could get it in-editor. Of course, this doesn't work for builtins and control constructs -- perhaps someday I'll have the compiler put the pointer in SOURCE in the word header, and write a language server that looks through them.

> [...] JS, Rust, or OCaml; have you tried them?

Yeah, more the latter two than JS. Maintaining memory safety is easier in a memory-safe language, but I'm not certain overall correctness and design simplicity is. Certainly, when I'm implementing things in OCaml, I feel like I have to make design compromises much more than in Forth (where Forth gives me more room to redesign instead of getting forced into an unideal compromise). Rust's macros give me a bunch more leeway, but it's still occasionally awkward to express some things where something really is just easier to express at runtime, but it's impossible or UB/unsound to do so.

Common Lisp is perhaps a better competitor in this regard, since like Forth it supports both powerful compile-time and run-time metaprogramming. Maybe MetaOCaml too, though I've yet to try it.

> However, each trust domain in QEMU [...]

Yeah, of course QEMU isn't a silver bullet in this regard; if your application needs to be able to spawn large numbers of sandboxes quickly, especially. If you wanted to push virtualization as far as it could go, I'd consider the rust-vmm/Firecracker work instead -- they have stock Linux booting in 250ms/core, and a (perhaps custom) ringbuffer shared between the two processes would of course be vastly lower overhead than proper emulation of hardware.

> [...] Lucet [...]

I actually considered dropping WASM in initially, but I removed it after remembering my experience with trying to do JITtish things in it -- it's not currently practical (as of like a year ago) to do a DTC Forth, or a Forth that inlines, in WASM, since words are just too small. WASM being an "extreme" Harvard architecture is also fairly annoying. I suppose an ITC Forth ought to work, but without an inliner, performance seems like it'd be fairly bad.

I believe there's an API in spec-development currently to add better primitives for JIT that's sized closer to a basic block, so maybe that would change the situation.

> [...] object-capability security [...]

If you're going that... high-level? abstract? why not use Factor? I think, as you describe above, a Smalltalk-inspired approach might be a better foundation than most Forths' "assembly-inspired" ones. Though, as I understand it (too young to have lived through it!), Smalltalk had awful performance until Self came along and invented modern JIT, by which point you've completely lost the virtue of simplicity of implementation (at which point I'd be biased to say, you should use a Lisp or Haskell variant instead!)

> [...] 64-bit pointers [...]

Yeah, I considered a variation of that (most memory bugs are off-by-n for a small n, so guard pages are almost as good), but having a minimum allocation size of 4k seemed too wasteful.

> [...] replaced pointers [...]

Oh, I think TCC [2] still implements that! My understanding was that doing that relied on the sorts of C UB rules that some people hate (and that I quite like that Forth lacks!) though. I suppose if sizeof(uintptr_t) == 2*sizeof(ptrdiff_t), that wouldn't need to use the rules, but I think that'd suck pretty bad in Forth.

---

I think there are (at least!) two axes being compared here:

1. How easy is it to write correct Forth (I include security in correctness -- arbitrary code execution is typically pretty incorrect!)

2. How easy is it to execute attacker-provided Forth safely

I'd say Forth is strongly competitive on the first axis, and isn't at all on the second. I'd certainly believe that Factor or a Smalltalk-like Forth could be competitive on the latter, but if you include bugs in the compiler and runtime as correctness violations, I suspect it'd suffer a lot on the first axis.

I've thought a bit about getting stronger assurances of correctness for my Forth programs without complexifying the implementation of the system (which would make incorrectness much more likely). Currently I'm thinking about doing proofs in separation logic, with symbolic execution acting to hopefully provide a high degree of proof automation. An important characteristic is doing these proofs on the program starting at some physical machine state at a fixed time (e.g. just before some main loop is entered), rather than on the abstract machine that the runtime and compiler hopefully implement correctly. I think the system is simple enough that no mismatch between the two would occur, but I suppose I'll see whenever I've finished learning enough to start implementing this.

---

[0]: https://git.sr.ht/~remexre/stahl/tree/main/item/kernel/src [1]: https://managedway.dl.sourceforge.net/project/thinking-forth... [2]: https://bellard.org/tcc/

Re:Forth: Stack-Manipulation Operators by kragen on Saturday May 15, 2021 @06:04AM (#27163888)
Wow, this is wonderful stuff! I'll definitely look more at stahl :)

By "parameter-passing bugs" I mean things like "I forgot to put a DROP after this loop that was keeping a variable on the stack" or "this takes 3 args, and I knew that, but I accidentally passed it 2, and then it took me an annoyingly long time to figure it out".

I don't really think of Factor (or PostScript) as being terribly similar to Forth despite their concatenative syntaxes, although maybe that just means I'm emphasizing a different aspect of Forth than you are. I feel like Factor and PostScript are more like Lisp or (especially) Smalltalk than they are like Forth, just with less syntax.

The thing that appeals to me most strongly about Forth is the prospect of a steam-catapult-like leap from machine code, through a few hundred lines of code, to a fairly usable low-level programming language environment: concise code with nested expressions, recursive functions, named variables, ad-hoc compile-time metaprogramming, function pointers (DEFER or DOER/MAKE), a form of closures (CREATE DOES>), and an interactive REPL that doesn't need to be restarted to see code changes (though maybe not a single-stepping debugger). That is, it's the simplicity of the implementation that appeals to me, with the transparency and malleability that implies.

But I don't really prefer to type b negate discrim sqrt + 2 a * / instead of -b + sqrt(discrim()) / (2*a) or (/ (+ (- b) (sqrt (discrim))) (* 2 a)). I don't really like having to type "." or "?" to see the result of an expression I'm evaluating interactively. I don't think the RPN syntax is actually better, mostly, though it has some real advantages as a user interface. I just think the RPN syntax is an acceptable tradeoff for the simpler implementation, more powerful metaprogramming and refactorability, and better performance that the Forth design provides, and it's easy enough to use it to implement something more reasonable.

I'm still more intimidated than I should be by formal methods. I've found DRMacIver's QuickCheck-like Hypothesis, which randomly searches for counterexamples to your assertions of properties you would like your program to have and then reduces them to minimal test cases, to be astoundingly effective at improving my testing, and I've been thinking something like his "Minithesis" could be a significant boost to getting a new programming environment up and running. But that's about as far as I've gotten.

Re:Forth: Stack-Manipulation Operators by remexre on Monday May 17, 2021 @12:54PM (#27185560)
> [...] parameter-passing bugs [...]

Ah, yeah, incremental development is the panacea here.

> [...] Factor (or PostScript) [...]

Yeah, agreed; if you're needing to be able to safely execute untrusted code though, I'd rather have a language with trustworthy properties (i.e. one restricting one from making arbitrary syscalls, arbitrary pointer reads+writes, etc.) rather than applying sandboxing to a language that allows the program to do all these unsafe operations.

> [...] leap from machine code [...]

Agreed :)

> [...] prefer to type [...]

Yeah, I haven't found a syntax for reading math that's better than infix; I don't actually think the postfix is much better than prefix, but maybe that's just me.

> [...] RPN syntax [...]

Oh, I actually think it does; it emphasizes function composition directly, and unifies it with sequencing statements in a single syntax!

> [...] formal methods [...]

Yeah, especially given the diversity of techniques under the formal methods banner, it's hard to get a good look at "the whole landscape." I think symbolic execution is getting to the point where it's workable for testing with a similar UX to property testing, just slower but with stronger guarantees (since all possible executions are taken, not just the ones that the RNG finds). Haven't actually built it yet though, so maybe Forth's flexibility will make it too hard, who knows.

If you wanna chat more about these things, I and at least one other person looking into formal methods + Forth hang out on #forth on Freenode.

Re:Forth: Stack-Manipulation Operators by remexre on Wednesday May 19, 2021 @03:15PM (#27213088)
Well, #forth on Libera.chat now :)
Re:Forth: Stack-Manipulation Operators by astrobe_ on Saturday May 15, 2021 @05:46AM (#27163815)
> What should you use if you want memory safety guarantees [...] but what should it be?

That's a bit off-topic but I would say any interpreter for a dynamically typed language that use a GC. That is to say almost anything but Forth (at least in the non-AoT category)

> Well, precisely what I want is to be fail-fast; standard Forth pointer arithmetic isn't

That particular point is often addressed by "correct by design". If it is really important not to off-by-one, you can make your array a power of two in size, and then access it through "gate keeping" words that bitmask the index. Or perform a quick check on the passed index using the same technique.

This is of course not a one-size-fits-all solution, but one useful thing Forth taught me is to think about things in their context. Specialization leads to simplification, generalization often leads to complications.

> You say, "The paradigm in Forth is to increase the skills of the programmer," which is to say, to be more difficult than necessary.

If you want to jump higher, you train yourself to jump higher by raising the bar a little every day, you don't lower the bar a little every day and pretend you've improved.

When you train yourself you build the muscles involved in the task you are training for. Eventually what was next to impossible last month is easy today. It is not difficult anymore.

"Difficult" is not an absolute value.

> I mean, you're probably right that most people using Forth today are doing it, like Apollo, not because it is easy but because it is difficult.

I did not really say that. I have no idea of how many people are using Forth in production, as a hobby, as a personal tool or as a puzzle game.

> But that's not what Chuck Moore was looking for, it's not what Elizabeth Rather is looking for, and it's not what I'm looking for either.

Chuck Moore has been developing Forth for 50 years. When you say he is not trying to make Forth more difficult, you are right but you are IMHO mistaken on the means. One of his attempts was "source-less programming" [1]. It was a dead-end, but IIRC the experiment led to ColorForth.

E. Rather is retired now I believe, but what she was looking for as a co-founder of Forth, Inc. is to sell Forth. That's a different objective, and that led co-founder C. Moore to leave the company in order to pursue his personal vision of Forth.

For one thing Forth, Inc. has pushed ISO-Forth because a standard gives credibility and makes things a bit easier for their customers, but Moore was quite not favorable to the principle of an "executable" standard and criticized it for being too complicated.

[1] http://www.ultratechnology.com/okad.htm

Re:Forth: Stack-Manipulation Operators by kragen on Sunday May 16, 2021 @02:30PM (#27175887)
> That's a bit off-topic but I would say [to get memory safety you should use] any interpreter for a dynamically typed language that use a GC. That is to say almost anything but Forth (at least in the non-AoT category)

Okay, but if you use, say, Lua instead of Forth, you're going from an interpreter that weighs 4 kilobytes to one that weighs 100 kilobytes. And instead of running 5 times slower than C (assuming a conventional threaded-code Forth), it runs 20 times slower (assuming not LuaJIT). And you've lost inline assembly. And you've lost compile-time metaprogramming (and if you want eval, which lets you get some of it back, the price goes up to 150 kilobytes). And you can no longer write real-time code because of the GC. And, even without real-time requirements, your programs randomly fail in limited-memory environments because they're allocating all the time, and sometimes they suffer atypical allocation fragmentation. And the memory requirements aren't just unpredictable, they're also large, because most of your memory is taken up by pointers, so even when your programs don't crash randomly in a low-memory environment they can't do as much as Forth.

There are other compact interpreted languages with other tradeoffs: BASIC, Smalltalk, XLISP, ZIL, Red, Elisp, and PostScript, for example. But they all have many or all of the above drawbacks.

So I don't think it's unreasonable to try to find a spot in the design space that's closer to Forth than it is to Lua and PostScript. GC and dynamic typing clearly aren't necessary for memory safety. The resulting language clearly wouldn't be the same thing as Forth, but that doesn't mean it has to have the kinds of orders-of-magnitude disadvantages listed above.

> If it is really important not to off-by-one, you can make your array a power of two in size, and then access it through "gate keeping" words that bitmask the index. Or perform a quick check on the passed index using the same technique.

This is going to the opposite extreme from "fail fast": unless the desired semantics is really an infinitely replicated buffer, like if you're using it as a ring buffer, this is deliberately producing incorrect results to mask the programming error of an out-of-bounds index. There are situations where that's a good idea (any possible way of handling such an error in an NPC script in a first-person shooter would be better than crashing the game, and ring-buffer FIFOs are a perfectly reasonable way to communicate with interrupt handlers and real-time threads) but there are also a lot of situations where failing fast is better.

> I have no idea of how many people are using Forth in production, as a hobby, as a personal tool or as a puzzle game.

My objection to your comment was basically that you seemed to be saying that anyone who's using Forth as something other than a puzzle game is doing it wrong.

Yes, doing things in a way that's more difficult than necessary is a worthwhile activity and makes you stronger. Football players get better at playing football by lifting weights. But they don't carry the weights with them onto the football field. That would make them worse at playing football.

> Specialization leads to simplification, generalization often leads to complications.

An important insight. You're right about the history of Forth, too.

Re:Forth: Stack-Manipulation Operators by liberalbias998 on Sunday May 16, 2021 @06:47AM (#27172739)
Messing around with FORTH rather than writing apps using it is also a mental trap, at least at the hobbyist level.
Re:Forth: Stack-Manipulation Operators by kragen on Sunday May 16, 2021 @01:33PM (#27175370)
Yeah, it's a common observation that more people have written their own Forth than have written useful programs in any Forth, whether their own or others'.

Part of the reason is that Forth is a low-level language like C, which makes it easier to implement, and harder to write programs in, than, say, Python or Rust. But, although there are considerably more C compilers out there than Python implementations, C doesn't suffer from this to nearly the same extent.

I'm no expert in Forth (though of course I've written a few quasi-Forths of my own) but I suspect that what's happening here is that, nowadays, the advantages of Forth over C or assembly just aren't great enough to counterbalance the disadvantages of using a minority language (like worse documentation, FFI, libraries, answers on Stack Overflow). Also, Forth compilers typically produce worse code than C compilers or assembly programmers, which rules it out for a lot of the applications where using a low-level language would be most tempting. That is, itself, a category that is smaller every year, in relative terms anyway.

The difficulty gets made worse by the conceited bullshit discourse about how Forth programmers are an elite distinguished by their superior intelligence, a story I sometimes hear even from people who don't write Forth.