#i is the prefix for inexact
#e is the prefix for exact
So `#e0.1` is exact 0.1 which means it will be read as an exact fraction 1/10
and `#i0.1` will be read as a floating point number.
The `i` in `8i` is the imaginary unit.
The `1@1` is the polar notation for complex numbers.
The `10#` is a compatibility remain (the Scheme authors wanted a way to see how many signifant digits were known. In `10#` there 2. So `#` stands for "some digit". In most implementations this is read as 0. More details at [1]
An `#` followed by a list datum is read as a vector.
Thus `#()` is an empty vector and `()` is an empty list.
Complicated? Well, to support both inexact (floating point) and exact numbers (bignums and fractions) it makes sense to have `#i` and `#e` to explicitl choose.
The default is (more or less): numbers with . are inexact the others are exact.
At first I was frustrated by this comment, having a fair amount of Lisp experience failing to grok the examples. In less than one minute I was able to locate, download and install the latest Racket and was in a REPL. There really are no excuses.
This was a wild ride. I'm torn on whether this feels like the Reader is bloated/polluted or whether this is the coolest numerical parser I've ever used. You can really do a lot to coerce the these numbers just by inserting an {o,s,e,i,b,#} into the number, with different semantics depending on the position of the character.
Somewhat overwhelming, I can only hope there's some elegance underlying it all...
It looks more like a "program" for an HP15c pocket scientific calculator.
Not necessarily a bad thing but also not exactly what I would call "expressive".
You may be able to build anything you want --- which may be efficient when communicating with the machine. But I wonder about now well this would communicate with other programmers.
It's not a program, it is (obviously) a totally unrealistic hodgepodge expression for a list of constant values that simply demonstrates the literal syntax for a variety of types of values (real numbers, dotted pairs, rational numbers, complex numbers, polar coordinates, etc. -- Racket is unusually expressive in the types of literals). Your comment demonstrates something typical of HN, and it's not a good thing. It's as if in a discussion about C someone posted, without explanation, an entry from the Obfuscated C contest and then someone else, making zero effort to understand what was posted, said that it looked like line noise and questioned whether C is conducive to communication between programmers.
My first language, 1980 in CMU, was Pascal... quickly followed by LISP in CS/AI courses and C for EE courses. I was a big fan of MacLisp at the time, the MIT version that GLS worked on (could see his comments all over the sources). Of course GLS moved on to Scheme and wrote a few definitive papers on closures. Yeah, that was before PC's everywhere... a loooong time ago. (C++ came years and years afterwards, not a fan. I would have stayed with hardware if the ++ version of C was forced on me all those years ago.)
GLS moved to CMU in the early 80's... I remember taking "Comparative Programming Languages" from him. Good teacher and impressive guy. In addition to the obvious, he also covered SNOBOL and APL in that course. Memories :-)
> In The Amazing Digital Circus (episode 8, "hjsakldfhl"), when Kinger opens the terminal to try to reset Caine, you can see that Caine (a creative AI built in 1996) is programmed in Lisp. The file is literally named Caine-core.lisp.
Nice touch. Explains how Caine returns in episode 9: Lisp continuations allow for graceful error recovery.
wasn't Caine written in common lisp? Pretty sure they show that the debugger they used in the command line was gdb (which ... is that even actually possible?)
It is rich with information, to the point, and presented in a way that a motivated and competent reader can immediately know something about and be useful with racket. That’s how it was for me anyways; someone who didn’t know anything about racket before reading through this.
Wtf kind of intro to a programming language doesn’t include syntax rules? We are all informed, experienced, technologically inclined people here. Not dumb children.
I’m struggling to see your comment as anything more than contrarian babble.
> Wtf kind of intro to a programming language doesn’t include syntax rules?
Given that most languages don’t have syntax rules or anything like it, I’m gonna go out on a limb and say that most language introductions/overviews/tutorials don’t mention it.
C, python, java, Fortran, ruby, Common Lisp, Haskell, go, zig (I think), forth, swift, cobol, pretty much any language that doesn’t directly descent from Scheme.
When I see "friendly introduction," I don't expect to see a deadhead sticker front and center. But I guess if you're an actual Deadhead it's not that startling. ;-)
While the language is interesting, sadly, nobody is using it in the wild. Maybe due to cumbersome deployment options? The ability to produce native standalone executables would boost its usage I believe.
I agree. In 2020 we had to manipulate some Moodle files. I wrote the code in Racket and I sent the ".exe" file to my coworkers, that never knew they were using Racket.
any time the topic of racket comes up, i wonder if there are any interesting apps i could explore. but all i find is libraries and dev tools: https://awesome-racket.com/
The co-creator of Scheme is also co-author of Structure and Interpretation of Computer Programs[0]. Namely, Gerald Jay Sussman[1], the Panasonic Professor of Electrical Engineering at the Massachusetts Institute of Technology (MIT).
In teaching our material we use a dialect of the programming language Lisp. We never formally teach the language, because we don’t have to. We just use it, and students pick it up in a few days. This is one great advantage of Lisp-like languages: They have very few ways of forming compound expressions, and almost no syntactic structure. All of the formal properties can be covered in an hour, like the rules of chess. After a short time we forget about syntactic details of the language (because there are none) and get on with the real issues—figuring out what we want to compute, how we will decompose problems into manageable parts, and how we will work on the parts. Another advantage of Lisp is that it supports (but does not enforce) more of the large-scale strategies for modular decomposition of programs than any other language we know. We can make procedural and data abstractions, we can use higher-order functions to capture common patterns of usage, we can model local state using assignment and data mutation, we can link parts of a program with streams and delayed evaluation, and we can easily implement embedded languages. All of this is embedded in an interactive environment with excellent support for incremental program design, construction, testing, and debugging. We thank all the generations of Lisp wizards, starting with John McCarthy, who have fashioned a fine tool of unprecedented power and elegance.
We used it in the course I had that introduced functional programming. The only memorable thing I can remember writing in it for that class was a topological sort (I believe).
Lisp/Scheme/Racket is a very simple language that is also very fluid and malleable. You can express ideas and computations very precisely, concisely, and intuitively. There really aren't any barriers as they (Scheme/Racket at least) are rooted in the Lambda Calculus which is a theoretical model equivalent to the Turing Machine. Any language feature you'd like can be expressed: Want standard infix math expressions or a borrow-checker? That's a macro. Want lazy evaluation? That's a simple change in a meta-circular evaluator. Want a language that is the simplest and most direct mapping to your problem domain? A DSL.
The syntax lends itself to easy structural editing way before LSPs..
The other powers of Lisps (interactive development, hot reloadability, restarts on error, etc) are more tied to the sophistication of their implementations and runtime environments.
What 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?
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. 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.
> 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.
> 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...
Pre-Scheme is a statically typed dialect of the Scheme programming language, combining the flexibility of Scheme with the efficiency and low-level machine access of C. The compiler uses type inference, partial evaluation, and other correctness-preserving transformations to compile a subset of Scheme into C with no additional runtime overhead. This makes Pre-Scheme a viable alternative to C for programming virtual machines, operating systems, and embedded systems where the runtime overhead of a complete Scheme implementation is not desirable.
Microscheme, or (ms) for short, is a functional programming language for the Arduino, and for Atmel 8-bit AVR microcontrollers in general. Microscheme is a subset of Scheme, in the sense that every valid (ms) program is also a valid Scheme program (with the exception of Arduino hardware-specific primitives). The (ms) compiler performs function inlining, and features an aggressive tree-shaker, eliminating unused top-level definitions. Microscheme has a robust FFI (Foreign Function Interface) meaning that C code may be invoked directly from (ms) programs. Therefore, the power of the existing wealth of Arduino libraries is available within Microscheme.
That'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.
Homoiconicity 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.
it 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.
This 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.
You 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.
Does 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.
To answer your question, yes it does look pretty much the same in the Lisp version, less so in the string version. Also I didn’t complain; I expressed the opinion that I don’t agree that homoiconicity increases the appeal of a programming language, because the drawbacks outweigh the possible benefits for me. I spent some time programming in Lisp and am familiar with how homoiconicity works there.
The point is that these things are subjective, and there will never be a programming language that everyone likes the best.
> For decades, Lisp was the language of artificial intelligence. [...] Then came the "AI winter," funding dried up, and Lisp went from star to cult language.
Lisp had fallen from relevance before then. Only the United States was still using it, and mostly out of technical debt and a stubborn refusal to move on. Prolog displaced it in the late 1970s, and even within the US, the Lisp part was an unfortunate implementation detail to get to a Prolog-shaped object. It's not surprising that the US fell way behind Japan in this area in the 1980s. Anybody who would imply they would take a Lisp machine over a PIM is either not interested in symbolic computation and just likes Lisp, or they're doing so out of total ignorance of how much more advanced the PIMs were for symbolic AI.
The cutting edge was with Prolog, and that's still the case today. Nobody's researching MIL in Lisp, even in America.
I've been designing my own small language runtime in Rust (VM + JIT + AOT backends) mostly as a way to actually understand tradeoffs compiler authors make instead of just reading about them. Racket's approach to macros and language-oriented programming is one of the things I keep coming back to as a reference curious how much of that flexibility comes at a real runtime cost vs. being mostly a compile-time abstraction.
I can answer that for Common Lisp, specifically the SBCL implementation (there's several others). It compiles every function to native code (you can even inspect the assembly with `disassemble`). Macros are executed before code is compiled. Hence, once you've compiled a function, the macro disappears since its only role is to generate the expressions that are going to actually be compiled and then executed.
`unwind-protect` is like a `try/finally` in other languages, and I used that above to create something similar to `defer` in Go/Zig which is related to it, but with the operands inverted.
The ` symbol is a quasiquote. Unlike quote `'` it lets you unquote symbols inside with the , operator. That's why you'll always see a bunch of '`' and ',' in macros.
The @, thing is a "spread" (looks different in Racket from what I saw in the post, which used `...`). It just spreads whatever was on the list in the place you put that on, so if `action` is `(p 1) (p 2)`, then `(progn ,@action)` becomes `(progn (p 1) (p 2))`.
You can inspect what the actual code that will be compiled looks like with `macroexpand`:
The `i` in `8i` is the imaginary unit. The `1@1` is the polar notation for complex numbers.
The `10#` is a compatibility remain (the Scheme authors wanted a way to see how many signifant digits were known. In `10#` there 2. So `#` stands for "some digit". In most implementations this is read as 0. More details at [1]
An `#` followed by a list datum is read as a vector. Thus `#()` is an empty vector and `()` is an empty list.
[1] https://stackoverflow.com/a/10936403/23567
Complicated? Well, to support both inexact (floating point) and exact numbers (bignums and fractions) it makes sense to have `#i` and `#e` to explicitl choose. The default is (more or less): numbers with . are inexact the others are exact.
This was a wild ride. I'm torn on whether this feels like the Reader is bloated/polluted or whether this is the coolest numerical parser I've ever used. You can really do a lot to coerce the these numbers just by inserting an {o,s,e,i,b,#} into the number, with different semantics depending on the position of the character.
Somewhat overwhelming, I can only hope there's some elegance underlying it all...
Not necessarily a bad thing but also not exactly what I would call "expressive".
You may be able to build anything you want --- which may be efficient when communicating with the machine. But I wonder about now well this would communicate with other programmers.
GLS moved to CMU in the early 80's... I remember taking "Comparative Programming Languages" from him. Good teacher and impressive guy. In addition to the obvious, he also covered SNOBOL and APL in that course. Memories :-)
Nice touch. Explains how Caine returns in episode 9: Lisp continuations allow for graceful error recovery.
https://en.wikipedia.org/wiki/Backslash#Filenames
When an introduction says “friendly”, I don’t expect it to assume that I know what lambda is.
When an introduction says “friendly”, I don’t expect syntax rules to appear in it. At all.
Wtf kind of intro to a programming language doesn’t include syntax rules? We are all informed, experienced, technologically inclined people here. Not dumb children.
I’m struggling to see your comment as anything more than contrarian babble.
PP could have been more clear and say syntax-rules (note the dash), which means macros. Macros are usually considered an advanced topic.
Given that most languages don’t have syntax rules or anything like it, I’m gonna go out on a limb and say that most language introductions/overviews/tutorials don’t mention it.
In fact, what languages can you say do not have syntax rules?
Since macros are a very important part of Racket, complaining that a friendly introduction mentions them is [insert non-friendly characterization].
https://users.cs.northwestern.edu/~robby/logos/
The skull didn't last long.
https://racket-stories.com/
See: https://en.wikipedia.org/wiki/Arc_(programming_language)#His...
You can read the about it here: https://defn.io/2020/01/04/remember-internals/
In teaching our material we use a dialect of the programming language Lisp. We never formally teach the language, because we don’t have to. We just use it, and students pick it up in a few days. This is one great advantage of Lisp-like languages: They have very few ways of forming compound expressions, and almost no syntactic structure. All of the formal properties can be covered in an hour, like the rules of chess. After a short time we forget about syntactic details of the language (because there are none) and get on with the real issues—figuring out what we want to compute, how we will decompose problems into manageable parts, and how we will work on the parts. Another advantage of Lisp is that it supports (but does not enforce) more of the large-scale strategies for modular decomposition of programs than any other language we know. We can make procedural and data abstractions, we can use higher-order functions to capture common patterns of usage, we can model local state using assignment and data mutation, we can link parts of a program with streams and delayed evaluation, and we can easily implement embedded languages. All of this is embedded in an interactive environment with excellent support for incremental program design, construction, testing, and debugging. We thank all the generations of Lisp wizards, starting with John McCarthy, who have fashioned a fine tool of unprecedented power and elegance.
[0]: https://sarabander.github.io/sicp/
[1]: https://en.wikipedia.org/wiki/Gerald_Jay_Sussman
The syntax lends itself to easy structural editing way before LSPs..
Why Racket? Why Lisp?
https://beautifulracket.com/appendix/why-racket-why-lisp.htm...
Why language-oriented programming? Why Racket?
https://beautifulracket.com/appendix/why-lop-why-racket.html
Creating Languages in Racket: Sometimes you just have to make a better mousetrap.
https://queue.acm.org/detail.cfm?id=2068896
Lisp is clay: the power of composable DSLs
https://fosdem.org/2026/schedule/event/HDE7JZ-lisp-is-clay/
The other powers of Lisps (interactive development, hot reloadability, restarts on error, etc) are more tied to the sophistication of their implementations and runtime environments.
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?
If you don't like it, you can try https://rhombus-lang.org/ that is build on Racket and also has macros but uses a Python-like syntax.
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
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...
Pre-Scheme is a statically typed dialect of the Scheme programming language, combining the flexibility of Scheme with the efficiency and low-level machine access of C. The compiler uses type inference, partial evaluation, and other correctness-preserving transformations to compile a subset of Scheme into C with no additional runtime overhead. This makes Pre-Scheme a viable alternative to C for programming virtual machines, operating systems, and embedded systems where the runtime overhead of a complete Scheme implementation is not desirable.
https://ryansuchocki.github.io/microscheme/
Microscheme, or (ms) for short, is a functional programming language for the Arduino, and for Atmel 8-bit AVR microcontrollers in general. Microscheme is a subset of Scheme, in the sense that every valid (ms) program is also a valid Scheme program (with the exception of Arduino hardware-specific primitives). The (ms) compiler performs function inlining, and features an aggressive tree-shaker, eliminating unused top-level definitions. Microscheme has a robust FFI (Foreign Function Interface) meaning that C code may be invoked directly from (ms) programs. Therefore, the power of the existing wealth of Arduino libraries is available within Microscheme.
https://wiki.call-cc.org/eggref/6/crunch
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.
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_00...
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
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.
The point is that these things are subjective, and there will never be a programming language that everyone likes the best.
https://parentheticallyspeaking.org/articles/bicameral-not-h...
> For decades, Lisp was the language of artificial intelligence. [...] Then came the "AI winter," funding dried up, and Lisp went from star to cult language.
Lisp had fallen from relevance before then. Only the United States was still using it, and mostly out of technical debt and a stubborn refusal to move on. Prolog displaced it in the late 1970s, and even within the US, the Lisp part was an unfortunate implementation detail to get to a Prolog-shaped object. It's not surprising that the US fell way behind Japan in this area in the 1980s. Anybody who would imply they would take a Lisp machine over a PIM is either not interested in symbolic computation and just likes Lisp, or they're doing so out of total ignorance of how much more advanced the PIMs were for symbolic AI.
The cutting edge was with Prolog, and that's still the case today. Nobody's researching MIL in Lisp, even in America.
For example, here's a simple CL macro:
`unwind-protect` is like a `try/finally` in other languages, and I used that above to create something similar to `defer` in Go/Zig which is related to it, but with the operands inverted.The ` symbol is a quasiquote. Unlike quote `'` it lets you unquote symbols inside with the , operator. That's why you'll always see a bunch of '`' and ',' in macros.
The @, thing is a "spread" (looks different in Racket from what I saw in the post, which used `...`). It just spreads whatever was on the list in the place you put that on, so if `action` is `(p 1) (p 2)`, then `(progn ,@action)` becomes `(progn (p 1) (p 2))`.
You can inspect what the actual code that will be compiled looks like with `macroexpand`:
`progn` is a "special operator" that's needed when you want more than one expression to be evaluated in order.Example calling the macro:
As you can see, it executed the deferred expression last.We can prove that macros disappear after compile-time with an example:
You can see the Assembly is very simple for `(+ x x)` (the ADD instruction plus a bunch of stack/error maintenance).If we instead had a macro that did this:
And a function used that: Now, disassembling the function: Same thing exactly.I don't know Racket, but knowing it can compile to binary, I expect macros in Racket would work exactly the same.