
Forth Lisp Python Continuum: A small highly-dynamic self-bootstrapping language - kick
https://github.com/asrp/flpc
======
asrp
Author here! Go ahead and ask if you have any questions about internals or
otherwise. Or e-mail me if you think of your question much later (e-mail in
profile).

I'm surprised it made it here even during a Github outage.

------
kick
Since Github is down, here's another link on this that's interesting.

On the steps it took to get it self-hosted, published a few days ago:

[https://blog.asrpo.com/flpc_is_now_self_hosted](https://blog.asrpo.com/flpc_is_now_self_hosted)

------
buzzkillington
>Python is Lisp with syntactic sugar and Lisp is Forth with syntactic sugar.

Those languages have nothing in common other than being pleasant to work with
because there isn't too much boiler plate.

Python is dictionary based, lisp is list based and forth is stack based. And
that's just the base languages without the fancy stuff like macros and
continuations in lisp, all the new syntactic sugar in python (:=, etc) and the
bit tricks in forth (-1 = true, 0 = false).

~~~
kick
_The Forth Lisp Python Continuum is a language made under the following
incorrect assumption._

It points out that it's not a perfect comparison.

However, it's kind of absurd to imply that Lisp and Forth have nothing in
common.

~~~
msla
Forth and Lisp are distorted reflections of each other: Forth intends to make
it easy to write programs with minimal abstractions, whereas Lisp intends to
make it easy to write programs with _any_ abstraction you want. They both,
therefore, converged on the notion of having minimal syntax for opposite
reasons: Forth eschews most syntax because syntax is an abstraction, Lisp
because it isn't necessarily _that specific programmer 's_ abstraction.

Python, OTOH, is a single, "frozen" M-synax for a Scheme-like Lisp descendant;
if Lisp is a pantry of ingredients and a kitchen, Python is fast food, or a
microwave dinner, albeit a relatively good example of such.

~~~
klibertp
> is a single, "frozen" M-syntax for a Scheme-like Lisp descendant

Sounds very close to what Dylan is/was. Well, it still _is_ , in the sense
that there's a working, open source implementation (over at
[https://opendylan.org/](https://opendylan.org/)), but it basically lacks the
last 20 years of optimizations, which makes it, unfortunately, really slow.
The ecosystem is also non-existent and very far from ergonomic. Still, it's a
very interesting language - basically Scheme + CLOS in M-syntax (though it
does have syntax-rules-like macros, so maybe not as "frozen" as Python here.)

------
yesenadam
At first glance, I love it! For years I programmed mainly in Cython, where you
can float in-between Python and C, making each line as C-like or Python-like
as you want, whichever suits the needs of the moment better, and can use
packages/libraries from both. I liked that floaty feeling, but having 3
dimensions is a whole new ball-game.

I have to share what n-gate said about Cython, it's so funny and true:

 _...a horrible chimera of a programming language, wherein the drawbacks and
limitations of Python are augmented by the drawbacks and limitations of C. The
result is a language that introduces header files to Python and requires
breathtaking amounts of boilerplate. The primary goal of Cython appears to be
transforming the programming experience from "implementing a solution to a
given problem" to "trying to guess when to turn off exception handling so that
your code runs marginally faster."_

[http://n-gate.com/fosdem/](http://n-gate.com/fosdem/)

~~~
boothby
Okay okay, I take issue with the last sentence of that (specifically the word
"marginal"). The aim of Cython is to provide a smooth slope from "quickly
writing comprehensible programs in Python" to "belaboring bits and mallocs in
C to crush the performance of that Python crap", where the optimal Cython
experience is "profile, identify a huge hotspot, and do that little bit in
C"... and then brace yourself for that boilerplate, boy howdy.

These days I write a lot of C++ and using it through Cython wrappers. With
both Python and C++ redefining themselves, each at a breakneck pace, I
frequently hope that they'll converge to a common language. But then I
remember that I know that devil, and its name is Cython.

~~~
pjmlp
Like Cling. :)

[https://developer.nvidia.com/gtc/2020/video/s21588](https://developer.nvidia.com/gtc/2020/video/s21588)

However, best way would be just having a JIT by default.

------
jes5199
this is really cool! I've actually been wondering for a while what a language
that's both Lisp and something concatinative would look like, but I keep
getting tangled up without actually solving it. I'm going to study this one

~~~
asrp
If you want to read this, I'd suggest looking at the sources in bootstrap
sequence from the readme (boot.flpc, stage0.flpc, ...). Alongside, run some of
the precompiled entries by hand by pasting it into the interpreter. Call `ps`
once in a while to see the current state. Then for larger chunks of code
invoke breakpoints by calling `debugger()` (in a `.flpc` file) or `debugger`
(in a `.f` file though this will mess up source position printing beyond this
point).

And of course, feel free to just ask!

------
jjcc
Wow! That's wonderful!. Forth and Lisp were my favorite languages long long
time ago. Python is my current daily use language among others. I even don't
know they are connected.

~~~
asrp
Here's what I was thinking when I wrote the "incorrect assumption" line. But I
think many other interpretations are also valid.

You can think of

    
    
        sum([x*x for x in range(10)])
    

as "desugaring" to

    
    
        sum(list_comp(lambda(x, quote(multiply(x, x))), range(10)))
    

which looks like Lisp if you move every open parens one token to the left and
remove the commas

    
    
        (sum (list_comp (lambda x (quote (multiply x x))) (range 10)))
    

To evaluate this, parameters are first recursively evaluated (in order) and
then the function is called on the outer value. Let's ignore the lambda for
the moment.

    
    
        (sum (list_comp quoted_inner_func (range 10)))
    

results in the following function calls are made at execution time _in this
order_

    
    
        quoted_inner_func 10 range list_comp sum
    

Normally, you'd have to pass the correct parameter to each function. However,
in Forth, we use a global parameter stack so provided all the functions
respect their inputs and output, running the above body would provide the
desired result on the parameter stack!

~~~
jjcc
Thanks. That's a good explanation. I like "desugaring"

------
galaxyLogic
> Eventually, the grammar itself will be modifiable at runtime

Wouldn't that make programs hard to understand since you can't learn the
syntax for once and all. It's like having a book with different pages written
in 3 different languages, to understand it you need to understand all those
languages.

~~~
mafm
Lisp let's you modify its syntax. You're making the standard argument about
why that might be a bad idea. At least for Lisp, historically it worked pretty
well. As new ideas came along, a lot of the good ones got incorporated in Lisp
as syntax extensions.

Overloading in C++ is kind of similar. Im not sure that worked out so well in
practice.

------
jgrant27
A Ruby subset in Common Lisp (circa 2010)
[https://imagine27.com/ruby_subset_implementation](https://imagine27.com/ruby_subset_implementation)

------
hota_mazi
Oh dear...

[https://github.com/asrp/flpc/blob/master/precompiled/flpc-
ge...](https://github.com/asrp/flpc/blob/master/precompiled/flpc-gen.f)

~~~
asrp
It is actually not hard to read (at least in the sense of knowing what
something will do when executed; getting the bigger picture takes more
practice). The (base) syntax is just whitespace delimited tokens, each
representing a function call (or string but we'll come to that later). So

    
    
        foo bar baz
    

will call the 3 functions in order

foo() bar() baz()

All functions are nullary (with side effects; these side effects determine
their "true" arity). There aren't really any special characters other than
whitespace so

    
    
        1 1 + print
    

just translates to

    
    
        1()
        1()
        +()
        print()
    

Function names do not have to start with a letter or be alphanumeric. I've
happened to name my function so that those ending in a colon treats the next
token to the right as a string instead of a function call. The [ function
treats everything as strings until ] (that is a single close square bracket as
a token) and puts the function in those body in a quote, effectively creating
an anonymous function. So

[ foo bar baz ] bind: somename

defines a function. The equivalent in Python would be

    
    
        def somename():
            foo()
            bar()
            baz()
    

And you can later call somename in later functions.

