Hacker News new | past | comments | ask | show | jobs | submit login
Understanding What It's Like to Program in Forth (2008) (dadgum.com)
84 points by pcr910303 13 days ago | hide | past | web | favorite | 55 comments





Forth is truly amazing in terms of what you can do with very little RAM and CPU power. It is quite possible to have a working interactive development environment and production implementation in only 4-8 kibibytes of RAM. In the world of small memory 8 bit computers it was amazing.

I think Forth can be useful to learn today, at least to stretch the mind to realize that not all programming languages have to be variants of Algol. It can also be a fun game as suggested by the article. I have enjoyed fiddling with it, including re-implementation of the language, and I can recommend it to others as a fun Bazaar toy. In addition, the book Starting Forth is amazing and still worth reading, because it shows that complex topics can be explained clearly and with humor.

However, for actual production use, I do not think the Forth programming language is ever going to return to widespread actual use. Our computer systems are far more capable, and if you have to program a tiny of computer, you would use a larger computer for programming instead of trying to fit your development environment within a few K of RAM. Nowadays the big constraint is development time, including the time to develop the resort code the time to read existing code to modify it. Forth doesn't do as well by those measures. When something is trivial another languages, but it's a puzzle to solve in Forth, that is not a good thing. That does not mean that Forth is a bad programming language, it is a very good programming language, but it is designed and optimized for a situation that is increasingly unlikely.


I have known an embedded engineer for probably a decade now.

He uses Forth.

I watched him write a tiny forth kernel in a Kb of RAM or so, then run a dictionary through it to get a brand new CPU bootstrapped in what could be as little as a day.

Very impressive. That system got an assembler / disassembler used to make new system words and other words fast.

Modifying a Forth system, and I say that due to how Forth is really an environment that becomes capable of the application, depends lot on who made it and what one can know of their thought process.

What I thought interesting about this guys approach us a fairly pure Forth is used to bootstrap. Then one that is a lot more comfy gets made for the longer get term.

Once that stage is reached, and there are good tools, assembler / disassembler, monitor, word viewer, etc... Forth can be made less of a puzzle.

One can even add other syntax, an interpreter, if desired.

The product is more like a dev system, self hosted and capable.

I find it all compelling, but as you say, much different.


That's a plausible use case. Question is, if the engineer goes away, how long would it take to replace him? Languages do not take too long to learn, but Forth programs are often hard for others to read.

I think most people prefer cross-compilation on small systems for professional use.

If you are doing it as a hobby, ignore everything I just daid. I see the fun in it, and applaud it. Enjoy!


Again, depends on how the Forth is done.

Counter question:

Given the obvious savings, why not invest some of that into a second person to retain a clear advantage?

Do what most people prefer and get results that most people get.

There are other ways to success.


> There are other ways to success.

Absolutely. I've played with Forth many times, and enjoyed it. But when it comes to building systems that people pay to develop & maintain, I haven't found any use cases recently. No doubt there are some, but I suspect they're much less justifiable today.


Let me expand a bit. Was tired when I wrote the snarky comment.

In a typical corporate environment, yes. Your thoughts ring true.

It is a big world outside that scene, and many playing there can benefit from Forth. That is the utility. People who grok Forth flat out solve hard problems like no other.

Tool for that toolbox, essentially.


Putting pay under emphasis does not add any value to your earlier comment.

It's a big world out there. Guess I shall reply with same.


To be fair, that's kinda the use case for Forth in the modern world: embedded development, single, capable developer, bootstrap something minimalist up and get it stable, create a DSL to solve the problem.

Seems like a worthwhile enough capability to make Forth worth learning.

Absolutely. I think it is.

And have been taking those steps myself.

It works how getting Linux onto something does. The benefit is much smaller initial resource footprint.


Ages ago when I was doing mostly assembly, I was introduced to Forth. After few tries I've decided that for it was more of a mental exercise and nothing good will come out of it. I've felt that it was actually more easy for me to program in machine codes (I've done some fair share of it too).

No not my cup of tea but I guess other people put it to a good use.


I mentioned the book "Starting Forth". You can get a lightly-updated version of the book for free here: https://www.forth.com/starting-forth/

There are other books about Forth, but this one is really its own thing :-).


