7 ms·
I'm just curious about something on the theoretical level. I think I can see the beauty of it, but if you take the two extremes: - a C++ compiler written in C+
by mojuba 5y ago
I'm just curious about something on the theoretical level. I think I can see the beauty of it, but if you take the two extremes:
- a C++ compiler written in C++ in millions of lines of code
- a Perl (PHP, ...) interpreter written in the same language in one line of code (along the lines of `eval $1`)
Given the above the Lisp interpreter written in Lisp is somewhere in the middle and it doesn't really say much about the language. Just that you can use the built-in facilities of the language and write an interpreter that will implement those facilities using... themselves.
In this regard, how is it fundamentally different from `eval $1`?
- p4bl0 5y ago`eval $1` uses the millions of line you were referring to in the first case. The few primitives necessary for a metacircular interpreter can be easily implemented from scratch (hence the name "primitives", which clearly does not apply to `eval`).
- enriquto 5y agothe difference between "millions of lines" and "a few easily implemented primitives" is quantitative. Thus there is a continuum between the two extremes, which was the point of the OP.
- mjw1007 5y agoWhere does the garbage collector come from?
- kaba0 5y agoIt is going to be an interpreter, so the “host runtime” including the GC will be of the host LISP program.
- mjw1007 5y agoIt seems a bit of a stretch to describe that program as an implementation of a Lisp interpreter, then.
- kaba0 5y agoIn case the given host implementation can be AOT compiled, the resulting binary will for all practical purposes be a LISP interpreter. Bootstrapping isn’t rare in this area.
- hnfong 5y agoThis sounds like "Given that modern Javascript engines have JIT capabilities, the eval() function is for all practical purposes a Javascript interpreter"...
- mjw1007 5y agoI agree that the resulting binary is a Lisp interpreter. But I don't think the program presented in this link was its implementation.
- DonHopkins 5y agoWhat matters most is whether the language is homoiconic or not. Lisp, PostScript, Prolog, Rebol, Snobol, and even TCL are, but C and most others aren't. That makes it much easier to implement a metacircular evaluator, because code = data. https://wiki.c2.com/?MetaCircularEvaluator https://wiki.c2.com/?MetaCircularEvaluator TCL is an edge case, since it simply represents everything as text, and its execution model is defined by passing and reevaluating everything as strings. https://en.wikipedia.org/wiki/Homoiconicity https://en.wikipedia.org/wiki/Homoiconicity >In computer programming, homoiconicity (from the Greek words homo- meaning "the same" and icon meaning "representation") is a property of some programming languages. A language is homoiconic if a program written in it can be manipulated as data using the language, and thus the program's internal representation can be inferred just by reading the program itself. This property is often summarized by saying that the language treats "code as data". https://wiki.c2.com/?HomoiconicLanguages https://wiki.c2.com/?HomoiconicLanguages >Languages in which program code is represented as the language's fundamental data type are called 'homoiconic'. Such languages allow code and data to be DeeplyIntertwingled, so that new code can be generated and manipulated by the program itself at runtime. [...] >Note that HomoiconicLanguages are strongly related to languages with a MetaCircularEvaluator, because it is always easy to make a MetaCircularEvaluator for a homoiconic language, but they are really two different topics. The Lisp example above is not metacircular, but it is homoiconic. You can write (with difficulty) an interpreter for C in C, but it will not make C homoiconic. >Eliminate this category, mention that meta-circularity is a prerequisite for homoiconicity, but that it doesn't imply homoiconicity. Also, the "strong" vs. "pure" seems to be "real-world implementation" vs. "mathematical ideal". We should mention that (afaik, I could be wrong here) no implemented language is a 'pure homoiconic' language. [...] >MetaCircularEvaluator (more commonly, "MetaCircularInterpreter"): it is possible to trivially implement a homoiconic language in itself. "Trivially" means that the semantics need not be specified explicitly; instead, they are implemented directly by the language construct being implemented. For instance, Lisp eval might be implemented by calling eval. If you don't already know what eval does, then reading the source code for the MetaCircularInterpreter might not enlighten you, it might just say "eval means eval". Thus "metacircular". >It is famous that the core of the Lisp language can be written in about 20 lines of Lisp. This is possible because the implementation is a MetaCircularInterpreter. The average person who writes a C compiler or interpreter requires about 20,000 lines of C to do so, and must be (or become) moderately expert about compilers or interpreters. Implementing Lisp in Lisp as a MetaCircularInterpreter teaches one extremely little about compilers/interpreters for other languages. https://wiki.c2.com/?HomoiconicFaq https://wiki.c2.com/?HomoiconicFaq >Q: What's this about Tcl? >A: Tcl is homoiconic because evaluation of data is part of the language: force evaluation of data, and it becomes program, and that's part of the language definition - that's how while works, for instance, in Tcl: it forces evaluation of its first string argument, and if the result is true, then it forces evaluation of its second string argument. Essentially the same is true of Lisp S-expressions as program or data (and is not true of Lisp hash tables nor arrays). The same is true of a subset of Snobol. It is not true of C, Java, C++, even though they're TuringEquivalent. Also: homoiconicity is something you can't just add to a programming language with a class or library or enough code or extensions. It's a property of the language definition itself. >Q: Well, the C language doesn't natively support constructs to manipulate C code, true, but what if, when compiled, it... >A: Nope. Doesn't matter. If a language isn't homoiconic at the source level, then no example of what can be done once it's compiled will change that. Why not? Because compilation means translation to a different language (such as machine language). Anything you can say about the compiled program is a statement about a language other than C. >Q: Well, but I could write a C program that, when run, would... >A: Nope. Doesn't matter. Same issue. You could write a C program that implements a Lisp interpreter. That doesn't make C into Lisp. [ Epic flame war about whether or not machine language is homoiconic redacted! ;) But the final quesiton is interesting: ] >Q: Is Homoiconic much ado about nothing ? >A: Yes, after much wasted bandwidth on c2 this seems to be the only logical conclusion. In particular "homoiconicity" should, in principle, facilitate meta-programming techniques, on its own it is of very little value. Languages without homoiconicity have managed to accomplish a lot in this area using lighter techniques. See for example AspectWerkz, RubyOnRails, etc. In the same time some homoiconic languages like Common Lisp fall far short from being fully reflective environments, and this subtracts further from the value of being homoiconic. In the end, what the client programmer should ask for is results. Whether or not a language is fully, 50% or 0% homoiconic matters very little.
- contravariant 5y agoThe main difference is in how basic the language concepts are that the interpreter uses. Pattern matching a list is quite a lot more basic than an 'eval' function that supports executing an arbitrary statement.
- slunk 5y agoYou've gotten a couple responses making arguments that the atoms required for a language like lisp are fundamentally simpler than eval in Perl and that makes the difference... Fwiw, I don't find these arguments very compelling. It's not so much that they're entirely false, but the "most beautiful program ever written [sic]" that we're talking about here is "beautiful" precisely because it elides all these "simple" details. I do get the vague sense that it is essentially a more verbose `eval $1`.
- mojuba 5y agoI agree. You could probably say though, that once you decide to implement anything that's not `eval $1` you jump to the next level that forces you to parse the input and then use your built-in primitives to implement themselves. I think any minimal interpreter for any dynamic language will probably end up being pretty simple, though not necessarily as elegant as Lisp's. But elegance aside, there's nothing groundbreaking here.
- urthor 5y agoIt's not different from eval $1 at all. Because it is eval $1. However, by the time that $1 has been passed, the Lisp zeroes and ones of the eval function have been turned into the appropriate machine language of the CPU. As of course different CPUs have different evaluation pipelines for integers, floats, vertices, branches etc. So it evaluates a block of lisp as purely integers, floats, branches, vertices, and sends the appropriate data type off to the appropriate evaluation line, and reassembles it.
- p4bl0 5y agoWhile this point of view can be seen as technically valid, I don't see it as relevant because it negates all the specificity of the different paradigms and levels of interpretation. Of course all those runs on the same computer in fine, but how is that what's interesting here? It's a bit like saying that mammals reproduction is not different from plants reproduction because in the end all it is are ADN based cells that work together based on the same chemical mechanisms.
- urthor 5y agoThe idea I'm struggling to explain, and probably failing to convey, is that the Lisp REPL does not look like Lisp when it accepts the Lisp. It will be machine code that accepts unicode strings, parses those unicode strings into Lisp, compiles the Lisp into machine code, then evaluates the machine code. So essentially it's not the same as passing a Lisp variable to a Lisp function inside a Lisp text file.
- slunk 5y agoI was also struggling to convey this point. I think you've hit the nail on the head.
- p4bl0 5y agoAh, I see, right. One specificity of Lisp however is that the textual representation already looks like the abstract syntax tree that the Lisp variable which is passed to the Lisp function in the text file contains. But I can easily admit that this fact can also be seen as some sort of illusion since in the end it is just a sequence of bytes.
- Galanwe 5y ago> - a C++ compiler written in C++ in millions of lines of code #include </dev/tty> This most beautiful program ever doesn't even need to be compiled, it's a JIT compiler in 1 line of code.
- taeric 5y agoMy take on what makes eval of lisp different from other evals is at https://taeric.github.io/CodeAsData.html https://taeric.github.io/CodeAsData.html The part that I think I do a poor job of understanding/explaining is that lisp doesn't eval strings of code. Instead, it evals lists of tokenized data.
- kmill 5y agoThe fun of writing an interpreter is that you can extend it with new features easily. With `eval $1` you're stuck with whatever it does (unless you want to do string manipulation of code -- it's only usually a bad idea :-) ) The book Structure and Interpretation of Computer Programs is organized by types of abstractions. The first three sections are about procedures, data, and modularity, but then the fourth is about abstracting language itself (called "metalinguistic abstraction"). This was a principle used often in classic AI research, where you built specific languages to implement different planning systems -- incidentally this is why it's called "scheme". By building a lisp in a lisp, you could make use of all the facilities of the host language and add new useful features. The book demonstrates how to add lazy evaluation, nondeterministic operators, and even Prolog-style logic programming to the language. These sorts of interpreters depend on having a compatible host language. The last section is about how you can transcend limitations by essentially simulating an appropriate CPU in the host language (i.e., a virtual machine). This is how the original Scheme was implemented in its host Lisp, which was necessary because Scheme has tail recursion and Lisps haven't traditionally had that.