4 ms·
I quite intentionally said "within the language" to preempt nonsense like "Of course, you can. C is perfectly capable of writing C interpreters and compilers."
by jibal 25d ago
I quite intentionally said "within the language" to preempt nonsense like "Of course, you can. C is perfectly capable of writing C interpreters and compilers."
- eru 25d agoWhat does 'within the language' mean? Is the standard library part of the language? Would a variant of C that came with an interpreter in the standard library (but no other changes) count as homoiconic?
- Zambyte 25d agoNo, that would not count as homoiconic. Homoiconicity means that a language natively supports its own syntax as a data structure. I used AI to help write an example of what C would look like if it were homoiconic. You should mainly look at the things that use `code{...}`. Note that the contents of the `code` value is precisely valid C syntax. It's not a string value that would require a C parser to operate on, and it's not a user-level tree using structs of pointers to operate on and different syntax to construct from what you actually write. #include <hc.h> int main(void) { /* 1. Symbols: interned, identity-comparable. */ symbol x = #x; symbol x2 = intern("x"); assert(x == x2); // same identity, not just strcmp assert(x != #y); printf("symbol name: %s\n", symbol_name(x)); // -> "x" /* 2. Code is written in *the same syntax* as code that runs. * `code{ 1 + 2 }` is a value of type `code` whose printed * form is literally "1 + 2". No separate DSL. */ code expr = code{ 1 + 2 }; printf("as source: %s\n", code_to_string(expr)); // -> "1 + 2" printf("evaluates: %ld\n", (long)eval(expr)); // -> 3 /* 3. Build the same AST programmatically; it is structurally * equal to the literal above. */ code built = code_add(code_int(1), code_int(2)); assert(code_equal(expr, built)); /* 4. Quasiquote: a code template with a hole, written in C syntax. * `~n` splices the runtime value of n into the form. */ long n = 40; code tpl = code{ ~n + (1 + 1) }; printf("template: %s\n", code_to_string(tpl)); // -> "40 + (1 + 1)" printf("evaluates: %ld\n", (long)eval(tpl)); // -> 42 /* 5. A program can rewrite its own code, because code is data. * Double every integer literal in a form. */ code e2 = code{ (1 + 1) + 2 }; code doubled = map_code(e2, double_int_literals); printf("rewritten: %s = %ld\n", code_to_string(doubled), (long)eval(doubled)); // -> "(2 + 2) + 4 = 8" /* 6. Definitions are code too. Write the definition in C syntax, * then eval the code value to install it at runtime. */ code square_def = code{ long square(long x) { return x * x; } }; eval(square_def); printf("square(7) = %ld\n", (long)eval(code{ square(7) })); // -> 49 /* 7. Macros: compile-time functions from code to code, written * in the same syntax they transform. */ code swap_macro = code{ macro swap(a, b) { a = a ^ b; b = a ^ b; a = a ^ b; } }; eval(swap_macro); // install the macro eval(code{ long u = 1; }); eval(code{ long v = 2; }); eval(code{ swap(u, v); }); // macro expands, then runs printf("after swap: u=%ld v=%ld\n", (long)eval(code{ u }), (long)eval(code{ v })); // -> u=2 v=1 return 0; } Here is the code translated directly to S-expression syntax. This is not exactly conventional Lisp in terms of how variables are defined (explicitly typed instead of inferred is a bit weird for Lisp), but I wanted it to be as close as possible to see the parallels. (include <hc.h>) (defun int main ((void)) ;; 1. Symbols: interned, identity-comparable. (symbol x #x) (symbol x2 (intern "x")) (assert (== x x2)) ; same identity, not just strcmp (assert (!= x #y)) (printf "symbol name: %s\n" (symbol_name x)) ; -> "x" ;; 2. Code is written in the *same syntax* as code that runs. ;; Now that the whole language is uniform, a plain quote is all ;; it takes: '(+ 1 2) is a `code` value that prints back as ;; "(+ 1 2)". (code expr '(+ 1 2)) (printf "as source: %s\n" (code_to_string expr)) ; -> "(+ 1 2)" (printf "evaluates: %ld\n" (long (eval expr))) ; -> 3 ;; 3. Build the same AST programmatically; structurally equal. (code built (code_add (code_int 1) (code_int 2))) (assert (code_equal expr built)) ;; 4. Quasiquote: a template with a hole. ,n splices n's value. (long n 40) (code tpl `(+ ,n (+ 1 1))) (printf "template: %s\n" (code_to_string tpl)) ; -> "(+ 40 (+ 1 1))" (printf "evaluates: %ld\n" (long (eval tpl))) ; -> 42 ;; 5. A program can rewrite its own code. Double every int literal. (code e2 '(+ (+ 1 1) 2)) (code doubled (map_code e2 double_int_literals)) (printf "rewritten: %s = %ld\n" (code_to_string doubled) (long (eval doubled))) ; -> "(+ (+ 2 2) 4) = 8" ;; 6. Definitions are code too. (code square_def '(defun long square ((long x)) (* x x))) (eval square_def) (printf "square(7) = %ld\n" (long (eval '(square 7)))) ; -> 49 ;; 7. Macros: compile-time code -> code, written in the same ;; syntax they transform. (code swap_macro '(defmacro swap (a b) (set a (^ a b)) (set b (^ a b)) (set a (^ a b)))) (eval swap_macro) ; install the macro (eval '(long u 1)) (eval '(long v 2)) (eval '(swap u v)) ; macroexpands, then runs (printf "after swap: u=%ld v=%ld\n" (long (eval 'u)) (long (eval 'v))) ; -> u=2 v=1 (return 0))
- deleted 24d ago[deleted]
- so-cal-schemer 24d agoI want to point out that "Homo-iconic" does not appear in SICP. But the concept is employed in the Meta-Circular Evaluator. Perhaps see: 4.1.5 Data as Programs https://sarabander.github.io/sicp/html/4_002e1.xhtml#g_t4_002e1_002e5 https://sarabander.github.io/sicp/html/4_002e1.xhtml#g_t4_00... Another striking aspect of the evaluator is that it acts as a bridge between the data objects that are manipulated by our programming language and the programming language itself. Imagine that the evaluator program (implemented in Lisp) is running, and that a user is typing expressions to the evaluator and observing the results. From the perspective of the user, an input expression such as (* x x) is an expression in the programming language, which the evaluator should execute. From the perspective of the evaluator, however, the expression is simply a list (in this case, a list of three symbols: *, x, and x) that is to be manipulated according to a well-defined set of rules. That the user’s programs are the evaluator’s data need not be a source of confusion. In fact, it is sometimes convenient to ignore this distinction, and to give the user the ability to explicitly evaluate a data object as a Lisp expression, by making eval available for use in programs. Many Lisp dialects provide a primitive eval procedure that takes as arguments an expression and an environment and evaluates the expression relative to the environment. The difference is this is built in to many Lisps without the need to create a separate interpreter. And thus much more direct. It's definitional.
- eru 24d agoThat part of SICP applies equally well to Haskell, which is not generally considered homoiconic.
- jibal 24d ago> What does 'within the language' mean? It's obvious, and the rest of the comment confirms that: > Would a variant of C that came with an interpreter in the standard library (but no other changes) count as homoiconic? I already answered this, so this is clearly bad faith trolling/sealioning: "even if you could, strings + eval alone is not homoiconicity--programs are not manipulated by the compiler as unstructured text strings."