Sometimes I feel discussions on HN always go into the weeds with people going back and forth while losing sight of reality.

I used Forth professionally for well over a decade. I made truckloads of money with the end products. One of the companies I was involved with, which was 100% Forth, sold for just south of $20 million USD.

Customers don’t go to Home Depot to buy drill bits. They go there to buy holes.

The world could not care less if solution A was coded in C, solution B in Forth and solution C using a keypad to enter machine code. All they care about is the best solution at the best price.

I have always felt that the distinction between experienced and less experienced engineers is that those with experience focus on what matters —delivering value to customers— while the less experienced folk needlessly burn time on theory, languages, code editors and other stuff.

25 years ago I made over $500K with a small 6502 embedded system used to help control lab equipment during cancer research lab tests. I made this box with a 6502, a couple of UARTS, a 2x16 LCD display and four pushbuttons. The box sold for $2,000 each. Sold piles of them. The code was written in Forth. The buyers could not care less. Neither did I frankly. I love Forth, but, at the end of the day, it’s just another tool.

Customers want holes.


Customers want holes.

At one level I entirely agree. But at another, when it comes to the holes they drill in the bathroom to hold the toilet roll, and the holes they believe were drilled in the pickle fork holding the wing onto the 737-max, they feel very differently about the holes. The first one, They don't much WANT The toilet paper falling off, but its kinda meh. The second one, being told uncle fester was given a hand drill from the barn and told to ream 'em good and deep doesn't fill anyone with joy.

Customers want the right kind of holes.


These kinds of analogies are not intended as precise engineering or business definitions. They are simple thinking tools used to deliver a point.

If you are in the business of delivering holes for aerospace customers your focus should be on delivering the kinds of holes they need. At the end of the day, they still only care about holes. That’s what they are buying.

Most customers using a software-based product could not care less if it was created using vim and python or toggle switches and machine language. They are after the solution to a problem; that’s all they care about.

Our customers don’t buy language features, compiler flags, stacks, lists, dictionaries, classes, variables or functions.


Aphorisms have to be tested. "Did you drill these holes to required tolerances and was the material tested for overheating and delamination" is not that far off "did you use llvm or gcc and what compile time options did you use"

You missed the point. Forget about holes and think about what customers are looking for. The answer is a solution to a problem. Nobody buys a car because they are after one with body panels made with hydro-forming rather than a sequential press. They are after a vehicle that addresses the transportation needs they might have. They could not care less if it was designed using SolidWorks, Fusion 360 or Siemens NX.

They are looking for a hole.


> Customers want holes.

So what? The customers of Forth are computer programmers, not carpenters.

And I, for one, go to Home Depot to buy drill bits (not holes).

If I wanted to buy holes, I'd hire a handyman.


Every single one of your statements is wrong.

You are missing the point, which, frankly, is exactly the reason business types don't want engineers anywhere near customers. It took me lots of professional coaching and years of experience to stop seeing our products as an engineer.

> The customers of Forth are computer programmers, not carpenters.

Unless the activity being conducted is an academic pursuit, the tools (languages, compilers, editors, etc.) are utterly irrelevant. The customer isn't the programmer. The customer is the one exchanging something of value (typically cash) for a solution to a problem, and usually something that delivers value beyond the mere cost of the thing. The customer is NOT the programmer creating the solution. Far from it. Any software engineer who honestly believes they are the customer ought to be fired as quickly as possible.

> And I, for one, go to Home Depot to buy drill bits (not holes).

The only way that would be true is if you purchase drill bits for display and never use them. If they are used, even once, their purpose is to make a hole.

In other words, the solution one is seeking isn't "I need to own a drill bit", but rather "I need a hole" or "I know I might need a hole in the future".

> If I wanted to buy holes, I'd hire a handyman.

Only if you'd hire a handyman to just come over, drill a hole and go away. Which, of course, would be a very rare and even bizarre use case.

Again, this is missing the point of my parent comment: The discussion of tools and languages often diverts into truly irrelevant minutiae that has absolutely nothing whatsoever to do with what a business exists to deliver: Solutions to customer's problems.

To get back to Forth, it would be like objecting to the use of the language on the grounds that traditional Forth isn't object oriented (in a traditional sense) when all you are trying to build is a simple irrigation controller (or whatever). This is getting lost in the weeds for no reason at all.

