Back

Forsp: A Forth+Lisp Hybrid Lambda Calculus Language

134 points1 dayxorvoid.com
James_K3 hours 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.

gergo_barany3 hours 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

um115 hours 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.

xorvoid12 hours 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.

diffxx12 hours 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 :)

boltzmann-brain6 hours ago

what is cvbp? google yields nothing.

tonyg6 hours ago

It's a typo for CBPV, Call-By-Push-Value.

boygobbo6 hours ago

I think that was meant to be CBPV (Call-By-Push-Value)

alexisread3 hours 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. :)

xorvoid3 hours ago

Happy to discuss. Email address can be found on my website.

mkovach4 hours 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.

CrociDB4 hours ago

Forth+Lisp = Wet dreams of every programming nerd

pbrhocwp4 hours ago

Very nice! Thanks a lot for the discovery.

tangjurine7 hours 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

tonyg6 hours ago

Could ^a be dispensed with? It looks like (a) is equivalent to it.

xorvoid4 hours 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.

tonyg3 hours 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 :-)

xorvoid3 hours ago

I thought of that. But they you’re making semantics complicated all. 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.

dugmartin5 hours ago

I think the equivalent would be ('a) instead as (a) would resolve to "'result of application of a' push".

tonyg4 hours 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`.

thsksbd12 hours 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

xorvoid12 hours ago

A friend mentioned RPL, but that didn’t seem to even have first-class functions… unless I’m mistaken?

ceving8 hours ago

You can put functions on the stack.

kazinator9 hours 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.

dugmartin5 hours ago

I really like the syntax and ergonomics - nice job @xorvoid.

throw15675422810 hours 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.

alexisread3 hours 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.

nickcw9 hours 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

diffxx12 hours ago

Cool language. One modest suggestion: perhaps return 0 rather than 1. Feature request: addition and division as well as subtraction and multiplication ;)

xorvoid11 hours 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.

bowsamic6 hours 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!

xorvoid4 hours ago

Ha. Well I meant “add those operations to the C interpreter”, which would be like 4 lines of code.

FrustratedMonky5 hours ago

Nice, anyone using this? Have practical feedback?

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