Remix.run Logo
vdupras 3 days ago

As @addaon writes, your missing ingredient is immediateness. This is one of the most powerful, yet mind-boggling aspects of Forth. I encourage you to check it out, it will make you grow as a developer.

bxparks 3 days ago | parent [-]

I will definitely look into that.

If understanding this special IMMEDIATE mode is required to understand the Forth interpreter for something as fundamental as control-flow, it seems fair to say that Forth is not a simple language. It's not just an advanced programmable RPN calculator An RPN calculator has a program counter, which makes control-flow easy to understand.

In comparison, C is a high level language, but the mapping from C code to assembly language is relatively simple. (Yes, compiler optimizations against the C "abstract machine" can make the resulting code completely obscure. But if we turn off optimization, the resulting assembly code matches the C code fairly directly.)

kragen 2 days ago | parent | next [-]

Very much the contrary! In C all the syntax and control structures have to be built into the language; this makes C a much more complex language than Forth, because in Forth the language and interpreter don't even have to support things like comments, string literals, variables, and control flow. Because of immediate words, all of that can be built on top of the base language in high-level Forth, and almost always is. This allows the language itself to be enormously simpler.

It's also generally the case that in a native-code-compiling Forth the mapping from the Forth source to the machine code emitted is very much simpler and more direct than in C; as Virgil implicitly pointed out, the machine code is generally more or less in the same order as the source code, which in C it is not, and you don't have a bunch of implicit type conversions, ad-hoc polymorphic arithmetic operators, and so on. (It doesn't have to be more direct, since you can do arbitrary computation at compile time, but it usually is.)

addaon 3 days ago | parent | prev | next [-]

> If understanding this special IMMEDIATE mode is required to understand the Forth interpreter for something as fundamental as control-flow, it seems fair to say that Forth is not a simple language. It's not just an advanced programmable RPN calculator An RPN calculator has a program counter, which makes control-flow easy to understand.

"Simple" is not a well-defined threshold but rather a continuum, so it's hard to agree or disagree with this. I think it's perfectly valid to observe that Forth is more complex than an RPN calculator, though.

But think of it this way: An RPN calculator has two types of tokens, literals and symbols. When seeing a literal, it evaluates a push of that literal to the stack. When seeing a symbol, it evaluates an immediate call to the behavior associated with that symbol.

Forth adds exactly one more concept: non-IMMEDIATE words. Everything an RPN calculator can do can be done as IMMEDIATEs in Forth. But by adding one metadata bit to the symbol table (IMMEDIATE or not), and adding a threaded call to any non-IMMEDIATE words to the output code stream, Forth gains function definition, full generic control flow support, compiler extensibility, support for embedding domain-specific languages (just IMMEDIATE words that consume the interesting tokens), and more.

I don't know if this counts as "simple" compared to C, but it surely counts as "parsimonious." It's hard to think of a more cleanly defined single semantic change that adds as much power to a language.

(And of course in C, once you understand the language understanding the runtime library is mostly about understanding runtime behavior, some macros not withstanding; but in Forth, the runtime library and the language are conflated through IMMEDIATE symbols, so this separation is much less clear; totally accept that this could be considered less "simple", although in practice most Forths have about as many pre-defined IMMEDIATE words as C has keywords.)

vdupras 3 days ago | parent | prev [-]

The mapping to assembly of:

42 = if ."hey!" then

is much more straightforward than

if (n == 42) printf("hey!");

I understand that to the newcomer, it might not appear that way, but implementing a Forth is really eye-opening in that regard.

If I might allow myself a bit of promotion, I wrote https://tumbleforth.hardcoded.net/ as such an eye-opening process. It's less "gentle" than Easy Forth here, but it digs deeper.

bxparks 2 days ago | parent [-]

From the comments in this thread, it seems that to understand how Forth implements a simple IF-THEN-ELSE control-flow, I have to understand the difference between non-immediate and immediate words. I also have to understand the difference between outer and inner interpreter. And I have to understand how Forth generates snippets of machine code (where does that get stored? I thought Forth only has 2 stacks, does it also have a general heap?). Then understand how the THEN token goes back and patches the placeholder address generated by the IF token. And understand the difference between the parsing phase and the interpreted phase of the Forth interpreter/compiler.

But you are saying that the Forth version is simpler than C version which will kinda look like this after it's compiled (Z80 assembly code, it's in my head right now):

    ld a, (variableN)
    cp 42
    ld hl, StringHey
    call z, Printf
    ...
 StringHey:
    .db "hey!", 0
I find that hard to believe, but I accept that you believe that.
vdupras 2 days ago | parent [-]

It's fine, I can't force you in either. Maybe one day you'll dive into the subject. From the look of the comments here, you have all the hints you need.