Live data from Hacker News

Easy Forth (2015)

skilldrick.github.io

61–70 of 128 posts

Re: Easy Forth (2015)

#61
post #54
post #49

Earlier quoted context omitted.

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.

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 th…

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.

Re: Easy Forth (2015)

#62
post #48
post #46

Earlier quoted context omitted.

> So there must be something else fundamental in the Forth interpreter that I don't understand. The missing bit is IMMEDIATE mode. Words can be tagged as IMMEDIATE, which means that they get executed at compile time (or parse time, for an interpreter), rather than a call to them getting compiled (executed at run time, for an interpreter). IF/ELSE/THEN are then "just" IMMEDIATE mode words -- but you can add your own.…

Ah I see, this is peeling back a few layers of obscurity about the Forth interpreter for me. Let's stick with a Forth interpreter because that seems easier to think about for me. Are you saying that the Forth interpreter is a 2-pass interpreter? Or does the interpreter go into a special IMMEDIATE mode upon hitting the IF keyword, then it just consumes subsequent tokens without doing any dispatching, until it hits the…

Oh, and because I didn't address it directly in the longer answer...

> Does the interpreter hit the WHILE token, goes into IMMEDIATE mode, remembers the location of the WHILE, then dispatches all the subsequent code, until it hits the REPEAT token, then branches back to the WHILE?

Yes. The beauty is that, in the context of a threaded code compiler (which, again, I encourage you to use as your default model for Forth, even though other options are possible), WHILE just pushes the address of the output code stream onto the compile-time stack. REPEAT expects the address to be on the compile-time stack and compiles a conditional jump to that address. This obviously and trivially provides support for nested control structures of all types; as long as the word that pushes compile-time data onto the stack is correctly paired with the word the pops it, we have stack semantics for nested control, which is exactly the expectation. So while your description is completely correct, "remembers" is almost trivial here -- "puts data on stack" is the primitive operation of remembering anything in Forth, and that's all that's needed here, no fancy data structures or look-aside buffers or anything. (Note that the compiler does require at least two non-stack data structures, the symbol table and the output code stream, but those reflect real domain complexity.)

Re: Easy Forth (2015)

#63
post #7

This has showed up here a few times before (example): https://news.ycombinator.com/item?id=10634918 I'm always interested in hearing people's reactions to Forth though and every now and then you get a cool new story on these threads, so I'm not complaining.

Right. And once again, you’ll also notice that no one is actually coding anything useful in Forth.

[deleted]

Re: Easy Forth (2015)

#64
post #54
post #49

Earlier quoted context omitted.

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.

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 th…

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

Re: Easy Forth (2015)

#65
post #52

Earlier quoted context omitted.

You're describing the outer interpreter in interpretation state; Forth control flow words don't work properly in interpretation state, only in compile state. They're immediate words, so they execute at compile time instead of run time, so they can do arbitrary things to the code being compiled. Here's Mike Perry and Henry Laxen's implementation of the main control-flow words from F83, which is an indirect-threaded Fo…

Thanks for this expansion of the ideas involved. My question here is what does the COMPILE word do? What is the state of the VM / compiler / repl or whatever after it encounters that word? That "IF" is implemented in terms of other more fundamental operators is fine, but can we write a program that just uses the fundamental operators that demonstrates IF-like behavior but doesn't introduce any intermediate words?

Actually compile is not an immediate word (at least in F83). I was wrong about that. Here's the F83 definition:

    : COMPILE   (S -- )   R> DUP 2+ >R   @ ,   ;                     \ COMPILE     Compile the following word when this def. executes
This takes its return address (which points to the following word in the colon definition that called it), dups it, adds 2 to it, and puts that back on the return stack as its new return address. Then, it fetches from its original return address with @ (thus getting the address of the word that followed it in the colon definition, such as ?branch in my if example above) and compiles it with , into whatever is currently being compiled. Then, when it returns, having added 2 to the return address means that we don't actually execute ?branch or whatever; we've skipped over it.

So it doesn't change the state of the interpreter at all!

I think you're asking if you can use things like ?branch usefully without writing any immediate words. In some sense I think the answer is yes in F83 but no in standard Forth. I think you can put a code sequence like ?branch [ here 0 , ] into a colon definition to do what if does, and then later on say [ here swap ! ] to do what then does. I just typed this definition into F83, and it seems to work†:

    : is3 3 = ?branch [ here 0 , ] ." yes" [ here swap ! ] ;
