Forsp: A Forth+Lisp Hybrid Lambda Calculus Language
228 points
1 month ago
| 23 comments
| xorvoid.com
| HN
NackerHughes
1 month ago
[-]
This is incredible. I'll be tinkering with this for a while, guaranteed. As the advantages of combining Lisp and Forth in this way are slowly revealed to me it's like unlocking parts of my brain to interact with each other that never have done before.

Pretentiousness on my part aside, this is a pretty mind-blowing concept. The interpreter being in less than 1000 lines of (comprehensible) C is all the more commendable (most of these minimal languages turn out to be some monolithic Rust thing or similar, which kinda defeats the entire purpose imo).

Excited to take a closer look at the source to see how the various data structures etc. are laid out in memory. I won't be able to resist making comparisons to my current/other favourite mini-language 'fe' (https://github.com/rxi/fe), a sub-1000-line Lisp also written in C. If you haven't seen that one I'd definitely recommend checking it out.

Thank you for putting this online. Happy hacking!

reply
xorvoid
1 month ago
[-]
Thanks for the kind words. But if you’re looking for fancy data-structures, you won’t find them. Haha. It’s just a single object type (sum-type/tagged-union) and lots of cons cells. In other words, just a minimalist Lisp.
reply
incanus77
1 month ago
[-]
I am in love with 'fe', as well as the slightly-better IMHO 'aria' by the same author.
reply
um1
1 month ago
[-]
Very cool. I like that both lisp and forth were “discovered” and that this cvbp is more fundamental than both(?!). This reminds me of [pdf] https://dl.acm.org/doi/pdf/10.1145/181993.181999 and wonder if/how it relates.
reply
xorvoid
1 month ago
[-]
Also thanks for sharing; looks interesting. I’ll read this when I have time and re-comment then. Off the bat: Forsp variables are definitely not linear.
reply
diffxx
1 month ago
[-]
Thank for sharing that paper. I am currently implementing a compiler that has very similar ideas to those presented in it. It's very different syntactically, but the mental model is quite similar. It's simultaneously encouraging and discouraging to see your ideas reflected back to you from 30 years in the past :)
reply
alexisread
1 month ago
[-]
Looks complicated to reason about, but really interesting. Freeforth implements a similar mechanism to eliminate swaps via register renaming, obv. the focus is different in that Freeforth tries to still be an optimised stack, rather than a VLIW processor. I guess you could look at this paper as a vector processor, similar to the way APLs handle things with function composition. In that, you'd need the compiler to take care of things as managing it manually would be a nightmare. I guess the best way forward would be a specialized (forth) word lib which invokes the compiler to manage the top N stack cells for the duration of the word.
reply
boltzmann-brain
1 month ago
[-]
what is cvbp? google yields nothing.
reply
boygobbo
1 month ago
[-]
I think that was meant to be CBPV (Call-By-Push-Value)
reply
tonyg
1 month ago
[-]
It's a typo for CBPV, Call-By-Push-Value.
reply
sevensor
1 month ago
[-]
What I like about this is how neatly it reduces everything to a small set of concepts. Much like lisp and forth, but not the same as either. It's exciting to think that there may be more of these out there, waiting to be discovered!
reply
thsksbd
1 month ago
[-]
I been wanting to make a forth+lisp language for a while now - inspired by HP's RPL (its so close to being a lisp!).

Super cool

reply
xorvoid
1 month ago
[-]
A friend mentioned RPL, but that didn’t seem to even have first-class functions… unless I’m mistaken?
reply
thsksbd
1 month ago
[-]
Im not a CS guy, so I dont know what "first class" means.

I can do whatever to a function. I can use it as an argument. I can return it from another function. I can make a function that modifies functions.

All that w/out doing anything out of the ordinary day to day programing.

reply
ceving
1 month ago
[-]
You can put functions on the stack.
reply
agumonkey
1 month ago
[-]
with arrow syntax even

   << a -> << a a * >> >> 'SQUARE' STO
I still can't believe how advance RPL calculators were
reply
thsksbd
1 month ago
[-]
The HP engineers were good.

First HP engineers made RPL, then they attended Sussman and Abelson's intro to CS class. I think Sussman once said that, if RPL had anonymous functions it'd be a perfect lisp.

They were real good.

https://ocw.mit.edu/courses/6-001-structure-and-interpretati...

reply
pierrebai
1 month ago
[-]
Reading the example, I really wish the syntax for push and pop had been "<foo" (push foo on stack) and ">bar" (pop from stack into bar). I find the choice of $ and ^ not obvious. Especially since, for me, ^ implies popping, not pushing.
reply
xorvoid
1 month ago
[-]
You can fork and fix that with sed. Haha.

I decided on using sigils and then my thought-process drifted to Perl/Php, so $ naturally felt like a variable definition (which naturally must pop from the stack). I guess ^ is “push ‘up’ to stack”. But it’s all really arbitrary irrelevant syntax. YMMV.

reply
dandanua
1 month ago
[-]
'$' is common for interpolation in many languages. It's a good choice.

Awesome language, and it's definitely important from a theoretical point of view. However, you've missed the chance to call it "LiFo" :)