What would be a valid objection to using Forth? Oh, there are many, and this coming from someone who loves the language and used it professionally for about a decade. The simplest one I can offer with my business hat on is, the pool of qualified programmers isn't large. Another would be that the library ecosystem is dwarfed when compared with something like Python.

How are these different from objections like "it isn't typed"? Simple: These are real impediments to the delivery of solutions customers need. All the CS theory stuff is irrelevant. The proof is that the language enjoyed commercial application for a very long time, with everything from embedded systems to sophisticated industrial controls and more being developed using Forth. As I said in my parent post, I was involved in a company that sold for USD $20MM and the product was 100% done in Forth. BTW, the first thing the buyers did is convert the codebase to C. Because by that time finding Forth programmers was starting to become very hard.

So, yeah, I would not use Forth or APL today (both languages I love) because this would be a bad business decision. On a technical level you can do anything with these languages, despite whatever limitations they may have. From a business perspective, if you advocate for the customer rather than the engineers, when contrasted to other options, they are not the best idea.


I experimented with Forth with my Apple II (I had serial number 71, one of the early ones!). I liked the interactive bottom up REPL coding style, but I found it difficult to read code that I had written the week before.

After playing with Forth, I bought Pegasus Lisp for the Apple II and for me Lisp hits a sweet spot of concision, bottom up REPL development, and readability.

That said, I can see why some people like Forth if they are very experienced with it and can get things done quickly.


Reading the book 'Thinking Forth' will help you write legible code.

I second this, IMO to a first approximation reading that book is the way to understand programming in Forth.

http://thinking-forth.sourceforge.net/


I played with something called GraFORTH on my II+ clone.

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.

- - - -

Also, see "Compiling to Categories" wherein Haskell is converted to a kind of point-free form very similar to Joy... https://conal.net/papers/compiling-to-categories/


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.


That's why OForth was invented.

http://www.oforth.com/


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.

See, for example,

http://zedcode.blogspot.com/2011/02/forth-parameter-stack-na...


I've since discovered that the latest Forth standard, Forth-2012, includes an optional set of Forth words to support local variables.

Gory details here (warning, spec-ese): http://forth-standard.org/standard/locals

I don't know how widely these are supported in Forth implementations.


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.


> 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 !)

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:

    : MY-NAME {: var1 var2 var3 :} var1 var2 + var3 * ;
Which is exactly what was desired.

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.


When we did forth(like) languages in computer science kindy (back in the 70s and 80s) the lecturers used to smirk because real languages like ADA took 20 passes and built huge models and were therefore more betterer and gooderer for some value of complex. Having the thing function as a stack machine in Reverse-Polish meant you didn't have to parse anything, it was basically already there for you sitting on the top of the stack (dock of the bay) watching the PC come in.

I think the smirk was misplaced. What they needed to do was talk about this in comparative language classes and about why people drive to small(er) languages, and fitness for purpose, and what kind(s) of things happen when you force people to think this way, vs why you drive to COBOL and what you force people to think and do, when you go there. They're all turing-complete. They're all capable of expressing the same things, at some differing level of complexity and cost, in some variant of machinery.


If it were up to me to design curricula for CS, I'd do the entire thing in Ada, with the exceptions of "Data-Structures & Algorithms" being in Lisp and "Machine Architecture" in Forth — both of these classes would have a "Compilers & Interpreters" prerequisite where they would be provided the specification-files for Forth and Lisp interpreters/compilers which the students would implement, and that is what they would use in the aforementioned classes — not only would having the curriculum in these less-popular languages help emphasize that the important part is the underlying-theory (rather than hyped-up language-of-the-month), but making its prerequisites actually matter and introducing an element of software-maintenance would do a lot to produce good graduates.

That is an unfair comparison. Forth is designed to run on metal, with no os, no stdlibc, no vector. Can any of other language do that?

In fact, by just adding first class function, retro forth makes the code way easier to read.


> That is an unfair comparison. Forth is designed to run on metal, with no os, no stdlibc, no vector. Can any of other language do that?

Not much of a stretch with C, really - there's a reason its used to write operating systems ;) (the standard library is mostly about OS interaction which is irrelevant in the bare metal/no OS context)

