1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
# Compilers are FAKE
_2026 May NOT_
Languages typically either
- follow the "interpreted language" mentality and at best add things
like bytecode cache files or JIT as behind-the-scenes optimizations,
or
- follow the "compiled language" mentality where code written in the
language is a representation of the intended behaviour of an
executable that will eventually be produced.
In the latter model, compile-time logic is often limited to primitive
pre-processors, declarative DSLs or otherwise limited subsystems (C++
templates, Zig comptime, etc.) and often even offloaded to an entirely
separate "build system" language. In the former model, compilation is
swept under the rug like it's shameful dirt.
Why?
How about designing a language that embraces the reality of what it
means to "compile code": Run an executable with source code as input.
Sound familiar? IT'S WHAT AN INTERPRETER DOES! So, in my preferred
language:
- The "interpreter" includes APIs for explicitly compiling any of the
currently defined functions, modules, etc. straight from their in
memory representation (be it AST or bytecode) into native code or
assembly snippets, with the ability to then "serialize" or assemble
and link these to create object files or executables.
- The "compiler" is just a thin wrapper around these APIs, loading
some source files that define functions and globals etc. (all of
which is first interpreted) then compiling them and producing
executables.
- The "top level" of any source file is naturally executed at compile
time. You get meta programming and a build system language "for
free" since these kinds of features can simply be libraries.
Some lisp/scheme implementations already contain an interpreter and
compiler in one, exposing parts of the compiler through APIs, and
allow full compile time code execution via macros. However, in my
experience, they still tend to embrace the traditional interpreted
vs. compiled mentality, which sometimes just makes things awkward.
For example, GNU Guile insists on automatically compiling everything
into a bytecode cache on the filesystem, unless you pass a CLI flag
disabling this behavior. And if you write a Guile Scheme codebase,
you still typically use Autotools and Make to call `guild` to compile
the Scheme files. Just why? You have the full Guile runtime at your
disposal, at compile time... I propose a change in mentality.
## Dial the schizophrenia to 11
Compilers are all FAKE. They just pretend to be compilers. All a
compiler is, is an interpreter for a language that pretends to be a
"compiled language" when in reality it's just an interpreted DSL for
declaring the desired behavior of an executable, which is produced
when the codebase written in this DSL is interpreted ("compiled") by
the interpreter ("compiler").
They have played us for absolute fools.
C is a declarative, interpreted DSL to describe executables.
GCC is an interpreter that runs C, a declarative DSL describing the
executable that GCC should shit out.
|