You could sort of think of if and then as being macros for ?branch [ here 0 , ] and [ here swap ! ] respectively (although I'm omitting the checks they use for proper control structure nesting).

On the other hand, this is only possible because [ is an immediate word, and because ?branch is exposed, and happens to take an absolute address in the next word in the colon definition (as opposed to a byte delta or something). As it happens, exactly the same definition of is3 appears to work in GForth 0.7.3 and PFE 0.33.71, but it definitely will not work on, for example, any native-code-compiling Forth.

The standard way to invoke things like ?branch is using if, while, and so on. And you don't have to define any immediate words to do that, either.

______

† By "work" I mean it seems to behave the same as

    : is3 3 = if ." yes" then ;

Re: Easy Forth (2015)

#66
post #60
post #52

Earlier quoted context omitted.

You're describing the outer interpreter in interpretation state; Forth control flow words don't work properly in interpretation state, only in compile state. They're immediate words, so they execute at compile time instead of run time, so they can do arbitrary things to the code being compiled. Here's Mike Perry and Henry Laxen's implementation of the main control-flow words from F83, which is an indirect-threaded Fo…

Wow, that's going to take some time and effort to digest, but thank you. Yes, I think control-flow is easier to understand in assembly language than the implementation you showed in Forth. :-)

Happy to help!

I think you're mistaken about assembly language.

In assembly language, the thing that plays the role of these definitions like if and then and ? is the assembler's symbol table and relocation logic, which goes back and changes your jump instructions (etc.) to jump to the places where it finds that your labels have been defined to point. Typically this involves things like hash functions, hash table collision resolution, various operand encodings for things like short jumps and long jumps, and so on.

Although you can write an assembler that does all this in an afternoon, I don't think you will ever find an assembler whose implementation of all this functionality is easier to understand than the above 30 lines of code. It might be easier to understand per line of code but there will be a lot more lines of code to understand, like 10× or 100×.

Re: Easy Forth (2015)

#67
post #14

Forth is very enjoyable, and it's always exciting to see someone new discovering it, but it has three big problems. The first is a technical problem: the forte of Forth is self-hosted developer tooling in restricted environments: say, under 256KiB of RAM, no SSD, under 1 MIPS, under 10 megabytes of hard disk or maybe just a floppy. In that kind of environment, you can't really afford to duplicate mechanism very much,…

The description of F83 sounds interesting - any way I can see it in action, or use it on my own?

Sure, I git cloned my copy from https://github.com/ForthHub/F83, and it runs fine under DOSBox. If you have Git and DOSBox installed, I think you can just type

    git clone https://github.com/ForthHub/F83
    cd F83
    dosbox .
    f83
    : fish 0 do i . ." fish" cr loop ;  7 fish

Re: Easy Forth (2015)

#68
I think Forth is well worth learning just for the mind expansion.

It also makes a nice starting point for building your own interpreters / designing your own languages.

Re: Easy Forth (2015)

#69
post #28

Earlier quoted context omitted.

Reading lines from a file and handling the strings in memory is what made me stop using it after a 3rd day of advent of code one year. I simply couldn't find a good solution, without a massive excursion into how to use the pad. Such a supposedly simple thing like reading a complete line from a file, yet it stopped me completely. Of course I could have "cheated" and put the input right into the program, but I wanted t…

I think mostly learning Forth is like learning any other programming language (or, better said, programming environment): you learn by doing it. Books can be a useful complement to practice, but practice is how you learn to do things. You can't learn to do things by reading. As for string handling, in my limited experience, string handling in Forth is a lot like string handling in C; you have to allocate buffers and…

I think there is another problem for me: The last time I have done any manual memory management a la C, before using Forth was some >10y ago. And immediately the next question would pop up in my head: "What if that line is longer than 128 bytes? Is there no general function to read a whole line?" And I guess then I would reinvent the whole machinery to read a whole line, determining at which byte the newline appears. And then I would have doubts like: "Uh, but what if someone puts some unicode characters in there?". While actually all I wanted was to read a single file, to get working on an AoC puzzle.

So I think I lacked the manual memory management basics as well at that point, and any haphazardly implemented hack like "assume the longest line is at most 128 ASCII characters long" would not have made me happy with my code.

Re: Easy Forth (2015)

#70
post #31

Earlier quoted context omitted.

>>Forth is a million languages that solve almost nothing." :-P That brings us to the question, when it was invented and people did use it. What kind of problems were they solving with it?

Forth started as Chuck Moore’s solution to the problem of how to bring up an interactive programming environment on hardware with limited memory. The base of the system used a small number of primitives written in assembler or machine code upon which more complex functions were built. The genius of the system was that you could easily bring it up on different hardware by translating the primitives, which was quite he…

> Nowadays Forth is probably most useful on embedded systems.

Nowadays almost nothing is written in Forth. That's the problem with Forth. Even on 8-bit Arduino microcontrollers with 2kiB of RAM, we write programs in C++ (with a little bit of C, on top of hand-coded AVR assembly).

Post reply on HN