6 ms·
I Wrote a Scheme in 2025
- 0xcafefood 8mo agoI really wish lisps were more popular (or, really, popular again). Most people can't make it past the non-Algol syntax, which is silly IMO. But they do also demand more of the user than a typical language. Their use of metaprogramming doesn't just allow you to extend the language, it really expects that of the programmer. Which means you have to assume the role of language designer to some extent. Learning how to do that definitely feels like a way to level up your skills. But it seems uncommon for people to want to do that.
- maplant 8mo agoI think people underestimate how pragmatic meta programming can be because there are some obvious downsides. Arguably one of things that made Rust so popular was its inclusion of procedural macros. But beyond that the thing I don't understand about the modern hate towards macros is that they are simply very fun.
- shpongled 8mo agoAs someone who is "into" programming languages (and making toy implementations of them), I think some of the most important macros are along the lines of Rust/Haskells `derive/deriving` for quickly enabling serialization, printing etc. Using a language without such capability quickly becomes frustrating once you move to any kind of "real" task.
- fmbb 8mo agoIn any kind of real task, serialization is not the hard part. If you can write a meta program for it, you can execute that in CI and spit out generated code and be done with it. This is a viable approach in any programming language that can print strings to files. It’s not frustrating, but maybe it feels tacky. But then you shrug and move on to the real task at hand.
- skydhash 8mo agoLisp macros are more for not having to write the same type of code (all subtly different, but sharing the same general structure). One such example is the let-alist macro in elisp https://www.gnu.org/software/emacs/manual/html_node/elisp/Association-Lists.html#index-let_002dalist https://www.gnu.org/software/emacs/manual/html_node/elisp/As... Dealing with nested association lists is a pain. this let you write your code with a dot notation like jq. Macros are not only for solving a particular task (serialization, dependency injection, snippets,…) they let you write things the way it makes sense. Like having html-flavored lisps for template, sql-flavored lisp for query,… Lisp code is a tree, and most languages are trees, so you can bring easily their semantic in lisp.
- bccdee 8mo agoYou say that, but I've run into real production problems which were ultimately caused by bad serialization tooling. Language semantics are never going to be your biggest problem, but rough edges add up and do ultimately contribute to larger issues.
- nine_k 8mo agoRuby, Python, and Typescript use metaprogramming rather heavily. They lack the homoiconic property of lisps, but they can do both higher-order functions and monkey-patching. Meta-heavy code usually offers a nice DSL, but is proportionally harder to drill down through.
- skydhash 8mo agoLisp code is a tree, which fits how most languages are written. So it’s easy to embed other languages in lisp. But other languages grammars are very cumbersome and can’t fit one another.
- pjmlp 8mo agoAnd yet we have done it across most modern languages, with a lesser experience as Common Lisp or Scheme.
- bccdee 8mo agoYeah I strongly agree. I think the issue is that metaprogramming is complicated, so people (especially early in their careers) tend not to do it themselves, and only notice it when it's making their lives difficult. But there are a lot of cases where a little bit of judicious metaprogramming makes life MUCH easier. If you treat metaprogramming as a first-class tool, countless rough edges will smooth themselves out for you—everything from `#derive[Clone]` to `#derive[Serialize]`.
- tmtvl 8mo agoLisp is versatile as all get-out, so you can program however you want. For example, we can roll like it's 1969: (prog ((a 0) (b 1) (c 0)) (declare (type Fixnum a b c)) :fb-start (print a) (incf b a) (setf a (- b a)) (incf c) (when (< c 100) (go :fb-start)))
- deleted 8mo ago[deleted]
- Joker_vD 8mo agoWhich actually supports the OP's original argument that even with training and getting used to, this syntax reads harder than let a, b, c = 0, 1, 0 fb_start: writen(a) b +:= 1 a := b - a if c < 100 do goto fb-start
- tmtvl 8mo agoLeaving aside the fact that you have at least 4 (EDIT: 5) mistakes in that code, I find it less readable than the Lisp equivalent. The brackets reinforce the structure imposed by the indentation and help me keep track of order of execution. But that's how my brain works and that's why syntax is subjective.
- Joker_vD 8mo agoMy apologies, it should've been (if you insist on indentation) let a, b, c = 0, 1, 0 fb_start: writen(a) b +:= a a := b - a c +:= 1 if c < 100 do goto fb-start Would you care to elaborate about brackets helping with tracking the order of execution? I'm really curious about that part.
- kazinator 8mo agoTXR Lisp, with infix via ifx macro: (ifx (let ((a 0) (b 1) (c 0)) (tagbody fb-start (prinl a) (b += a) (a := b - a) (c += 1) (if (c < 100) (go fb-start)))))
- cultofmetatron 8mo ago> But it seems uncommon for people to want to do that. It becomes more obvious once you start managing developers vs being a solo dev. everyone making their own designer means the language can morph into a completely insular creation with a learning curve that expands exponentially with every new hire. A little extra boilerplate is the cost of standardized idioms that work across both your codebase that your new hires are already familiar with from working in that language at other companies. its why go was created. personally I prefer rust and elixir as good middle grounds.
- reikonomusha 8mo agoThis is a common reaction/belief but usually from people who have not actually managed a team of Lisp devs. Lisp devs are managed in the same way as any other: You have style guidelines, design review, code review, etc. Sometimes a new macro is good and vastly simplifies code. It's accepted as a PR and documented like anything else. Sometimes a new macro is bad, and it's promptly rejected by the team. It's a persistent myth that Lisp programmers are just going to design their own little languages everywhere in a shared code base and it'll be impossible to understand. (Case in point: Look at open source Lisp code. There isn't even management or code review there! Yet the vast majority of Lisp code is actually just functions and classes, with the occasional macro to reduce boilerplate. In some circumstances, you have a library offering a macro, and it's actually well documented and easy to understand. See Iterate, SERIES, etc. for actual examples.) Rust or Elixir or Java or whatever aren't at all immune to monstrosities created by astronomically complex or baroque abstractions, FactoryFactoryFactories, and so on. How do teams avoid those? Style guidelines, design review, code review, etc.
- saghm 8mo agoIt's hard to reconcile that with the comment they were responding to that claimed that lisp requires you to be the designer of the language a bit. I don't know enough to know who is right, but if the majority of the code is "just" regular functions and classes then I'd argue that it doesn't require custom design as a much as allow it, and the solution you're proposing is to mostly disallow it by convention. Like the comment you're responding to suggests, it's hard for me to imagine why having that much flexibility is worthwhile if it's going to be mostly unused when you can still write that occasional macro you call out in Rust or Elixir.
- reikonomusha 8mo agoThree thoughts (in the context of Common Lisp specifically): - Every day that passes, the gulf between Lisp's tooling and what a typical user expects grows wider. It needs to escape Emacs and SLIME to something that feels complete and polished. - There needs to be a little bit of a culture shift around Lisp to actually write programs that do things. How many programs can you download via apt or brew that are written in Lisp? They're executables at the end of the day so nothing in principle stops this from happening, but there's just a thread of modern Lisp culture where it's more fun to play around in the REPL and write creative libraries than to ship. (There are notable exceptions of course.) - I personally like the quirkiness of Common Lisp, but there are so many ways to write it (imperative, functional, etc.), so many ways to structure your programs (one package, package per file, package inferred system, etc.), and so many ways to offer APIs (plain old data and functions, generic function protocols, etc.) that it makes it a combination of confusing and intimidating. I think shifting toward something a little more structured and disciplined like Coalton, while still giving the escape hatches to all of Common Lisp, would help a lot of people "join in" on building new code or building upon existing code.
- 0xcafefood 8mo agoAgreed. I think Clojure strikes a pretty reasonable balance here. It's opinionated about the programming paradigm, scales back some of the pain that comes from reader macros, and solves some of the bootstrapping problems by compatibility with other JVM languages.
- harperlee 8mo agoI love clojure but the points still stand, kind-of. - There is Calva for VS Code but the community default is emacs and cider - How many programs in apt or brew are written in clojure? I'd concede that the community is great and focused on productivity, but it's so niche that you don't see much work out there made in clojure, and there is also a vestigial lisp sentiment to prefer building your own library from scratch instead of contributing to a standard library, which spreads the efforts of a small community too much - Third one you need to mutate it a little bit: clojure is opinionated instead of having "so many ways", but its opinions, while great, are foreign to most programmers
- renato_shira 8mo ago[flagged]
- codr7 8mo agoI've been playing around with dropping most of the parens, but keeping the rest. https://gitlab.com/codr7/shik https://gitlab.com/codr7/shik
- antonvs 8mo agoPeople have played around with those exact ideas for decades. No-one has ever come close to making it work.
- codr7 8mo agoIt's working just fine here; but I'm not simply changing syntax, this is a ground up redesign.
- shakna 8mo agoAny thoughts on SRFI-119's syntax? https://srfi.schemers.org/srfi-119/srfi-119.html https://srfi.schemers.org/srfi-119/srfi-119.html
- codr7 8mo agoI don't think this is something that will happen inside of an existing language. It needs to be a complete redesign where all parts pulls in the same direction.
- taylorallred 8mo agoI was really interested in lisps for a couple of years, but eventually I came to the conclusions: it's just hard to read. I know they say "the parens disappear" but even if that is the case, it simply requires you to jump around the expression holding lots of context in your head. I'm a fan of code that mostly reads top-to-bottom, left-to-right.
- FranklinJabar 8mo ago> I'm a fan of code that mostly reads top-to-bottom, left-to-right. Programs are rarely linear; why do you expect code to be?
- reikonomusha 8mo agoLisp code is written top to bottom, left to right. Is your grievance more to do with expression-oriented—as opposed to statement-oriented—languages? "Do this" (statement) vs "represent this" (expression)? For instance, do Haskell, OCaml, etc. also irk you in similar ways?
- Avshalom 8mo agoLisp is written top to bottom left to right but because it's (almost) fully nested it's executed right to left bottom to top. Haskell and OCaml are, by comparison, not very nested.
- reikonomusha 8mo agoThis is not true. (defun f (x) (let ((y x)) (setf y (* y x)) (block foo (if (minusp y) (return-from foo y)) (loop :for i :from 1 :to 10 :do ... This is absolutely typical bog-standard left-to-right top-to-bottom structured programming type code. It also must be executed like so: - Define the function - Bind the variable - Mutate the variable - Set up a named block - Do a conditional return - Run a loop - ... The order of execution literally matches the order it's written. But not unlike almost all other languages on the planet, expressions are evaluated inside-out. Haskell's whole raison d'etre is to allow arbitrary nesting and substitution of terms, and all or none of these terms may or may not be evaluated depending on need. De-nesting happens with a copious number of syntax to bind names to values, sometimes before the expression (via let), sometimes after the expression (via where), and sometimes in the middle of an expression (via do).
- shirian 8mo agoI just really like the homoiconicity.
- nxobject 8mo agoAnother reason this is interesting beyond "Lispiness" instead: implementing a (compliant) Scheme takes on some interesting problems that other languages punt - proper tail recursion, delimited continuations (off the top of my head, I don't know other methods apart from CPS), and hygienic macros.
- antonvs 8mo agoLast I checked no compliant Scheme required delimited continuations. Unless something like that was added in R7RS or later.
- maplant 8mo agoThey don't. They will probably get added in R7RS large. They're available in scheme-rs regardless: https://scheme.rs/Standard%20Libraries/prompts/ https://scheme.rs/Standard%20Libraries/prompts/
- Apofis 8mo agoIf anyone wants to know why he bothered to do this, he wrote another article explaining: https://maplant.com/2025-02-17-Why-I%27m-Writing-a-Scheme-Implementation-in-2025-%28The-Answer-is-Async-Rust%29.html https://maplant.com/2025-02-17-Why-I%27m-Writing-a-Scheme-Im...
- maplant 8mo agoThat is linked literally in the first sentence of the article