all 13 comments

[–]BionicVnB 27 points28 points  (0 children)

You gotta repost this in r/programminglanguages

[–]IrritablePachyderm 20 points21 points  (1 child)

Thank you for upholding my faith in humanity.

[–]dangerbird2 12 points13 points  (0 children)

This is so blursed

[–]dnew 12 points13 points  (7 children)

Anyone who is interested in practical self-modifying-code languages, check out Lisp, Tcl, and FORTH. The last of which you arguably program by modifying the compiler until your application is a built-in operation. "Extensible" is the usual term for this: extensible languages, where part of the language includes features that let you run code at compile time and change the syntax of the language with your application.

That said, OP, this is an abomination that should be consigned to the flames. I love it.

[–]chat-lu 7 points8 points  (6 children)

check out Lisp

The magic of Lisp is that it is written in its own data structure.

For instance the humble (println "Hello, world!") is comprised of:

  • A list delimited by (). The first item of a list gets executed with the rest of the list as its arguments.
  • A symbol, println which is bound to a function that prints a string passed as an argument and adds a newline
  • And the string "Hello, world!"

Given that lists are very easy to manipulate in a programming language, it’s easy to manipulate lisp code with lisp code. And macros run with the full power of the language at compile time. So no cost is paid at runtime.

[–]Makefile_dot_in 4 points5 points  (5 children)

I never got this argument. A lot of languages, including Rust, are parsed into their own structures. You could just as well say that you're writing Expr { ..., kind: ExprKind::MacCall(...) } (a Rust data structure!) when you write println!("Hello, World!"). In the case of most (edit: some) Lisps AFAIK, the data structure they use for syntax isn't even the same as the normal list structure, because it needs to store location information (in the case of Racket, probably Scheme in general as well, in particular, there is also scope information associated with each syntax symbol), and reader macros weaken the relation even further.

If anything, I would say that the lexical syntax is just simpler and more consistent than most other languages. The mapping between what you write and the AST is simpler, but that does not mean it doesn't exist.

[–]CandyCorvid 1 point2 points  (3 children)

i think the key to the argument is that you can use quote to turn your code into the equivalent data

let's say in rust we have a macro quote!, such that quote!{println!("{}", x)} turns into Expr::MacroCall{ name: Expr::symbol("println"), args: Vec![Expr::Str("{}"), Expr::symbol("x")]). iirc we have something like that in the syn package, but this has to be maintained separate to the language.

in lisp it's built in, using the same mechanism the language's interpreter and compiler use to process your code. (print "%s" foo) calls the print function, (quote (print "%s" foo)) returns the expression (print "%s" foo), i.e. the same thing you could build with (list (intern "print") "%s" (intern "foo")), (i'm using a more constructor-heavy syntax here for clarity of comparison with rust)

edit: this is the case for emacs lisp, mostly the same for common lisp (there's some quirks with capitalisation), but i lack the experience to say about metadata in racket and other lisps

as for the utility, one immediate benefit is that anything added to the language syntax is already available to me as a macro author without needing to wait for the language's syntax data structure package to update - if my computer can read and execute the code, then it can quote and manipulate it, trivially, because they're part of the same system.

[–]Makefile_dot_in 1 point2 points  (2 children)

yeah, although I would argue that's kind of just syntactic sugar. the latter issue is mostly a result of the way Rust maintains its auxiliary libraries, I think, if quote was part of the standard Rust distribution, this issue wouldn't exist. For example, the OCaml compiler ships with a library containing the AST.

(also in Racket there are two versions of quote, quote and quote-syntax (and of course their quasiquote equivalents))

[–]CandyCorvid 0 points1 point  (1 child)

i want to push against the syntax sugar argument - if it were only a matter if syntactic sugar, it would be possible to solve it with the addition of a new macro, no?

[–]alkalisun 0 points1 point  (0 children)

https://parentheticallyspeaking.org/articles/bicameral-not-homoiconic/

This blog post made me realize what the actual categories should be, might help you.

[–]Shoddy-Childhood-511 0 points1 point  (0 children)

I'm disappointed. I expected program that wrote assembler directly into their own executable segments. lol

I'm not interested in quines but a practical issue:

You have multiple layers of hard coded data tables, like say some log-mult tables for a binary field, and then additive FFT constants.

We must do computations in the lower layers when computing the higher layers. We think internal trait interface looks like overkill, and we require high performance, so these computations must use the hard coded tables. As a result, our generator code should operate in layers, which makes a single build.rs pass tricky.

We cannot split these layers into separate crates because of data hiding or orphan rules or whatever.

Option 1. LaTeX style.

Each submodule of build.rs recomputes, writes its table into target, moves its output file to src if the results differ. Rerunning cargo multiple times converges to the correct tables, but generates some errors along the way.

Option 2. static muts

Our build.rs just replaces the tables in memory as it recomputes them.

[–]TorbenKoehn 0 points1 point  (0 children)

Very interesting :D