reply
quibono
1 month ago
[-]
Interesting. I'm not sure about $ and ^ either but I think I'd keep getting confused about which one of < and > meant pushing vs pulling
reply
dugmartin
1 month ago
[-]
Maybe + for push and - for pop?
reply
pmarreck
1 month ago
[-]
This looks really neat, although I'm still wrapping my head around it! It's funny how things that are even more fundamentally simple than what has been discovered, can be (initially) harder to reason about!

So you could presumably also write a Lisp interpreter AND a Forth interpreter using it?

(Might as well write a Turing machine interpreter too for the trifecta... assuming one can decide on the syntax...)

That plus some additional functionality to make it more usable in the general sense (adding math functions, string manipulation, maybe some basic I/O) and it might be a VERY interesting instructional tool.

I've heard the term "thunk" but I forget what it means...

reply
kazinator
1 month ago
[-]
A thunk is a piece of glue machine code that adjusts some pointer (which is imagined to make a "thunk" sound) and then calls the real code.
reply
pmarreck
1 month ago
[-]
It's a delayed evaluation, as I understand it?
reply
alexisread
1 month ago
[-]
That's the way I tend to read it. The fun bit then is applying context at eval time.

Rebol allows you to use a block as a list/function/thunk, which is lazily evaluated. This changes the scoping in that you BIND a context to this thunk.

It's a different take on process migration in that the context can be serialised with the thunk, or a new context applied at the process site. This is in contrast to lexically scoped lambda functions which then need to account for dynamic (free) variables somehow.

reply
kazinator
1 month ago
[-]
There is a usage of thunk in the context of implementing a compilation strategy for call-by-name arguments.

The usage survives into modern times, whereby lambda arguments are sometimes called thunks.

reply
gct
1 month ago
[-]
A thunk is just a function with no arguments representing a computation. In C++:

  int a=1, b=2; auto thunk = [=]() { return a+b; }
reply
pmarreck
1 month ago
[-]
so a deferred evaluation, like (in elixir) wrapping a computation in an fn (lambda of 0 arity)
reply
kazinator
1 month ago
[-]
What has Lisp semantics and a stack with "call by push value" and whatnot is any one of the stack-based virtual machines used for compiling Lisp over the past 60 years.
reply
im3w1l
1 month ago
[-]
Another recent attempt at a language combining features of Forth and Lisp https://github.com/rickardnorlander/misc/tree/main/cursed_la...

Someone even asked the same question

    ' seems redundant. 'x could just be (x)?
though for that language the answer was that yes it's exactly the same.
reply
diffxx
1 month ago
[-]
Cool language. One modest suggestion: perhaps return 0 rather than 1. Feature request: addition and division as well as subtraction and multiplication ;)
reply
xorvoid
1 month ago
[-]
Addition can be implemented as:

(0 swap - -) $+

Division is much harder, but doable also. Generally Forsp in the camp of minimalist languages so minimizing the number of primitives seemed more interesting.

If you want to play around yourself, it’s trivial to add those operations.

reply
bowsamic
1 month ago
[-]
> Division is much harder, but doable also.

> If you want to play around yourself, it’s trivial to add those operations.

Now you're using "trivial" like a mathematics professor!

reply
xorvoid
1 month ago
[-]
Ha. Well I meant “add those operations to the C interpreter”, which would be like 4 lines of code.
reply
tonymontana69
1 month ago
[-]
Reminds me of the language from this paper, which also shows how to smoothly integrate I/O and state into such a style https://arxiv.org/abs/2212.08177
reply
CrociDB
1 month ago
[-]
Forth+Lisp = Wet dreams of every programming nerd
reply
CyberDildonics
1 month ago
[-]
Until you have to deliver something and you realize you love programming languages more than creating software.
reply
CrociDB
1 month ago
[-]
nothing wrong with enjoying playing with your guitar more than actually making music.
reply
CyberDildonics
1 month ago
[-]
Nothing wrong with it until it's time to make music and you need a normal guitar.
reply
tangjurine
1 month ago
[-]
If you have time, could you explain the ycombinator and if parts a bit more? Been a while since I looked at this stuff
reply
nickcw
1 month ago
[-]
I like this very much!

pop is kinda like ! in FORTH and push is like @ so I think $foo would be more FORTHy as !foo and ^foo as @foo