On some systems, C requires a minimal library for compiler-emitted intrinsics (e.g., for 16/32-bit multiplies on 8-bit systems, for floating point, etc).


I think you misunderstand. When the parent says "designed to run on metal" they don't mean "designed to be cross compiled to produce binaries that run on metal", they mean "the compiler, editor, etc. are all running on metal."

That pretty much describes everything in the first 20 years of computers (1945 to 1965) as well as everything in the first 10 years of personal computers (1975 to 1985), plus a fair bit of stuff that came after that. Even once operating systems were invented, they took a long time to get adopted.

Just about every PC before the IBM PC came with a BASIC interpreter with a built-in editor and some kind of storage interface, running on the bare metal. So did every IBM PC up to at least the PS/2. CP/M did provide a BIOS and BDOS, but they really were very basic, providing little more than a convention for talking to the terminal and avoiding overwriting disk sectors some other program was using. MS-DOS didn't do much more, especially in the early years. So, I'd say that every programming language implementation from that time qualifies, including Microsoft BASIC-80 (originally Altair BASIC), VisiCalc, Turbo Pascal up to version 3.0, Apple Integer BASIC and AppleSoft BASIC, the 6809 BASIC Microsoft developed for the TRS-80 Color Computer, ZX-81 Sinclair BASIC, BDS C with the RED editor, and so on; not to mention the first FORTRAN compiler (and up through at least FORTRAN III), LISP 1.0, COBOL, and assemblers for pretty much every machine. Also, and perhaps most notably of all, Smalltalk on the Alto, Mesa and Cedar on the Alto, and Oberon on Lilith and its successors (including a version for the IBM PC).

So, Forth is far from unique in being a bare-metal IDE without requiring an OS. It's just unusual in still surviving in that form.


One of the very first things I thought of when I heard about WASM is, "Oh.. It would be so fun to build FORTH on that". I never got around to it (yet) and from a quick search, there are already a ton of implementations. But I think this is one of the things about FORTH: it's not just that you can run it on the metal. It's that you can easily build a FORTH environment for pretty much any metal you can think of. Bootstrapping from absolutely nothing to a very productive environment is really an empowering feeling. I always thought it was a shame that there are so many people who have made a Lisp, but so few that have made a FORTH. I kind of feel like both of these tasks should be required material in school... Though I only have nebulous feelings about why it is valuable... Not sure I could explain it to someone who hasn't done it already.

I haven't done any serious Forth programming in 30+ years, but every time I see a limited resources programming environment, I immediately get the urge to implement Forth on it. "This new game is going to include a DCPU-16 virtual computer you have to program? Great, all I need to do is implement Forth on it..."

Luckily for my already very limited spare time, so far I've managed to resist this urge.


> Not much of a stretch with C, really - there's a reason its used to write operating systems

C is a terrible language for Operating Systems.

No modules, weak-typing, and full of gotchas and pitfalls — plus, it's answers like this that ignore the history of OSes: the Burroughs was written in Algol, Multics was written in PL/I, and VMS in BLISS (plus other languages, thanks to the design/architecture of the CLE).


http://forth.works/examples/AddingVectors.retro.html

Here is the retro forth version of adding vector that I was talking about.

Tri@ apply function (2+x)on top 3 items. So get address of the 3rd element of c b a ..


> Can any of other language do that? Ada can. The biggest Ada vendor actively pushes Ada-as-an-embedded-language, which is nice in some ways, but in others it can make it seem like Ada is only for embedded.

It's an assembly language for a 2-stack VM. It's never going to be easy but it is hella-fun to play with.

