Most active commenters

    ←back to thread

    210 points JoeDaDude | 12 comments | | HN request time: 0.015s | source | bottom
    Show context
    tombert ◴[] No.42207795[source]
    Forth has been something I've wanted to learn for years now. It seems weird to me that for most stuff in old computers, you have the option of "assembly" if you want your program to be fast, and "BASIC" if you want your program to be slow, but Forth lingers along as the "medium speed" language, despite at least looking pretty high-level.
    replies(7): >>42207863 #>>42207931 #>>42208026 #>>42209557 #>>42210559 #>>42213720 #>>42213966 #
    1. FuriouslyAdrift ◴[] No.42207863[source]
    Forth was the standard language for hardware development and drivers (along wiht C) for a very long time (still is used all over). Stack based.

    https://www.forth.com/starting-forth/

    replies(2): >>42207922 #>>42208085 #
    2. tombert ◴[] No.42207922[source]
    Yeah I knew that, that's why I've found it interesting.

    It looks a lot more high-level than something like C, but is used in similar spaces, which makes me wonder why it never really became more popular.

    replies(1): >>42208622 #
    3. DougDroogSharp ◴[] No.42208085[source]
    The reason I used FORTH to code ChipWits in 84 was twofold. First, it allowed me to develop natively on the 128k Mac rather than buying an outrageously expensive Lisa. Second, I knew I was going to port it to other micros and FORTH was usually one of the first languages implemented on new computers. I eventually ported it to Apple II and C64 and about 70% of the Mac code was easily portable.
    replies(2): >>42209979 #>>42212968 #
    4. selvor ◴[] No.42208622[source]
    It may look more high-level than something like C, but it is actually no more high level than a macro assembler with registers eliminated. As there's no syntax tree, essentially every word that is parsed from the input stream is replaced with a subroutine call. So the resulting FORTH code is nothing but a series of subroutine calls.

    In my experience quite often writing in assembler is easier than FORTH unless you have a strategy and self discipline, which when acquired makes one a whole lot more productive than using assembler, and arguably more so than a high level language. There're no pointer arithmetics, no rudimentary type checking, not even an array type (you have cells and addresses). There is nothing that throws an error except things like stack over/under-flow checks, and if you are lucky your code will crash. If not debugging can be tricky. Stack imbalances won't be reported/checked for you, there is no bounds checking for anything (not even structs). But there are conventions/strategies to prevent these kinds of bugs from happening that one needs to either discover for themselves, or find out in books (the book Thinking Forth helps, but is not enough I would say).

    Working with the stack can be more difficult than with registers, although the latter can be easily simulated with variables, which is often frowned upon. But words like CREATE ... DOES> enables meta-programming that helps with generating code with code, and makes it quite powerful, but can make your code complicated to reason about (see the concepts of compilation vs. execution vs. interpretation semantics described in ANS Forth).

    In the end the appeal of FORTH for me is in its "simplicity" (but nowhere any ease of use as it requires mastery of laying out code in memory as you would do in an assembler without any help from the language itself), its overall philosophy (see Chuck Moore's A Problem Oriented Language) on focusing on the nature of the problem rather than thinking in terms of the tools/language given to you (build a "language" for the problem), and providing solutions with as minimal "cruft" as possible.

    replies(3): >>42209019 #>>42211960 #>>42212494 #
    5. drivers99 ◴[] No.42209019{3}[source]
    Anything that you say is missing can be added. I'm no expert but when I had some confusion about the stack I would create a word that did something in a balanced way, test it quickly, and use that word instead to build on. Forth makes it easy to climb the levels abstraction quickly.

    The method that it uses to interpret/compile a word varies by implementation. Subroutine call is just one of them.

    replies(1): >>42213578 #
    6. guestbest ◴[] No.42209979[source]
    Thanks for opening the source code to another generation
    7. sph ◴[] No.42211960{3}[source]
    > Working with the stack can be more difficult than with registers, although the latter can be easily simulated with variables, which is often frowned upon

    Yet every time I hear experienced Forth developers recommending to use more variables, and that newbies tend to eschew them, making the code much harder to read and understand than it is necessary.

    You become a true Forth programmer once you go past the code golf and stack juggle phase.

    8. cess11 ◴[] No.42212494{3}[source]
    Factor addresses some of these concerns and instead of giving you a bare metal REPL you get a Smalltalk-like image: https://factorcode.org/

    It's rather neat.

    9. ekidd ◴[] No.42212968[source]
    Forth really is one of easiest languages to build up from bare metal, piece by piece. And when you get it working, sure, it's arguably weird, but it's far better than where you started.

    My personal inclination is to make the longer jump, and go straight for a deeply rudimentary Lisp. There's a trick where you start off with Lisp macros that expand to assembly, and I once knew someone who got it working for new hardware during a 10-hour plane flight. It's a slightly longer climb than Forth, but even a primitive Lisp is nice.

    However, the deciding factor here really is the 6502 and 65C02 microprocessers. You really want at least 4 general-purpose registers for a primitive Lisp dialect, and that's pushing it. And the 65C02 basically has 1, or 3 if you clap your hands and believe. Even C is a serious challenge on a 65C02.

    But Forth thrives in that enviroment. You only need a stack pointer and enough registers to do exactly 1 canned operation. So: victory to Forth.

    And wow, I wish I had seen Chipwits back in the day. I was a massive fan of the Rocky's Boots logic game, but Chipwits never showed up in our neck of the woods. Thank you for open sourcing it!

    replies(1): >>42213944 #
    10. selvor ◴[] No.42213578{4}[source]
    > Anything that you say is missing can be added.

    For sure, it can be extended indefinitely. It's good that you made that clear. You can add a C compiler if you like (see Dusk OS) even, or a generic BNF parser generator (see Bradford Rodriguez) into "the language". Anything that you devise for code correctness, and static analysis can be added. My points about the lack of these language features were towards the previous comment about FORTH looking "more high-level than C". These are definitely major shortcoming for an inexperienced programmer to be able to do anything reasonably complex in FORTH (similar to using assembly).

    > ... I would create a word that did something in a balanced way, test it quickly, and use that word instead to build on. Forth makes it easy to climb the levels abstraction quickly.

    I would say any programming language with functions provide the same ease by that definition. That is, in each you can write a set of functions, than use them to compose higher level functions ad infinitum until you create a DSL as essentially a collection of functions for your problem space. Although doing so in C-like languages syntactically it may look more like Lisp than FORTH. In FORTH it looks more concise, and reads left-to-right thanks to it being accidentally a "concatenative language" with point-free notation and combinatory calculus roots. A great example of this being formalized is Joy by Manfred von Thun.

    So I think what makes FORTH unique is more of the philosophy around it (again, see POL book by Chuck), which is a kind of zealous struggle for simplicity, but not easy, and keeping the problem in focus and not ignoring what's inside the boxes you build upon. You could say it's a panacea for yak-shaving if done right. Concrete examples for what FORTH does away in its search for simplicity and avoidance of yak-shaving, here are a few:

    - no in-fix notation or ASTs: computers understand post/reverse-Polish by default by virtue of them being a stack(/register) machine, the programmer can do this work without major difficulty, - no filesystem: blocks and table of block indices are enough most of the time, - no floating point: fixed point is good enough for most problems, - no classes, arrays, structs: you can build your own constructs suited for your problem as they are needed, there is no one size fits all solution,

    Etc. The list goes on. Some of these are added into the so-called standards, but some argue trying to standardize FORTH defeats itself.

    > The method that it uses to interpret/compile a word varies by implementation. Subroutine call is just one of them.

    I used a vague terminology there, and should have clarified by saying "regardless of the threading model". What I meant was that effectively the FORTH source compilation is a one step pass that converts string tokens into series of "subroutine" (conceptually) calls that map 1:1 with the source code (homoiconicity); direct/indirected threaded, token threaded, or subroutine threaded all effectively is laying out/interpreting a series of subroutine calls, including those in FORTH CPUs like Harris RTX or GA144.

    11. acegopher ◴[] No.42213944{3}[source]
    Do you have any texts/websites/papers that would allow one (me) to learn about "deeply rudimentary Lisp" and how to create one? I am especially interested in learning why 4 general-purpose registers are important and other lower-level details like that.
    replies(1): >>42215429 #
    12. ekidd ◴[] No.42215429{4}[source]
    Sure! One fantastic starting point is Lisp in Small Pieces, which shows you how to build multiple different Lisp interpreters, and then several increasingly fancy Lisp compilers.

    The trick with a macro-assembler that uses Lisp macros to generate assembly was basically folklore when I learned it, and I haven't seen it fully fleshed out anywhere in the literature. For a tiny chip, you'd run this as a cross compiler from a bigger machine. But you basically have Lisp macros that expand to other Lisp macros that eventually expand to assembly representated as s-expressions.

    As for why basic Lisps are register-hungry, you usually reserve an "environment pointer register", which points to closure data or scope data associated with the currently running function. And then you might also want a "symbol table base" register, which points to interned symbols. The first symbol value (located directly where the symbol register points) should be 'nil', which is both "false" and the "empty list". This allows you to check Boolean expressions and check for the empty list with a single register-to-register comparison, and it makes checks against other built-in symbols much cheaper. So now you've sacrificed 2 registers to the Lisp gods. If you have 8 registers, this is fine. If you have 4 registers, it's going to hurt but you can do it. If you have something like the 65C02, which has an 8-bit accumulator and two sort-of-flexible index registers, you're going to have to get ridiculously clever.

    Of course, working at this level is a bit like using #[no_std] in Rust. You won't have garbage collection yet, and you may not even have a memory allocator until you write one. There are a bunch of Lisp bootstrapping dialects out there with names like "pre-Scheme" if you want to get a feel for this.

    Forth is a stack machine, so you basically just need a stack pointer, and a couple of registers that can be used to implement basic operations.

    Anyway, Lisp in Small Pieces is fantastic, and it contains a ton of the old tricks and tradeoffs.