reply
ungamedplayer
1 month ago
[-]
If you like to work the other way I present https://github.com/garlic0x1/forth forth in common lisp.
reply
throw156754228
1 month ago
[-]
Wow he even provides an interpreter in C. How would a compiler implementation for this language foreseeably differ from the interpreter given, does anyone know? Trying to learn about this more.
reply
alexisread
1 month ago
[-]
I'd take a leaf out of Freeforth's compiler (https://github.com/dan4thewin/FreeForth2): It implements a repl via single-pass (as with all forths) compilation of an anonymous block. Scoping in forth is dynamic ie. what's on the stack, and lexical in the dictionary ie. new words can shadow old ones.

Stacks have great GC properties vs. an actual GC, but for more complex scenarios, and dictionary management, some sort of GC would be preferred. I've not looked at the forsp source yet, but it may need a more general (realtime) GC, possibly via a linear-types system.

reply
packetlost
1 month ago
[-]
I feel like simple reference counting + linear types would be a really good approach in this case.
reply
tonyg
1 month ago
[-]
Could ^a be dispensed with? It looks like (a) is equivalent to it.
reply
xorvoid
1 month ago
[-]
Subtly different. Originally I didn’t have ^a, but you do in-fact need it. ^a means “push with no evaluation” and (a) means “build a closure” that computes a”.

If you push, pop, push, pop, push repeatedly you’ll be wrapping the value in lots of closures. The value is no longer a value too. You’d have to “force” it to get back the value.

Semantically quite different. If you’re going all Turing tarpit, could you survive without “^a”? Maybe. But you’d be suffering for sure.

reply
tonyg
1 month ago
[-]
Operationally, you don't have to do the wrapping: since (a) is equivalent to ^a, you can substitute the implementation of the latter when you see the former.

Though if `(a)` is observably "no longer a value" of course then that's an issue. Which may or may not be a problem :-)

reply
xorvoid
1 month ago
[-]
I thought of that. But then you’re making the semantics complicated. Say you really did want a (a) closure. Now you can’t have one, and you have to resort to more tricky wrapping. Not worth it.
reply
dugmartin
1 month ago
[-]
I think the equivalent would be ('a) instead as (a) would resolve to "'result of application of a' push".
reply
tonyg
1 month ago
[-]
The page says that "`(foo bar) force` is the same computation as `foo bar`" and that "`^a force` is the same computation as `a`". So then `(a) force` should be the same computation as `^a force`.
reply
mkovach
1 month ago
[-]
Since it is written in C, we need to write COBOL and FORTRAN interpreters with it, make sure the manpages are in Latin, and we'll hit the ancient languages nerdvonia.
reply
lanstin
1 month ago
[-]
User viewable docs should be Anglo-Saxon and dev docs should be Latin (or Attic Greek?) Publicity materials should be medieval Arabic, and the secret documentation that isn't shared should be in Mandarin.
reply
dugmartin
1 month ago
[-]
I really like the syntax and ergonomics - nice job @xorvoid.
reply
pbrhocwp
1 month ago
[-]
Very nice! Thanks a lot for the discovery.
reply
James_K
1 month ago
[-]
I've never really got Forth. It's good for sending commands to a printer, but actually programming in it seems like a drag. I get that it's technically the same as lisp code, just backwards without the parens, but adding the parens just makes it easier to understand. Adding variables is smart and would make it more tolerable but I would still rather work in lisp.
reply
bunderbunder
1 month ago
[-]
I guess you could say that syntactically it's like lisp, only backwards and without the parens. But the deeper truth is that concatenative programming and procedural programming (or functional, I suppose, depending on your preferred lisp flavor) are very different paradigms that lead to fundamental differences in how you structure your code.

I haven't used Forth in anger for over two decades now, but in some ways I do miss how the language felt even more hackable than a good lisp, while simultaneously being easy to reason about at a low level. That said, the RPN and stack manipulation definitely make it a bit of a brain teaser, especially at first. In that sense it reminds me of Prolog and APL and, for that matter, Lisp: they're languages where the greatest joy of learning them is the way they force you to think in very different ways, and how that can promote epiphanies about computation.

I would need to spend more time grokking this language to really understand, but it appears that the author has effectively achieved something that's semantically equivalent to Forth, but has a lot of syntactic commonality with Lisp. That's rather compelling to me from a "eliminates the first and most noticeable stumbling block for newcomers" perspective. That said, I'm having to repress a bit of a desire to react the same way parts of the Racket community did when Matthew Flatt announced his infix syntax project. Is a key part of Forth's concatenative nature lost when the syntax itself is no longer concatenative? I don't know, and I'll admit it's very doubtful that I'll spend enough time with Forsp to find out. But it's an interesting thought, all the same.

reply
veltas
1 month ago
[-]
Here's why it's interesting: https://termbin.com/blz3

There you go, now it has parens, just backwards. Can even use parens instead of braces if you want, I just didn't want to overwrite comment syntax.

reply
Y_Y
1 month ago
[-]
I copy your code below, because it's so terse and marvellous and I don't want to deprive anyone who assumes otherwise and doesn't click on your link:

    : >BRACES       1 CELLS BRACES +!  BRACES @ ! ;
    : BRACES>       BRACES @ @  -1 CELLS BRACES +! ;
    : {             DEPTH >BRACES ;
    : }             DEPTH  BRACES> 1+  <> ABORT"unbalanced" ;
And the example "backwards lisp" expressions you give as test cases:

        { 1 2 + } .
        { 5 { 1 2 + } * }
reply
James_K
1 month ago
[-]
I don't find that particularly interesting. My entire point was that it has essentially the same semantics as lisp, and this just demonstrates that. Also, I don't know a lot about Forth, but it would seem to be limited to 30 levels of brace nesting, even between functions. Unless I am mistaken, it requires a pointless increment and decrement of a variable before and after every function call. I also don't know if it would work in a threaded context. Aren't those just functions? If so, it wouldn't work in compiled code.
reply
veltas
1 month ago
[-]
> it would seem to be limited to 30 levels of brace nesting

Yes because I declared 30 cells, you can declare more if you want.

> Unless I am mistaken, it requires a pointless increment and decrement of a variable before and after every function call.

Yes it adds overhead, you can rebuild with : { ; IMMEDIATE : } IMMEDIATE ; and then there's zero overhead on any Forth, when you've demonstrated the braces match. This is meant as an aid for people that struggle without parens.

> Aren't those just functions? If so, it wouldn't work in compiled code.

It works in compiled and interpreted code, 'just functions' compile automatically without trouble in Forth, it's parsing words and immediate words that need care.

No that code wouldn't work with multiple threads, but if you declare the space as USER space it would, similar to just whacking 'thread_local' on something.

The example is failing in one regard, that it doesn't provide a way to reset the stack if something goes wrong. You can use something like : RESET{} BRACES BRACES ! ; to accomplish that. But it is just meant to show that Forth is quite succinct and powerful, which it does.

reply
James_K
1 month ago
[-]
> you can rebuild with : { ; IMMEDIATE : } IMMEDIATE ;

Does the call to DEPTH still work in that case?

> But it is just meant to show that Forth is quite succinct and powerful, which it does.

How does it show that? You could write a similar function in almost any programming language, with the only difference being the call to DEPTH which I don't see as being generally that useful. As an example in common lisp:

  (defvar depth nil)
  (defun { () (push (depth) depth))
  (defun } () (unless (and depth (= (depth) (pop depth))) (error "Unbalanced expression")))
And this example doesn't stop working after a certain number of open braces. It's simultaneously more readable and more terse than the Forth code. Saying that Forth is powerful just because you can use it to write a compiler for a more powerful language is silly. By the virtue of Turing-completeness, you can do this for any language.
reply
veltas
1 month ago
[-]
> Does the call to DEPTH still work in that case?

Code using the braces can use those definitions when you want to remove the overhead, and it disables the feature. This lets you use it as a programming aid, and then remove the overhead completely when it's not necessary or for a 'release build'.

> It's simultaneously more readable and more terse than the Forth code.

Does it still look neater when you use the syntax you've defined?

> And this example doesn't stop working after a certain number of open braces.

If you really want to remove the limit you can, Forth is just explicit about memory management. This is like C vs JavaScript. I'm quite happy playing with memory management myself, but if you don't like that or think declaring a fixed size stack is hacky then I can see why Forth wouldn't be appealing.

Here is one way to remove the limit, it has drawbacks (but so do all dynamic memory approaches, they're just hidden by managed languages).

  : POP   HERE -1 CELLS + @  -1 CELLS ALLOT ;
  : {     DEPTH , ;
  : }     DEPTH  POP 1+  <> ABORT" Unbalanced" ;
> Saying that Forth is powerful just because you can use it to write a compiler for a more powerful language is silly.

Yes but in a few lines of simple code, and the result is actually a syntax that looks nice to use.

reply
James_K
1 month ago
[-]
> This is like C vs JavaScript.

And you'll notice that C isn't a very powerful language.

> Does it still look neater when you use the syntax you've defined?

No because in CL you would define a with- macro that syntactic enforces equal nesting. So the CL version is:

  (defmacro {} (&rest args) `(progn ,@args))
I thought that would be too trivial demonstrate though. If you really don't like how it looks, you can fix it with two more lines of reader macros. The result would be a nicer syntax as you wouldn't have to put spaces around the braces.

> Yes but in a few lines of simple code, and the result is actually a syntax that looks nice to use.

Those are a few lines of highly complicated code that does something very simple and just happens to use the short function names { and }. The actual implementation of those functions is easier to write in almost any other language, which is what I was demonstrating with the CL example. If you want to make the case that Forth is powerful, you need to do more to prove it than show you can hack together a half-solution for a problem Forth created. Forth simply isn't a powerful language when counting matching braces is a difficult problem.

reply
veltas
1 month ago
[-]
> C isn't a very powerful language.

Okay.

> I thought that would be too trivial demonstrate though. If you really don't like how it looks, you can fix it with two more lines of reader macros. The result would be a nicer syntax as you wouldn't have to put spaces around the braces.

The example is 'trivial' though. Also compare how big Forth is against CL, I mean Forths are often well under 10KB. I've written a Forth like this. I've never written a CL. Have you?

> Those are a few lines of highly complicated code that does something very simple and just happens to use the short function names

Just because you're incredulous doesn't mean that's not a feature.

> easier to write in almost any other language, which is what I was demonstrating with the CL example

CL isn't 'any other language' though, lisps specifically have quite powerful reflection.

> hack together a half-solution for a problem Forth created

Hacky half-solutions are charming, and Forth didn't create the 'problem' of structured syntax, it just didn't provide a solution out-of-box. That's one of the very explicit tradeoffs of the language.

reply
James_K
1 month ago
[-]
> Also compare how big Forth is against CL

I would say a lot of this is because CL has a large standard library, but not much of that is primitive. Even then, I would say the CL standard lacks many things, so it is too small for general use rather than too large. From a perspective of implementation, I would almost always choose a small Scheme over Forth or something Forth-like.

> CL isn't 'any other language' though, lisps specifically have quite powerful reflection.

Those functions don't use reflection.

> Forth didn't create the 'problem' of structured syntax, it just didn't provide a solution out-of-box. That's one of the very explicit tradeoffs of the language.

It created the problem of not having structured syntax and that's a trade-off that you lose as soon as you write more than a maybe a hundred lines of code.

reply
veltas
1 month ago
[-]
> Those functions don't use reflection.

Sorry I mean macros / or whatever. You know what I mean(?).

> that's a trade-off that you lose as soon as you write more than a maybe a hundred lines of code

And yet you can have braces with e.g. 2-3 lines of code. Anyway this is beyond tedious now, sorry for provoking you with my code, or my opinion that Forth is interesting, or with the praise I received, or that Forth kicked your dog or something; I'm not really sure what did it. I have talked with you in good faith to try and help you understand why it's interesting to me, I don't claim it's an absolute good or even necessarily practical in most situations.

reply
James_K
1 month ago
[-]
You misrepresent me here. I never claimed that Forth is uninteresting, just that your code was. The statement I take issue with is that Forth is powerful. In reality, it is useful only because it is not powerful and hence is easier to implement.

> yet you can have braces with e.g. 2-3 lines of code

You can't really. You'd be better off treating braces as whitespace than having them check the stack depth before and after each function call. They wouldn't even be correct then because you could make multiple calls within the same set of them. If you want to implement braces properly, you'd need a way to introspect how many arguments a function takes, but Forth is not powerful enough to do this.

reply
gergo_barany
1 month ago
[-]
Forths do have local variables. (Cue debates on what makes a stack-based Forth-like language a "Forth".) Here is the description of Gforth's support for its own extended locals and the Standard Forth locals: https://gforth.org/manual/Locals.html (Cue debates on whether Standard Forth is a Forth and whether it is standard.)

When I learned Forth about 15 years ago, we used the { } syntax described here, not the newer(?) {: :} syntax: https://gforth.org/manual/Local-Variables-Tutorial.html

reply
eschneider
1 month ago
[-]
Forth is awesome in situations where you've got a small environment, with crap tools, and you need to do some iterative development. In a pinch, you could hand assemble a Forth kernel and just burn it on an EEPROM and away you go. There are usually nicer alternatives today, but when you had to build your tools up from nothing, it was a very reasonable option.
reply
dreamcompiler
1 month ago
[-]
Correct. You can build a Forth compiler from hand-typed hex machine code. It will be a bit easier if you have an assembler handy, but that's not essential. Trying to build a Lisp compiler this way would be madness.
reply
astrobe_
1 month ago
[-]
The comparison between Lisp and Forth that is often made is very shallow. It's more than just prefix vs postfix. Lisp takes garbage collection for granted, Forth is designed to avoid any complication and doesn't even have heap allocation.

The lack of local variables and named parameter is also a consequence of this search for ultra-low complexity, in dialects that follow the traditional style (the one of the creator of Forth, Chuck Moore).

The readability argument is one that should always be taken 1024 grains of salt. You say Lisp is more readable, yet you'll find some people to say that it's "Lots of Insipid and Stupid Parenthesis" and that prefix notation is unnatural. You could also find positive opinions about APL. It is just a matter of training and habits.

reply
James_K
1 month ago
[-]
Lisp is objectively more readable. Adding parentheses around expressions distinguishes them. Compare the following.

  a b c d
  (d (c b) a)
The bottom example is objectively more readable. You can determine more about its intended function by looking at the code. In the top case, you can't tell what is a function call or how many arguments each function consumes. In the bottom, you can.
reply
astrobe_
1 month ago
[-]
> objectively more readable

Certainly not "objectively" in the literal sense, because there's no measure of "readability". But you can define one if you want, so that we can argue about its biases and relevance ;-)

But Forth programmers sometimes use double spaces to separate sub-expressions:

    (d (c b) a) <=> a  b c  d
... or something like that. But in reality what one writes is (hopefully this is close enough to your abstract example):

    (square-root (+ (square a) (square b)))
    <=>
    a square  b square  + square-root
Or if we stick to real-forthers-do-not-use-local variables:

    square swap square + square-root
Newbies will probably use "training wheels comments":

    ( a ) square swap ( b ) square + square-root
I know the usual objections that one needs to keep the number of inputs/outputs of each word/function. It's actually not as difficult as it seems with good naming and when you keep things simple. Actually, the former is a well-known hard problem and the latter is a perhaps lesser-known hard problem. But I would say that's good problems to solve.
reply
James_K
1 month ago
[-]
> But you can define one if you want

Something is harder to read if it takes more effort to read it. Reading code is converting it from the format it is written down in to the tree of invocations in your mind. Forth is more complicated to read because you must use the definition of each function to determine how many arguments it takes, and hold that information on a stack in your head. All you have to do in lisp is count the number of parenthesis. Reading Forth is O(stack depth) memory, where reading lisp is O(1).

Imagine you wrote some code and it produced an error because you didn't give enough arguments to a function. In lisp, you can look at the function and trivially see how many augments it has. In Forth you will have to scan the entire piece of code to locate the error.

Purely practically, I can almost guarantee that someone with no experience in either will learn lisp faster, and they certainly wouldn't need to write "training wheels comments" to figure out how the functions are called. In fact, the person reading the lisp code would likely understand instantly how it works, where the person reading the Forth code would spend a long time staring ant it and having to write stuff down to figure that out. It's just transparently obvious that reading Forth code takes more effort than reading lisp code for anyone who has spent equal time doing both.

reply
astrobe_
1 month ago
[-]
> Something is harder to read if it takes more effort to read it.

I think the smiley was a sufficient warning not to go there. Define harder. For whom ? etc.

> hold that information on a stack in your head.

I have proactively addressed that point, it is a matter of coding style and "good practices". But since you can only believe me on that, the discussion can only go in circles if you don't agree.

> It's just transparently obvious that reading Forth code takes more effort than reading lisp code for anyone who has spent equal time doing both.

It depends by whom this code was written. I can certainly craft Lisp code which is a nightmare to read. I've seen many times Forth code that was not designed and written in the best style. "Readability" is a relationship between the reader and the writer, it's something that becomes clear when you do code reviews. I think people would generally agree that C++ is "more readable" than both Forth and Lisp, yet if you use lesser-known C++ features - or just omit parens around expressions because you assume that the priorities are obvious - you can easily lose intermediate level reviewers.

Anyway, after reading many online threads about "readability", I came to the conclusion that it is a non-argument, and that discussions about it are most often pointless. But I wanted to use that to show how Forth solves this apparently huge problem, and introduce a bit of its philosophy.

reply
James_K
1 month ago
[-]
> It depends by whom this code was written

This is really just a hand-wave. You are saying "maybe there's some difficult to read lisp code out there, so lisp might not always be easier", whereas I am talking about objective measures. That is the exact same program translated between the two representations. This can be accomplished by taking the lisp code, reversing it, and removing the brackets. Note the opposite direction is much harder because information is lost. The lisp source code encodes more information, and what is added is highly relevant to the process of reading it. When, as in Forth, this information is not given, it must be computed which is more mentally taxing hence making Forth code harder to read.

> Define harder

The human mind is a computer. Operations on this computer require energy, as much has been verified scientifically. Reading a program can be seen as an operation in this computer, as I have previously defined. Reading a Forth program requires a greater use of working memory to store items of the stack, and hence is more difficult as this is an extremely scarce resource. Using more working memory also implies an increased number of operations, so a higher time complexity as well as space. Forth code also requires more accesses to long term memory for arities. The human mind is not nearly as subjective as you take it to be, many aspects of it have been measured and observed. In the long term, pattern recognition kicks in and the problem begins to lessen but this is analogous to function memoisation. That a function can be memoised does not prove it is easy to compute, just that after putting enough work in you don't have to do the work again.

> how Forth solves this apparently huge problem

It partially solves the problem by writing expressions in a format closer to lisp. To fully solve the problem, you end up writing lisp.

reply
astrobe_
1 month ago
[-]
> The lisp source code encodes more information, and what is added is highly relevant to the process of reading it

Lisp isn't that easy when you don't have supporting tools, mainly a coding editor that auto-indents, re-indents, and shows matching parenthesis. I think you're being a bit over-optimistic there.

Forth, OTOH, has been used with dumb terminals over serial lines. Old Forth systems even often featured an integrated code editor for this purpose, on systems with very limited memory. This was possible because Forth doesn't need a complex editor to be comfortable... When you don't neglect factoring, naming, etc.

> The human mind is a computer

Right. Can your human mind compute 358729358 x 5648759845 in less than 10 ns ? False premise, invalid conclusion.

Anyway, you're so right, you're so right. Have a nice day with Lisp.

reply
kazinator
1 month ago
[-]
> don't have supporting tools, mainly a coding editor that auto-indents, re-indents, and shows matching parenthesis

But such an editor is possible for Lisp, without it having a deep understanding of Lisp at all.

For instance :set lisp mode is quite usable in classic Vi.

Whitespace, like indentation, is used in Forth programming. Do commonly available editors support this?

reply
James_K
1 month ago
[-]
> False premise

So "the human mind is a computer" is a false premise, but "a computer must be able to compute 358729358 x 5648759845 in less than 10 ns" isn't? Do you know what a computer is? The human mind has a very different architecture than the Turing machines you are used to, so its performance characteristics are also different.

> invalid conclusion

My conclusion doesn't require the premise that the human mind is a computer. That is just an analogy to help you to understand my actual argument, which is as follows: there are certain things that happen in the brain which take time and energy (scientifically proven), reading Forth requires you to do more of those things than reading lisp (trivially true). Therefore it is harder, if hardness is a metric of how much effort (time and engery) something takes.

> mainly a coding editor that auto-indents, re-indents, and shows matching parenthesis. I think you're being a bit over-optimistic there.

Even Nano does this. Far from being optimistic, I struggle to think of a situation where you wouldn't have this. Unless your computer is hooked up to a teletype printer, you will have these features. People have edited lisp code on deep space missions and had these features. How? Because you can write the forms in an editor and then send them over the wire afterwards. This is a technical problem that was solved before it even existed, and it is just a sad coincidence that many people decide to edit and run code through a dumb terminal connected to a REPL instead of in an actual editor connected to that same REPL session.

> Forth doesn't need a complex editor to be comfortable

Neither does lisp. Matching brackets and auto-indenting lisp code is so simple its braindead. It would be the simplest feature of any editor that supports it. Quite frankly, this is a stupid argument anyway. Even if text editors as "advanced" and "complex" as GNU Nano were a total rarity, I would rather code in a language that's easier for my editor installed on my computer than one which caters to someone else doing everything on an embedded device which only connects to a physical printer. That's like saying we should all use Fortran because it works much better on punch-cards.

reply
kazinator
1 month ago
[-]
Forth:

  w1 w2 w3 w4 w5 w6 w7 
We have no idea what is the structure of the expression. We do know w7 is the last word. The last word is the main one. Beyond that we cannot tell which are the children: are they w3 and w6? Or whatever.

Lisp:

  (w7 (w4 (w3 w2)) (w6 w5 (w1)))
You know what the structure is, and if the compiler doesn't tell you that something is misused, the run-time will. E.g. that w5 needs at least 3 arguments, but two are given.
reply
astrobe_
1 month ago
[-]
I have addressed that point twice already. This is tiring.

I discovered Forth a long time ago and thought, there's no way this language is viable, and yet it was used in prestigious projects. It made me curious so I tried it, dropped it, picked it again and tried harder. I was young and open minded. A couple decades later Forth is my go-to language.

I have no interest in convincing people with immutable opinions who cannot listen.

reply
kazinator
1 month ago
[-]
But the topic is readability not viability. Pretty much all programming languages have some readability issues, dealing with them via coding conventions.

Nowadays we want our text editors to handle those conventions automatically, and be able to recover them even if we delete extra white space or line breaks.

I also discovered Forth 40 years ago. It looked viable to me. I recognized then that while it's interactive like BASIC, it's way faster. Also cleaner, because Forth functions are reentrant, usually not relying on global variables.

reply
astrobe_
1 month ago
[-]
> But the topic is readability not viability. Pretty much all programming languages have some readability issues, dealing with them via coding conventions.

Thank you, that's part of what I was saying.

> Nowadays we want our text editors to handle those conventions automatically

But you can't always have what you want. The other guy I was talking to apparently has no idea what it means to deal with an embedded system, hopefully you do.

> I also discovered Forth 40 years ago

And did you write Forth during these 40 decades? I'm apparently a bit younger than you but I did picked and dropped Forth a couple of times for other "nicer" interpreted languages. All in all I think I have a solid 2 decades of practice in Forth. During this time I have constantly changed my idioms, so it's not just doing the same thing for decades and getting used to it.

And I'm saying that readability became less and less of an issue as your writing skills improve. That's also something the other guy refuses to understand.

The arity issue that is often pointed out is not a bigger deal than stumbling on some unknown Javascript, Lua, Lisp or whatever function call and figuring out what each parameter does, especially when it's just a boolean (a bad practice, yet a common practice). I have programs that are made of hundreds of Forth definitions. Arity and expression structure are sometimes an issue, true. When it happens, I just use the search function of my editor.

But why do you specifically forbid yourself to mfk*g split your editor window and go look to the definition of a function when it's Forth, when you do that every day in any other language?

reply
kazinator
1 month ago
[-]
I don't want to look up the semantic definition of an identifier just to understand which chunks in the syntax are its arguments and which are not.

The tide is flowing the other way now. Kids are using editors that integrate deeply with compilers (via Microsoft's Language Server Protocol). The editors are providing code formatting and completion based on communicating with a compiler. That seems like it could work with Forth.

reply
James_K
1 month ago
[-]
> The other guy I was talking to apparently has no idea what it means to deal with an embedded system

I would love to see you justify this. Sadly you will not, because I understand it quite well. It's a pretty basic principal that if a system is capable of handling user input interactively, you can instead connect a more powerful computer and have it receive input from that device. Hence all devices with REPLs can benefit from editors hosted on my own computer. In an embedded system, it is much more likely for you to have to hook your own computer up to something than it is for that thing to be able to accept user input independently. Most embedded devices do not have a USB slot for you to plug your keyboard in.

> That's also something the other guy refuses to understand.

It is a point which I addressed in my other post.

This manner of response is stupid and infantile. I hope you can talk to me directly next time.

reply
astrobe_
1 month ago
[-]
> This manner of response is stupid and infantile. I hope you can talk to me directly next time.

I made it pretty clear I didn't wish to continue that conversation, so I wasn't responding to you. There will probably be no next time, as there's a clear gap in practical experience - on that particular topic - between you and me; it would take far too much energy for me to explain things step by step and I'd gain nothing from it.

reply
James_K
1 month ago
[-]
> I didn't wish to continue that conversation

Then why did you continue the conversation? I'm not forcing you to make snide jabs at me in a public space, and you can hardly blame me for responding to them.

> it would take far too much energy for me to explain things step by step and I'd gain nothing from it

If you cannot explain it simply, you don't understand it well enough.

reply
astrobe_
1 month ago
[-]
> I'm not forcing you to make snide jabs at me in a public space,

Ok, sorry. That was a poor way to invite that person to read the parallel thread. It's a bit frustrating to deal with claims based on nothing but theory when I have at least two decades of practical experience on these topics.

> If you cannot explain it simply, you don't understand it well enough.

First, that answers something I didn't write (of course I can explain those things simply, the problem is that it would easily make a whole article) and secondly, what you wrote ignore the fact that teaching is a skill, so not all experts are good teachers.

reply
James_K
1 month ago
[-]
Okay, that's well and good, but you are sat there saying that you can manually input data into an embedded device, but for some reason can't connect a computer to it. Unless it has a built-in keyboard, that simply isn't true. If it was true, it would be trivial to prove. You could just name the device and situation.
reply
trealira
1 month ago
[-]
Wow, I just learned that Forth programmers do something similar to the "point free" style in Haskell (which is discouraged in excess). For example, if you have a function like this:

  -- f: returns the second argument minus the first
  f :: Int -> Int -> Int
  f x y = y - x
You could technically rewrite it like this:

  f = flip (-)
In practice, I think the flip function is rarely used, except by bots online that rewrite your functions from ones that use variable names into ones that don't, e.g. the website "pointfree.io".
reply
astrobe_
1 month ago
[-]
Yes, Forth is point-free at its core, the only named variables that exist are global variables. The standard defines local variables as an optional extension.
reply
qludes
1 month ago
[-]
The coolest thing about Forth to me wasn't Forth itself but that it was a part of the OpenFirmware standard: https://standards.ieee.org/ieee/1275/1932/ I never understood why that standard just died and wasn't adopted for something like ARM SBCs where the built in debugging ability would come in useful.
reply
anta40
1 month ago
[-]
Me too. For some reason, Lisp is kinda easier for me. And if I need to go low level: assembly.

I read Forth was popular among embedded devs during 80/90s. That's not my field, though.

reply
timonoko
1 month ago
[-]
Forth was the sweetest thing in fixed length machines 60 years ago. When everything is one word, you can assign unique symbol for that word and keep those symbols in separate file (aka paper tape). Some Forth primitives were also machine primitives, like INC [SP] or "++", increase the number at top of stack.
reply
wolfadex
1 month ago
[-]
There are Forth like languages, such as Kitten, that have variables and more.

https://kittenlang.org/

reply
James_K
1 month ago
[-]
Do they have any compelling reason why "List<T>" isn't just "T List"? That's how they write it in OCaml. Then comma separated lists, are they mad? I much prefer how Uiua does this.
reply
alexisread
1 month ago
[-]
Hmm, really nice! I might have to steal all of this for my language (https://github.com/alexisread/minim/blob/develop/minim/minim... really in flux ATM)

Reminds me of https://pygmy.utoh.org/3ins4th.html except the third instruction is execute rather than quote.

In the spirit of building from scratch, I'd like to highlight sectorforth/milliforth (https://github.com/fuzzballcat/milliForth/blob/master/sector...) - they implement as little as possible to get a working system ie. just the basic fetch/store ops, numerical and I/O.

The parser can probably be reduced - there's a nice paper detailing Cognition (a forth dialect, but the parsing could be broken out into a lib - https://ret2pop.nullring.xyz/blog/cognition.html) where using a couple of crank operators, you can implement custom syntax in a forth-style way (as opposed to say PEGs).

In terms of variables and scoping, Dreams (another forth dialect- http://elilabs.com/~rj/dreams/dreams-rep.html) uses mianly dynamic scoping, and builds it's structs (C-style structs) using the standard forth struct words. This allows it to be hard-realtime though as you don't need to walk the lexical tree to bind variables. You can also early bind like with CBPV. I guess this is also similar to REBOL's BINDology (https://github.com/r3n/rebol-wiki/wiki/Bindology)

Lots of overlap here with these things. Just for completeness there's also dynamic variable handling (over lexically scoped structures) with wat (https://github.com/GiacomoCau/wat-js/tree/master) though this is not realtime.

Is it possible to make the closures dynamically scoped by default here? I've not had time to think about this properly. I like the fact that eval is lazy here like in forth or REBOL, rather than eager as in lisp - the idea of passing around blocks/thunks appeals wrt realtime (well with dynamic scoping) and parallelism.

The env per-thread I guess could be compared to the forth dictionary? Dreams abstracts this by virtue of it's (token) threading model which appears useful for task switching, objects and the like.

I'd also like to highlight able forth which has: Compiler-only design (like Freeforth) - Word-classes (like Retro) - A straightforward bootstrap process using a minimal set of seed words (like seedForth) - No (ZERO) special forms, not even integers, and a consistent model for adding literals without the complexity of i.e. recognises - A single flat word-list that can be freely manipulated and composed of other word-lists - No separate [assembly] code words - Explicit optimizations (ONLY) i.e. explicit tail-call elimination

I've only started looking into able forth so can't really comment on it much, but the no-special-forms appeals here.

Sorry, random thoughts here, I'd like to discuss further when I've had time to digest your language. :)

reply
xorvoid
1 month ago
[-]
Happy to discuss. Email address can be found on my website.
reply
kitd
1 month ago
[-]
> It's a hybrid language combining Forth and Lisp, so naturally it's called Forsp

Shame. Missed a golden opportunity to call it "Lithp" :)

reply
qludes
1 month ago
[-]
Someone could still call the libre implementation of Forsp Lithp so not all is lost.
reply
FrustratedMonky
1 month ago
[-]
Nice, anyone using this? Have practical feedback?

Does this have potential to grow, or would most people just say 'use lisp'.

reply
FrustratedMonky
1 month ago
[-]
I only ask because there are hundreds of cool niche languages now. And, even thought this looks cool, time is time, and I'm just wondering if is worth committing any effort to learning/using/playing with.

Is there any feeling that this has legs?

reply