5 ms·
There was a burst of interest in concatenative languages a few years back - essentially higher level variations on the features found in forth. https://en.wikip
by InternalCake 7y ago
There was a burst of interest in concatenative languages a few years back - essentially higher level variations on the features found in forth.
https://en.wikipedia.org/wiki/Concatenative_programming_language https://en.wikipedia.org/wiki/Concatenative_programming_lang...
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.
- codr7 7y agoThey make great foundations, since their simplicity allows them to be conveniently adapted to whatever purpose. 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.
- 0xdeadbeefbabe 7y agoForth has a data stack and return stack. Messing with the return stack seems like a practical joke on the programmer sometimes. I guess we don't live with the same constraints that made forth popular in the first place.
- codr7 7y agoI'm curious, what kind of messing with the return stack are you talking about? The only thing forthy2 does that is slightly out of the ordinary is TCO.
- 0xdeadbeefbabe 7y agoIn eforth doVAR is R>
- codr7 7y agoAh yes, hardcore Forthers do seem to take pride in finding the absolute smallest number of fundamental building blocks. There comes a point, though; where pretending everything is the same complicates rather than simplifies.
- notduncansmith 7y agoWhile I would rather call it acknowledging than pretending, I agree wholeheartedly with the sentiment and consider this one of the most prevalent and pernicious fallacies of system design (though I may only feel that way because it had such a strong hold on me for so long).
- codr7 7y agoRabbit holes, you can spot them in most projects. Where someone got carried away with some sort of enlightenment. Oh, I know, everything is X. Well, no. Everything is, and always will be.
- carapace 7y agoI've been playing with Joy the last few years. (My project: https://joypy.osdn.io/ https://joypy.osdn.io/ ) 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.
- journalctl 7y agoI had a lot of fun playing with Factor several years ago. The problem, as you pointed out, was that it was indeed difficult to reason about programs. The stack is implicit, so when you glance at code to try to figure out what it does, you have to manipulate the stack in your head as you think about it, effectively carrying out the computation anyway. That became too much for me after a while, and I gave up. Maybe I should pick it up again, though.