Ran across this post years before I started playing with Forth (recommend the whole blog, he's a great writer).

Later I was going through a Forth phase and came back to it to tackle the puzzle it poses, here's my take on it:

https://mnemnion.github.io/blog/2014/01/12/those-dadgum-adde...

I hope this illustrates the raw power of the Forth paradigm a bit better than the OP.


I was following along until the author stopped explaining new syntax.

"over", according to forth.com, copies second item to top... ( n1 n2 — n1 n2 n1 )

But I get lost with >r, r>, r@, and !


They move top cell from stack to return stack or vice versa. r@ and r! are effectively equivalent to r> @ and r> ! ... or was it r> @ >r and r> ! >r

r@, r>, >r are operating on return stack, another stack that's dedicated to return addreses and control structures (intermediate states of for loop for example)

James Hague has a lot more Forth experience than I do. But I would initially write this function something like this:

    variable v1  variable v2  variable v3
    : vadd v3 ! v2 ! v1 !
      3 0 do  v1 @ i cells + @  v2 @ i cells + @  +  v3 @ i cells + !  loop ;
Or, alternatively:

    0 value v1  0 value v2  0 value v3
    : vvadd to v3 to v2 to v1
      3 0 do  v1 i cells + @  v2 i cells + @  +  v3 i cells + !  loop ;
I mean, yes, you can make it a puzzle. But you don't have to. These versions will be slower than Hague's first working version and probably the second too, but all of Hague's versions are slower (with any Forth compiler I've used) than what GCC will give you for the C version. And what GCC will give you for the C version, even if it improbably manages to vectorize, is also a lot slower than what you'll get if you do your vec3 math on your GPU. So maybe when it comes time to rewrite the naïve Forth version above, because it is slow, you should just code an SSE assembly routine and skip the manual loop unrolling at the Forth level, or restructure the whole system to do the vector math on the GPU.

Or maybe not. James Hague is a smart guy and he's shipped a lot of code, in assembly, C, and Forth. But I'd like to understand where my reasoning is going wrong, because I can't see it.

My perspective on Forth is that it's the thinnest possible layer on top of raw machine code that gives you all the fundamental mechanisms for improving your leverage. You need structured control flow, so it has IF ELSE THEN, DO LOOP, BEGIN WHILE REPEAT, and a couple of others. You need expressions, so it has an operand stack. You need procedural abstraction, so it has a return stack, which also serves to store loop counters. You need labels both to make your code readable and so that you can patch your code without overwriting parts of it with JMP instructions to wherever you have free space to put the patch code, so it has words. You need metaprogramming, so it has IMMEDIATE. You need run-time polymorphic procedure dispatch, so it has ', EXECUTE, DEFER, IS, and CREATE DOES>. You need namespace management, so new definitions hide old definitions with the same name, and you also have wordlists.

The problem with all these wonderful mechanisms is that they can become an attractive nuisance, as you futz around trying to find the perfect way to express your intent in the smallest, clearest amount of code, navigating the tradeoffs imposed by the sparse toolset. But you don't have to do it that way. You can just write simple, straightforward code that gets the job done with no fuss, and move on to the next problem. Forth lets you do that, too.

That said, I find it easier to get my code working in assembly language than in Forth, and I don't know if that's because I don't know Forth well enough, because Forth is inherently less practical than assembly language, or just because Forth somehow fails to fit my brain. My guess is that I fall for the attractive nuisances too often and waste my time with unproductive futzing. And that's what this blog post looks like to me.


The problem with using global variables (like this) is that it's not thread-safe. Many Forth systems supported multi-threading (each thread gets its own pair of stacks), even on resource-constrained systems. If you're going to have names like this, named parameters AKA local variables are a better solution.

Those aren't global variables; they're just variables. They are statically allocated, which you would think would lead to the problems you describe. But multithreaded Forth systems traditionally use cooperative rather than preemptive multithreading, and since VADD doesn't invoke any words that can yield to another thread, it can be assured that no other instances of VADD will mess with its variables while it's running. So that problem doesn't actually exist unless you're using something like GForth's experimental pthreads interface. In that case you could define the variables as "user variables" (TLS, in more widespread terminology) as well as the possibility of making them stack-allocated variables as you suggest.

I like stack-allocated variables, and they'd be the obviously correct solution in a language like C; but ANS Forth doesn't have them (oh, I see they've been added in Forth-2012; thank you!), they make it harder to factor words, and some of the other problems they solve in Algol-family and Lisp-family languages have other solutions in Forth, so the case for them is not as strong.

Also, traditionally, multithreaded Forth systems were multiuser; each task had its own dictionary. So a fourth way to avoid the problem (in addition to procedure-local variables, cooperative multitasking, and user variables) is to load the definitions above after spawning the potentially interfering task rather than before.


"Better than Sudoku? Yes."

I would have went with riding a 5 EUR motorized kids skateboard without any protection and never looking back. But that's a good analogy too.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: