4 ms·
Homoiconicity.
by WalterGR 2mo ago
Homoiconicity.
- chowells 2mo agoWhat makes this a desirable feature? From the perspective where local reasoning is the most desirable property a language can have, how does homoiconicity support that? I honestly am curious. I've seen a few examples presented for it, but they always seem like bad software engineering to me. Where's an example that does something in a cleaner way than alternatives present in other languages while remaining compatible with local reasoning?
- bjoli 2mo agoit allows you to create a language that compiles to your original language. You can abstract everything away. Not like Haskell where laziness accounts for some and typeclasses for some (and often an exponential growth in compile times). No, it property let's you change the language. I got tired of loops sucking and made this, for example: https://rikspucko.koketteriet.se/bjoli/goof-loop https://rikspucko.koketteriet.se/bjoli/goof-loop
- chowells 2mo agoThis doesn't strike me as better than the alternatives. In Haskell, it's just a few different functions to handle the different use cases. That strikes me as far better than one "function" that magically does different things depending on how it's called. It is local, I'll give you that. But it still seems bad because you have overloaded the semantics along multiple axes simultaneously. I prefer building blocks with only a single set of semantics each. I believe it's better to write precise code than DWIM code.
- bjoli 2mo agoeverybody agrees, which is why most sane people very rarely reach for macros. At the same time, it would be great if some of the libraries that rely on typeclass machinery for code generation could use lisp-like macros, because turning a 3 second recompile to a 35 second recompile (and I have experienced worse. Much worse.) is what kills exploratory programming. I heard optics mostly solves much of the problems of generic lens, but god forbid you use something like servant or polysemy. Chez compiles a project of about 35000 lines (with heavy macro usage) in less than 0.5s on -O2.
- WalterGR 2mo ago> From the perspective where local reasoning is the most desirable property a language can have That's a perspective. If you're looking for a low-level language, then Scheme isn't it. (Forget iconicity - Scheme is garbage-collected. And supports continuations!) If you don't program in machine code - which would maximize local reasoning - then you must know the language with the Correct balance of local reasoning and higher-level constructs. Knowing which language that is would add specificity to this discussion...
- chowells 2mo agoThat's not at all what local reasoning means. Local reasoning is the property that a piece of code contains (when including the call graph) everything that can affect what it does. All mutation of a value is kept within some scope of ownership of that value. If you want to understand a piece of code, you can do it by understanding that piece of code, not the program as a whole. Assembly makes non-local reasoning mandatory, as any code can update any location in memory without restriction. All memory accesses are global. References need not even be by name - they can be via computed addresses. There are no restrictions in place allowing the structure of the program to provide boundaries on what pieces of code may be understood as units.
- so-cal-schemer 2mo agoHomoiconicity is orthogonal to local reasoning. It just means the syntax is represented by the native data format, nested lists. This does make code generation very straightforward with list processing primitives. Mutation in scheme is possible, via set!, and set-car! and set-cdr!, but it's not recommended. Functional program design side-steps the issue. see also: SICP: 3.1.3 The Costs of Introducing Assignment https://sarabander.github.io/sicp/html/3_002e1.xhtml#g_t3_002e1_002e3 https://sarabander.github.io/sicp/html/3_002e1.xhtml#g_t3_00...
- chowells 2mo agoYes, I know they're orthogonal. But every example of using macros I see in lisp is either some horrendous non-local logic, or something that can be done better using simpler tools. Where's the killer use case that clearly is good software engineering and not papering over the lack of another feature?
- gus_massa 2mo agoHomoiconicity makes writing macros easy. If you don't like it, you can try https://rhombus-lang.org/ https://rhombus-lang.org/ that is build on Racket and also has macros but uses a Python-like syntax.
- Zambyte 2mo agoEasy is an understatement. Macros and dynamic programming (often done using runtime reflection in other languages) are so trivially easy in Lisps that one can stumble into it without realizing they're even doing it. Anyone who has done these things in more "modern" languages knows these features are not something one can accidentally start doing. They take a lot of deliberate effort relative to standard, static programming.
- soegaard 2mo ago> Easy is an understatement. Macros and dynamic programming (often done using runtime reflection in other languages) are so trivially easy in Lisps that one can stumble into it without realizing they're even doing it. Well, getting the interaction between modules and syntax transformations (macros) right is not an easy task. "Composable and Compilable Macros: You Want it When?" Matthew Flatt http://dl.acm.org/authorize?24908 http://dl.acm.org/authorize?24908
- spdegabrielle 2mo agoBicameral, Not Homoiconic https://parentheticallyspeaking.org/articles/bicameral-not-homoiconic/ https://parentheticallyspeaking.org/articles/bicameral-not-h...
- GregBuchholz 2mo agoIs Prolog bicameral? Because it has a `read` predicate? Is bicameral then just a matter of implementing a `read` function which returns a tree of tokens, which you could implement for C or Python? Which makes bicamerality a property of a library rather than a property of the language?
- layer8 2mo agoI don’t find it appealing when everything looks the same irrespective of purpose and context.
- eru 2mo agoSeems like a non-sequitur.
- layer8 2mo agoHow so? Homoiconicity means that everything uses the same form of representation, and I’m familiar with how these things look in Lisp.
- eru 2mo agoHomoiconicity doesn't mean everything has to _look_ the same. If the term has any meaning, it's about how program source code can be represented and manipulated easily by the user. (And I say 'can' deliberately, because even Lisps still allow you to treat source code as a big flat string, if you want to.) I don't think homoiconicity is all that much of a useful concept. It's very hard to pin down. Eg C can represent its own source code, too, if a bit clunkily. And Lisps generally don't execute by walking over s-expressions: their internal representation of their own logic typically uses more sophisticated structures (and adding native code compilers to the mix complicates matters further).
- jibal 2mo agoDoes i + 1 look the same as "i + 1"? The first is an expression, the second is a string. In LISP, it's similarly (+ i 1) vs '(+ i 1). These sorts of shallow complaints can be made of any feature than one is not familiar with, hasn't used, and doesn't know or understand the benefits of. In any case, no one is forcing people to use this language or take advantage of this feature. P.S. The "response" actually ignores all points made, attacks strawmen, moves the goalposts, and is intellectually dishonest (the original comment was clearly a complaint, and besides it wouldn't matter if some other word like "criticism" or "dissatisfaction" were substituted--my point remains). Again, no one is forcing anyone.
- eru 2mo agoC has homoiconicity: you can represent C source code as C strings.
- jibal 2mo agoYou can't run that code within the language so no, obviously not. And even if you could, strings + eval alone is not homoiconicity--programs are not manipulated by the compiler as unstructured text strings.
- eru 2mo ago> You can't run that code within the language so no, obviously not. Of course, you can. C is perfectly capable of writing C interpreters and compilers. > And even if you could, strings + eval alone is not homoiconicity--programs are not manipulated by the compiler as unstructured text strings. Lisps (typically) don't execute by walking over s-expressions, either. Lisp interpreters and compilers use more sophisticated representations.
- jibal 2mo agoI 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 2mo 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 2mo 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 2mo ago[deleted]
- so-cal-schemer 2mo agoA spotlight on the subject: https://www.expressionsofchange.org/dont-say-homoiconic/ https://www.expressionsofchange.org/dont-say-homoiconic/ A humorous footnote: In fact, the original article excludes Lisp as an example of homoiconicity, but only because Lisp had not settled on a single representation in terms of s-expressions at the time of writing: “Finally, LISP is troubled with a dual language problem: an M-language, which is easy to read, and is used externally, and an S-language, with which the LISP processor operates, and which is usable externally only by the hardened initiates. It should be noted here that were the S-language the only LISP language, LISP would be close to being homo-iconic (excluding the machine-language functions).” Don't Say “Homoiconic” (2018) (expressionsofchange.org) 88 points by dmux on Aug 11, 2019 | hide | past | favorite | 69 comments https://news.ycombinator.com/item?id=20657798 https://news.ycombinator.com/item?id=20657798