I've been having a blast building a statically typ...
# devlog-together
j
I've been having a blast building a statically typed "lisp" I'm calling coil. It is very much alpha quality right now. But it lives in a space that no other language I know does and has been quite fun to build things with because of it. • As low level as C; Manual memory, totally unsafe (by default) • Has sums + structs + traits + derive like rust • Has the most powerful metaprogramming system I've personally seen It has the standard things you think of with lisp macros. But where it really becomes powerful is in its "metaprogram" system. Which is a concept I stole from jblows Jai. Though it is a bit different here I'm sure. A metaprogram is a complete arbitrary computation at compiletime that is fed your whole program. Right now the interface isn't in its final form. But here are some examples of what you can do.
Copy code
~/Documents/Code/projects/coil-experiments main
❯ cat tests/brainfuck/hello.bf
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.
<<+++++++++++++++.>.+++.------.--------.>+.>.

~/Documents/Code/projects/coil-experiments main
❯ coil run tests/brainfuck/hello.bf --use experiments.brainfuck.lang
Hello World!
The metaprogram here overrides the reader, making it so that bf is completely valid syntax in our language. The resulting program is all coil. No runtime component at all. I have taken this way further than bf. I made a C metaprogram. Took all of doom, and now doom works in my language. No C at runtime. Only coil. Hopefully soon I'll have some demos to share for all of the amazing things you can do with metaprograms.
🍰 2
👀 2
🤯 4
j
How is
Copy code
coil run tests/brainfuck/hello.bf --use experiments.brainfuck.lang
different from
Copy code
cat tests/brainfuck/hello.bf | coil run convert-bf-to-coil | coil run --from-std-in
? That is, what makes “metaprogramming” here different from “compiling” (or, if you prefer, “transpiling”)?
j
@Joshua Horowitz So this one example is compiling for sure. But it is perhaps a bad example of a general metaprogram. It just a reader metaprogram that turns text into Code objects. In this case, it just goes ahead and compiles them. In general, a metaprogram is handed the whole entire source of a program. Not as text, not just as s-expression, but it can get semantic information like the resolved types. A metaprogram can do anything it wants with this code. It can transform it into other code. It can record facts about it. It can ignore it. So for example, to write a linter you just need to write a metaprogram. Here is a simple test example of a linter. This is not a compiler unless by compiler you just mean data -> data. So with this you can do a lot of things. For example, coil makes function calls a purely static thing, no indirection. But I wanted redefining functions to work in the repl. So for the repl I have a metaprogram that introduces the indirection. This kind of whole program transformation lets me do a ton of things. Here are a few: 1. Hot reloading of functions 2. Track allocations 3. Introduce safety like bounds checking across the whole program (zig style safety) 4. Add automatic GC 5. A scheme implementation (include the gc above) 6. A live coding environment including redefining structs at runtime (this but for coil) 7. Things like type providers (f# feature) where at compile time you go and fetch stuff in to world to generate code. 8. Run a gui at compile time. It is literally arbitrary execution at compile time, that is handed the full source of your program (not itself) and then can do whatever it wants with it. Many of these could be done with compilation. But this is a built-in facility. It runs as part of the compiler. You get the type checked information. You get things like being able to stage addition levels of compilation. You get to decide how the compiler reads code. You can decide what the compiler compiles. I think of this not as the ability to write a compiler, but to arbitrarily extend your langauge's existing compiler.
❤️ 2
j
That’s cool. I’m looking forward to seeing some of those examples. 👍
2
j
This is breaking my brain a little bit. How is it different from metaprogramming in other languages? Just that in most languages "a program in this language" is not a primitive type that you can modify as you like?
j
So yeah it differs in a few ways from most metaprogramming. 1) It is full total arbitrary computation. No limits at all. 2) It is given the whole entire program, not some scoped subset. 3) They are invisible to your program meaning, nothing at all about them shows up. So coil still has macros, which are a form of metaprogramming. Macros do 1 but not 2 or 3. It's this combo that makes them so interesting (to me)
j
It occurs to me that in extreme forms of harness engineering, where there are a bunch of rules enforced in the CI/CD process to keep the agents on best-practice training wheel rails, being able to make the rules as arbitrarily deep as you like, and throw compile time errors with suggestions, would offer some token savings and correctness guarantees you couldn't get in a lot of other languages. 🤔
j
Absolutely! That's one of the reason I focused on some nice lint with auto fixes. I also made a meta program that makes markdown a file you can import. The metaprogram then calls an llm, has it write the code, type checks it, can even run tests, and saves that file as the implementation. So you can scaffold whole projects in markdown and generate them via compile.
🤣 1
j
LLM code generation as a compile step!
I haven't learned a compiled system language in nearly 10 years, but that is very tempting.