[HN Gopher] My history with Forth and stack machines (2010)
       ___________________________________________________________________
        
       My history with Forth and stack machines (2010)
        
       Author : panic
       Score  : 86 points
       Date   : 2021-02-20 12:02 UTC (10 hours ago)
        
 (HTM) web link (yosefk.com)
 (TXT) w3m dump (yosefk.com)
        
       | gsmecher wrote:
       | I return to this essay about once a year, when I hear the siren-
       | song of minimalism and am tempted to implement a Forth CPU. It's
       | situated exactly in the no-mans'-land between FORTH and "C" camps
       | and effortlessly slides between pragmatic and idealistic
       | considerations. It emphasizes the distinction between the lure of
       | toolbuilding (making a Forth, making a CPU) and actually solving
       | the problems these tools are intended to solve. The comments on
       | word sizes and problem-shifting are spot-on. It's an excellent
       | essay, and I wish there were more like it.
       | 
       | By day, I'm an FPGA coder. (The ASIC situation rhymes, more or
       | less.) From the perspective of implementing small CPU
       | architectures: register files are not the problem in the way
       | you'd expect. A modern FPGA has primitives that implement 32- or
       | 64-register files with remarkable efficiency, in 2-, 4-, or
       | 8-port varieties. Midrange FPGAs are now fast enough that a
       | lightly pipelined RISC CPU performs well enough; there's no need
       | to aggressively pipeline a CPU for normal clock rates (100 to 250
       | MHz) any more, and little motivation for exotic architectures.
       | The MicroBlaze (Xilinx) or NIOS-II (Altera) soft core CPUs are so
       | boringly effective they've forced ARM to license their designs
       | for FPGAs _for free_. For smaller state-machine-class problems,
       | the PicoBlaze is an interesting, minimal RISC. For most of us,
       | CPUs are solved problems and the frontier has shifted towards
       | cache-coherency, networking and other more interesting arenas.
       | 
       | I love the idea of stack machines in the right contexts
       | (PostScript, FORTH, Java, you name it), but it's hard to disagree
       | with the essay's conclusions.
        
       | vok wrote:
       | Does anyone know of other places where the word "flow" is used
       | like this?:
       | 
       | > Good Forth programmers arrange things so that they flow on the
       | stack.
       | 
       | I think this concept of "flow" applies elsewhere, for example
       | when creating tacit programs in J. But I haven't seen "flow" used
       | like this anywhere except this article.
        
         | guerrilla wrote:
         | At the same time it reminds me of tacit programming/point-free
         | style[1] in general and dataflow[2] programming.
         | 
         | 1. https://en.wikipedia.org/wiki/Tacit_programming
         | 
         | 2. https://en.wikipedia.org/wiki/Dataflow_programming
        
       | peter_d_sherman wrote:
       | >"To this day, I find it shocking that you can define defining
       | words like CONSTANT, FIELD, CLASS, METHOD - something reserved to
       | built-in keywords and syntactic conventions in most languages -
       | and you can do it so compactly using such crude facilities so
       | trivial to implement. Back when I first saw this, I didn't know
       | about DEFMACRO and how it could be used to implement the defining
       | words of CLOS such as DEFCLASS and DEFMETHOD (another thing about
       | Lisp they don't teach in schools). So Forth was completely mind-
       | blowing."
       | 
       | It is, isn't it?
       | 
       | Forth is sort of "the next level up" from assembler, in the
       | language abstraction hierarchy...
       | 
       | First you have switches...
       | 
       | Then you have punched cards...
       | 
       | Then you have a simple assembler that converts files of numbers
       | into machine code.
       | 
       | Then you have a simple assembler that converts files of
       | instruction symbols (operands/mnemonics) and numbers into machine
       | code.
       | 
       | From there, you might have assemblers that understand global
       | variables and labels/addresses in memory (necessary for function
       | calls), etc.
       | 
       | Forth starts to come into existence when you take a later (in
       | evolution/time) assembler's symbol (aka "function name") address
       | look-up table for functions -- and implement it dynamically (with
       | the ability to add to it) at run-time.
       | 
       | That's because the initial Forth tokens/symbols (it's
       | "primitives") -- are CALLs to static pre-defined assembler
       | routines (or would have been in the first version of forth) --
       | with the ability to define new tokens/symbols -- in terms of
       | combinations of executions the previous ones...
       | 
       | To make all of this magic happen, you need to separate the "CPU
       | provided" single stack (well, at least if we're thinking Intel,
       | which may have not been the case with early CPU's) -- into two
       | stacks, one for data, one as a call (function return) stack, and
       | this is exactly what Forth does.
       | 
       | Forth exposes the data stack to do whatever you want with, to you
       | the programmer.
       | 
       | Now, when we get to Lisp (the next level up from Forth, in my
       | opinion) -- Lisp does two additional things, which are
       | 
       | A) Takes the ability to monkey with any stack, away from the
       | programmer.
       | 
       | B) Implements the management of LISTS -- whose management (memory
       | management, pointers, etc.) the programmer no longer needs to
       | worry about (think of this as a pattern from mathematics, N, that
       | is, 'multiples', 'batches', 'vectors' ("more than one thing of")
       | -- meets Forth's ability to work with single symbols -- well
       | there has to be a convenient data structure to work with multiple
       | symbols (and data, which now there is a demarcation of in LISP)
       | at a time -- and that way LISP provides, with it's "I'll manage
       | the stack and the lists (at least the memory/structure/pointers
       | of the list) for you, so you the programmer don't need to worry
       | about any of that!
       | 
       | So yes, Forth is brilliant and fascinating in its simplicity...
       | 
       | I wouldn't write million-line business applications in it,
       | because as a programmer, I am not perfect, I make mistakes, and I
       | tend to like type-checking, automatic stack management, and other
       | things that modern compilers provide.
       | 
       | But Forth is utterly brilliant from a "how did Computer languages
       | evolve" / "how could a computer 'pull itself up by its
       | bootstraps' perspective"...
       | 
       | It's highly worthwhile for any programmer to learn about...
        
         | macintux wrote:
         | The parallels between Forth and Lisp seem painfully obvious to
         | me, but I've had knowledgeable programmers here tell me I'm
         | hallucinating when I say that. I really need to spend more time
         | with both.
        
           | peter_d_sherman wrote:
           | Both Forth and Lisp (and Basic, and every other interpreted
           | language) -- implement symbol lookup at runtime, that is,
           | take an arbitrary text string like 'something', 'something
           | else', 'FUNC1', 'bob', etc. -- and look up an address for
           | that value, then CALL that address (make that function
           | happen).
           | 
           | Modern programmers might understand this as a hash lookup, or
           | a key/value lookup... The key is the function/functionality
           | name (symbol, string, call it whatever you will), and the
           | value is an integer, which is the address in memory that will
           | be called to implement the functionality.
           | 
           | Forth doesn't hide anything from the programmer.
           | 
           | Lisp hides its implementation of its list structure (memory,
           | dynamic memory allocation, dynamic memory deletion, etc.) as
           | well as any and all stacks -- from the programmer.
           | 
           | Now lisp might have API's where that information is
           | accessible to the programmer -- (in theory, any language can
           | have an API which allows access to any and all memory, by
           | definition this allows any language to play with all parts of
           | the machine), but the way LISP was originally implemented,
           | the programmer no longer has to deal with this, much in the
           | same way that in Java/.NET, the programmer doesn't have to
           | deal with deallocating memory for objects when they go out of
           | scope...
           | 
           | But all interpreted languages, one way or another, boil down
           | to 'look up a symbol/string/"key" -- representing a function
           | name -- then call/goto/invoke that function in memory... sort
           | of like a key/value lookup (or even just a variable name that
           | gives you back an address -- a pointer in compiled
           | languages), and then you just CALL (preserving where you came
           | from (to later RET)) to that address"...
           | 
           | So in some ways similar, in some ways different...
        
             | dasyatidprime wrote:
             | Note that (a usual) Forth (here I'm centering on ANS Forth)
             | uses early binding for most code, rather than late binding
             | as you sound like you're talking about. The dictionary is
             | indeed available at runtime, but runtime _includes_
             | compile-time!--which is how a lot of the metaprogramming
             | works, by interleaving the two with no strict phase
             | separation. So it 's while you're _defining_ a word that
             | calls other words that the other words are looked up, and
             | not while you 're executing it. This has some convenient
             | implications for redefinitions:                   : foo (
             | something ) ;         : bar 2dup foo 1+ * ; \ or whatever
             | \ ... in something loaded later on ...         : foo (
             | other thing entirely ) ;         : xyzzy foo bar ; \ calls
             | later foo, then earlier bar which calls earlier foo
             | 
             | ... which means it's harder to run into problems with
             | stepping on "internal" symbols even if you're using short
             | names in the "same" dictionary. "Purely" interpreted Forth
             | does happen at the top level, in which lookups happen just
             | before execution, but the part of it that isn't being used
             | for compilation tends to be small fragments to kick off
             | execution or to enter commands interactively. (In
             | colorForth, phase distinctions are made by color instead,
             | but I believe it also does early-bound compilation.)
             | 
             | In (a usual) Lisp (here I'm centering on Common Lisp and
             | Scheme), function and variable lookups default to late
             | binding, but based on symbol objects rather than strings.
             | The symbols themselves contain pointers to function objects
             | and/or variable contents, so there's a level of
             | indirection, but it's not a hash table lookup every time.
             | (Indeed in CL you can have "uninterned" symbols which
             | usually have a name but can't be looked up by that name.)
             | Again the mapping from strings, that is, packages in CL,
             | obarrays in Emacs Lisp, etc. generally remains available at
             | runtime, so you can do (find-symbol "FOO"), but when
             | processing definitions, the lookup from string to _symbol_
             | happens at _read_ time. Then in CL there 's some
             | restrictions on redefinitions (http://www.lispworks.com/doc
             | umentation/HyperSpec/Body/03_bbc...) that make it easier
             | for compilers to do more localized early binding even
             | between top-level definitions.
             | 
             | Traditional BASICs I vaguely recall being closer to "fully"
             | interpreted than this, but I haven't studied those enough
             | to say--and I wouldn't expect that to apply to later BASIC
             | revivals after a certain point.
             | 
             | An interesting other example would be Lua, which uses late
             | binding semantics for global and method lookups, and then
             | (here I'm thinking of the mainline PUC implementation)
             | makes them faster by interning _all_ strings at the C
             | level, in a way which isn 't exposed to the Lua level. When
             | all strings with the same contents have the same pointer,
             | string equality is very cheap, which makes table lookups
             | over the string objects cheap. But lexical locals are
             | compiled into index lookups, which are even cheaper, and
             | it's often considered good practice to write internal file-
             | level definitions as locals, import external module tables
             | as locals, etc. so in practice once again some of the late
             | binding disappears along the way.
        
       | macintux wrote:
       | This really is an interesting article, and much of the
       | introduction aligns with my experience: I love the concept, wish
       | I had a reason to use Forth, but also I'm not and never have been
       | a hardware person and haven't the foggiest clue what to do with
       | it.
       | 
       | For anyone who isn't familiar with the language, Thinking
       | Forth[1] and Starting Forth[2] are (as far as I know) well-
       | regarded books on the subject.
       | 
       | [1]: http://thinking-forth.sourceforge.net
       | 
       | [2]: https://www.forth.com/starting-forth/
       | 
       | Also, for anyone like me who dearly misses W. Richard Stevens, he
       | wrote a Forth primer in the 70s. You can find it any several
       | other documents on forth.org[3].
       | 
       | [3]: http://forth.org/tutorials.html
       | 
       | Previous discussions of this post over the years:
       | 
       | https://news.ycombinator.com/item?id=3963896
       | 
       | https://news.ycombinator.com/item?id=8146306
       | 
       | https://news.ycombinator.com/item?id=8869150
       | 
       | https://news.ycombinator.com/item?id=21153555
        
         | ranma42 wrote:
         | In that context, mecrisp forth on a small micro[1] for doing
         | simple embedded/sensor programming sounds like something I
         | should try sometime... [1] https://jeelabs.org/article/1705c/
        
         | mumblemumble wrote:
         | re:factor is a nice blog for seeing how higher-level things can
         | be done in a concatenative language. It's been dormant for a
         | couple years, but the archives have some interesting examples
         | of redoing well-known stuff in Factor.
         | 
         | https://re-factor.blogspot.com/
         | 
         | For example, here's a gopher server: https://re-
         | factor.blogspot.com/2016/10/gopher-server.html
        
         | jlg23 wrote:
         | > I love the concept, wish I had a reason to use Forth, but
         | also I'm not and never have been a hardware
         | 
         | High level & easy: chat bots. I have implemented a forth
         | interpreter in several bots I wrote (admins only, for obvious
         | reasons ;). Since they are running within the bot's code, you
         | have as much access to the client's state as you
         | allow/implement.
        
       | picks_at_nits wrote:
       | "When you're holding a Shunting Yard Algorithm, every Domain-
       | Specific Expression Language looks like a compile-to-RPN-and-
       | evaluate-with-a-stack-machine nail."
       | 
       | https://en.wikipedia.org/wiki/Shunting-yard_algorithm
        
       | [deleted]
        
       | veltas wrote:
       | > ...it is scripting that is the best trojan horse for pushing a
       | language into an organization. Scripting is usually mission-
       | critical without being acknowledged as such, and many scripts are
       | small and standalone. Look how many popular "scripting languages"
       | there are as opposed to "systems programming languages". Then
       | normalize it by the amount of corporate backing a language got on
       | its way to popularity. Clearly scripting is the best trojan
       | horse.
        
       | jstanley wrote:
       | This was a really interesting article. I appreciate the author's
       | matter-of-fact style, and the way he communicates his own
       | thoughts without trying to say they're the _only_ correct
       | thoughts. And despite the author 's own experiences, I came away
       | thinking that I would actually like to try writing something in
       | Forth myself. Even though I like "C". This description really
       | resonated with me:
       | 
       | > being able to do what at least 3 people in their respective
       | areas normally do, and concentrating on those 3 things at the
       | same time. Doing the cross-layer global optimization.
       | 
       | I at least _desire_ to be competent at that level, and think I 'd
       | enjoy having a go.
        
         | jacquesm wrote:
         | Forth is an interesting language but if you are used to modern
         | stuff it is likely going to be an exercise in frustration to
         | get 'something done' unless 'something' happens to be a small
         | soft real time project. So I suggest you start with that and
         | then take it from there.
        
       ___________________________________________________________________
       (page generated 2021-02-20 23:01 UTC)