> I also like the idea of modifying function definitions at runtime. I have these visions/nightmares of programs that take other programs as input and then let me run experiments on how the program behaves under certain changes to the source code. I want to write metaprograms dammit
I've been working on an extremely dynamic programming language for a while. Ricing away on the syntax to make it as flexible as possible, whilst maintaining readability.
One of the things that already works is some insane metaprogramming. It doesn't really have functions. It has callable lists of things. Which means you can modify them at runtime (still working on the names for the functions for doing that).
It is stack-based, so it's a little quirky, but as a taste of some very simple meta-programming:
block
varname:
add! 1 $ $varname
: $varname
end
inc-env:
a: 1
inc-env! a
Bare-words are a symbol-type. So they only resolve from the environment when you ask for it with the $ instruction, which is how the above works. A little tedious, but does enable all kinds of dynamism.
Neat! Reminds me of the Lisps around that still have f-exprs, like NewLisp (f-exprs are functions that take their arguments unevaluated, functions are mutable lists of code you can overwrite, etc)
R (yes, the statistics language) has exactly this.
You can literally extract the body of a function as a list of "call" objects (which are themselves just dressed-up lists of symbols), inject/delete/modify individual statements, and then re-cast your new list to a new function object. Or you can construct new functions from scratch this way.
I've written utilities before that will inline specific function calls within the body of another function, or that implement function composition by constructing a new function from scratch.
I don't know why the original devs thought this was necessary or even desirable in a statistics package, but it turns out to be a lot of fun to program with. It has also made possible a wide variety of clever and elegant custom syntaxes, such as a pipe infix operator implemented as a 3rd-party library without any custom language extensions [0]. The pipe infix operator got so popular that it was eventually made part of the language core syntax in version 4.1 [1].
Absolutely! That and Vau Calculus are the inspirations. My vague thought that started it all was that if you take an f-expr, and apply it to a stack machine, you might be able to take a stack-based language from a toy and turn it into something that could look and act professional.
PostScript is one of the very few stack-based languages that is used anywhere, and it's mostly used in anger. Forth has a bit of a following, but the syntax isn't as regular as Lisp, which makes metaprogramming a tad annoying at times. So I was curious if I could use the f-expr to change both of those. Thus far... I think it's a workable concept.
The fexpr idea is at the very heart of Lisp, which I think makes it truly transposable to other paradigms. Alan Kay credits the fexpr as one of the inspirations for Smalltalk[0] taken from Lisp:
> My next question was, why on earth call it a functional language? Why not just base everything on FEXPRs and force evaluation on the receiving side when needed? I could never get a good answer, but the question was very helpful when it came time to invent Smalltalk, because this started a line of thought that said "take the hardest and most profound thing you need to do, make it great, and then build every easier thing out of it". That was the promise of LISP and the lure of lambda—needed was a better "hardest and most profound" thing. Objects should be it.
You can see the basic idea for the Vau calculus in that quote. Shutt (RIP)[1] later elaborated the theory and proved they were not "trivial" and anathema to compilation [2]. It's great to see new interpretations and uses of the fexpr!
Oh cool, another Vau Calculus enthusiast! There are dozens of us :D
F-exprs for stack machines is very interesting, I'll have to ponder on that - do you have somewhere I can follow your work on your language?
> ...do you have somewhere I can follow your work on your language?
Not really. Not committing to anything publicly has let me feel more free to tear down and start from scratch an embarrassing number of times, and experiment a bit more. Makes it easier not to get over-invested in any one technique.
I have written a little bit, as I've slowly changed my approach. But it's haphazard. Not a whole heap. You can, though, see a few of the previous versions, that do "work":
It's still evolving. You can see where some things have stuck, and others have been tossed by the wayside. The current incarnation isn't public at all, yet.
If I ever get satisfied that I've got the right approach, I'll probably do a big write-up, then.
Well, now it's been said... Scheme is my all-time favourite language. The regularity of Scheme's syntax, and the way scoping works in the language, are certainly influential on the way I'm going about designing this weird thing.
It is simpler in some ways, mostly because scheme has a "syntax tower" where even the most basic things can be understood in terms of other things. Let can be defined in terms of lambda. Let* in terms of let. Letrec* is also not that hard.
Once you know this and the difference between definition context and bodies (which is simple) there isn't much to add. The top-level works mostly like letrec*. Internal definitions work like letrec* Libraries work like letrec*
I've been working on an extremely dynamic programming language for a while. Ricing away on the syntax to make it as flexible as possible, whilst maintaining readability.
One of the things that already works is some insane metaprogramming. It doesn't really have functions. It has callable lists of things. Which means you can modify them at runtime (still working on the names for the functions for doing that).
It is stack-based, so it's a little quirky, but as a taste of some very simple meta-programming:
Bare-words are a symbol-type. So they only resolve from the environment when you ask for it with the $ instruction, which is how the above works. A little tedious, but does enable all kinds of dynamism.