Show HN: forthy2 – a higher level Forth-remix in C++
github.com
github.com
They're fascinating and one would hope (well, I did) that the structural simplicity of them would yield something that's amenable to automatic manipulations. In practice the lack of restrictions on what you can do to the stack makes it almost impossible to reason about programs written in such languages.
I too find dogmatic Forth a struggle to reason about, which is why we're here I guess. A type system in combination with generic methods help a lot, since most mistakes are caught in dispatch and give reasonable error messages.
The only thing forthy2 does that is slightly out of the ordinary is TCO.
There comes a point, though; where pretending everything is the same complicates rather than simplifies.
My experience has been that, once you get used to it, it's much easier to reason about Joy expressions than other notations/languages. The power of the combinators makes it easy to design functions.
Judge for yourself, here's the "Tak" function:
def tak(x, y, z):
if y < x:
result = tak(
tak(x-1, y, z),
tak(y-1, z, x),
tak(z-1, x, y)
)
else:
result = y
return result
'''Let's derive tak in Joy.
We know it's a recursive function that applies itself four times, so it should be pretty interesting. The genrec combinator expects four quoted programs:
x y z [P] [T] [R0] [R1] genrec
The predicate and base case are easy: x y z [pop >] [pop popd] [R0] [R1] genrec
This leaves us with the two recursive quotes to be derived. We want something
like this: x y z R0 [tak] R1
------------------------------------------------
x -- y z tak y -- z x tak z -- x y tak tak
If we assume that the three input vars have already been duplicated twice then
it's simple enough to write three small functions that prepare each triplet of
values for tak: x y z [--] dipd tak x y z [--] dip rolldown tak x y z -- rollup tak tak
Considering just these three sub-functions in parallel... x y z [--] dipd tak
x y z [--] dip rolldown tak
x y z -- rollup tak
...we let them be quoted and executed with the i combinator. x y z [[--] dipd] i tak
x y z [[--] dip rolldown] i tak
x y z [ -- rollup] i tak
We then quote [i tak] and extract it from the three sub-functions with app3. x y z [[--] dipd] [[--] dip rolldown] [-- rollup] [i tak] app3
This reduces the number of appearances of tak from four to two and takes care
of providing the input values to each sub-function without explicit duplication.
To complete the tak function we have to ditch the original three values
from the stack and call tak one more time: x y z x' y' z' [popop pop] dipdd tak
Let's define some helper functions and review: K0 == [--] dipd
K1 == [--] dip rolldown
K2 == -- rollup
popopopddd == [popop pop] dipdd
x y z R0 [tak] R1
-----------------------------------------------------
x y z [K0] [K1] [K2] [i tak] app3 popopopddd tak
From here we proceed somewhat automatically, our goal is to have a single
quoted [tak] in our expression. First, quote the bare tak and the cleanup
function and execute them with i, and extract the app3 call with dip: x y z [K0] [K1] [K2] [i tak] app3 [popopopddd tak] i
x y z [K0] [K1] [K2] [i tak] [popopopddd tak] [app3] dip i
(If popopopddd represented a value rather than a function we might have used
dipd on [app3] and just quoted tak. Or we could quote [app3 popopopddd] and
use dip. However, doing it this way gives us a symmetry that we're going to
use in the next step.)This leaves two quoted functions that end in tak. Carve off [tak] from each with concat and abstract with the ii combinator:
x y z [K0] [K1] [K2] [i] [tak] concat [popopopddd] [tak] concat [app3] dip i
x y z [K0] [K1] [K2] [i] [popopopddd] [[tak] concat] ii [app3] dip i
We now have one appearance of [tak] but it's quoted, get it out with cons: x y z [K0] [K1] [K2] [i] [popopopddd] [tak] [concat] cons ii [app3] dip i
And we are done. We have definitions for R0 and R1: R0 == [K0] [K1] [K2] [i] [popopopddd]
R1 == [concat] cons ii [app3] dip i
For ease of understanding, here is the above sequence played forward, as it were: x y z [K0] [K1] [K2] [i] [popopopddd] [tak] [concat] cons ii [app3] dip i
x y z [K0] [K1] [K2] [i] [popopopddd] [[tak] concat] ii [app3] dip i
x y z [K0] [K1] [K2] [i] [tak] concat [popopopddd] [tak] concat [app3] dip i
x y z [K0] [K1] [K2] [i tak] [popopopddd tak] [app3] dip i
x y z [K0] [K1] [K2] [i tak] app3 [popopopddd tak] i
x y z ... [popopopddd tak] i
x y z x' y' z' [popopopddd tak] i
x y z x' y' z' popopopddd tak
x' y' z' tak
Putting it all together, we have a new library function (that needs a better name)
and five wee auxiliary functions to make tak: popopopddd == [popop pop] dipdd
tak.x == [--] dipd
tak.y == [--] dip rolldown
tak.z == -- rollup
tak.R0 == [tak.x] [tak.y] [tak.z] [i] [popopopddd]
tak.R1 == [concat] cons ii [app3] dip i
tak == [pop >] [pop popd] [tak.R0] [tak.R1] genrec
The advanced combinator shuffling in the definition of tak is geometrically simple.The patterns I used to manipulate the program code are very simple. In Joy, metaprogramming is easy, and most programs are meta-programs in the sense that they build other programs and then execute them as a matter of course.
I should note that I'm working too hard maybe, Factor language provides for something like e.g. writing:
x y z [tak] foo
------------------------------------------------
x -- y z tak y -- z x tak z -- x y tak tak
where: foo == [\3 -- \2 \1 \0 i \2 -- \1 \3 \0 i \1 -- \3 \2 \0 dup b] interpolate i
It's a quote with special interpolation markers that are replaced by the stack
item that they index (meaning \0 is replaces with [tak], \1 with z, etc.) followed
by an interpolate function that does the replacing.A facility like this is pretty convenient and almost certainly more efficient. To make it work in Joy there would have to be some modifications to support the special interpolation markers.
On the other hand, the use of app3 implies concurrency that is not explicit in the interpolated version.
I'm only half joking.
I'm glad you find pleasure in Joy, really am, programming should be more enjoyable. And having only read about it, I feel like they're related in spirit.
But this looks like madness to me. There's just too much stuff in there that has nothing to do with the algorithm. It feels like stacking monads in Haskell. Sure, it's cool and all, I mean that it works. And beautiful on one level. But still madness, as far as solving problems go.
It looks like madness because you're not familiar with it. (APL has the same problem, and APL programmers insist they can read it.)
I've found that Joy makes it really easy to derive programs mathematically, in a simple, almost geometric way.
It's the same story wherever you look, true Haskell believers make exactly the same claims. You just need to get used to it. And I'm sure I could, it worked for a little while in Haskell.
But as soon as I'm back using practical minded, general purpose tools; it comes back to me and I swear to never ever accept anything that doesn't make sense to me again.
This is not about being different, if you think that's the case you probably didn't take a very close look at forthy2. If it walks and quacks like a religious cult, that's what it is.
The issue is this:
> (1 2 3) 4 push
My expectation is this should yield "(1 2 3 4)". It doesn't. For that you need the following:
> (1 2 3) copy 4 push
Why is copy necessary here?
I'm still undecided on the stack ops. There is one case in forthy2 where a reference is left on the stack, and that is splicing values into forms [0], where it's more likely you want to keep working with the same form. More likely is not a very good heuristic for designing API's though, so I might just go with the behavior you expected.
Bodies, or scopes, are first class though. You can take their references and/or quote them.
I get the feeling that PostScript is close in spirit, they were also into pimping Forth using Lisp as inspiration. I think some of the HP-calculators also had similar languages, but never used them myself.
For me, generic methods (or words in Forth lingo) is the biggest deal. Followed by scopes and compound literals such as stacks and scopes.
CMake Error at CMakeLists.txt:6 (add_link_options):
Unknown CMake command "add_link_options".Anyways, the easiest way forward for you is most probably to upgrade your CMake.
https://cmake.org/cmake/help/v3.13/command/add_link_options....
And I agree: once you have a directive for checking versions, knowing which goes where is pretty critical information. Maybe someone should remind them?
So no, I didn't do anything :) It's all C++, all the way.
I suspect most standard Forths don't give a crap about the CPU, they like to implement everything themselves.