MY PERFECT SCHEME (#📎) Artyom Bologov (Crudely drawn digital image of a person. The person looks quite puzzled, holding a colorful lambda letter. They also have a thought bubble with a larger lambda in it. Attributions to Artyom Bologov with lambdas used really improperly are in place too.) So I have recently picked up Scheme. I like it! While it’s not as featureful as Common Lisp, it wins by being really sane and simple. In fact, a basic implementation can take as little as 6KLoC of C! But there are some things that I want in my perfect Scheme. Sometimes just as a development of already existing themes. Sometimes some really unconventional things. My perfect Scheme might not rhyme with yours, but let’s agree to disagree. My perfect Scheme is: STRICT (#strict) Common Lisp is burdened with some sloppy historical accidents. Say, the concept of a symbol designator (symbol, string, or character!) or pathname designator (pathname, stream, or string, what?!) It brings some power but is quite unpretty. Scheme, on the other hand, is quite strict and exact. Almost all standard procedures are well-typed and domain-restricted. I like that! However, it’s not enough sometimes. So I bit the bullet and started my own SRFI series. Regarding type checking and enforcement. SRFI 253 —the principal one of these—is now merged in most implementations I care about. I can write perfectly well-typed code and be certain that it is correct and well-documented—at least type-wise. Sequels to 253, SRFI 273 and SRFI 283 —they add some more niceties, but are not really required for proper typed Scheme. SIMPLE (#simple) I really enjoy Scheme’s axiomatic nature. A really small set of special forms (10 in Chibi, 17 in Teeny Scheme !) It all can fit in my head. And the implementation can fit in my head too! Teeny has 6K lines of code, all of Skint is mere 11K lines, Chibi is 30K lines. Guile is… we don’t talk about Guile in this regard. So Scheme is small and easy to implement. And that’s beautiful, just perfect. READABLE & EDITABLE (#readable) Parentheses. They are fun… unless you use ed(1). No—not vi(1). ed(1). It’s line-based and unaware of ASTs. So I have to come up with my own syntax for Lisp to edit it properly. So my perfect Scheme is this: line based or at least close to it. I made a macro doing some heavy lifting for that: #lang scheme (define-syntax %%block (syntax-rules (:) ((%%block (form ...) : . rest) (form ... (%%block () . rest))) ((%%block (form ...) something . rest) (%%block (form ... something) . rest)) ((%%block (form ...)) (form ...)))) ;; TODO: Support return keyword (define-syntax %block (syntax-rules (_ <- !) ;; Terminators with bindings: return last binding ((%block (var <- form) (vars ...)) (let ((var form)) var)) ((%block (var <- forms ...) (vars ...)) (let ((var (%%block () forms ...))) var)) ((%block (form) (vars ...)) form) ((%block (forms ...) (vars ...)) (%%block () forms ...)) ;; Terminating clause recursion: remove trailing exclamation ((%block (forms ...) (vars ...) !) (%block (forms ...) (vars ...))) ;; Ignore exclamation at the start of the code or when duplicated ((%block () (vars ...) ! . rest) (%block () (vars ...) . rest)) ;; On every exclamation with binding, wrap into a let ;; First multiple values, then regular ones ((%block ((mv-var mv-vars ...) <- form) (vars ...) ! . rest) (let*-values (((mv-var mv-vars ...) form) ((var) mv-var)) (%block () (vars ... var) . rest))) ((%block ((mv-var mv-vars ...) <- forms ...) (vars ...) ! . rest) (let*-values (((mv-var mv-vars ...) (%%block () forms ...)) ((var) mv-var)) (%block () (vars ... var) . rest))) ((%block (var <- form) (vars ...) ! . rest) (let ((var form)) (%block () (vars ... var) . rest))) ((%block (var <- forms ...) (vars ...) ! . rest) (let ((var (%%block () forms ...))) (%block () (vars ... var) . rest))) ;; Insert exclamation for let-s automatically ((%block (var <- forms ...) (vars ...) another-var <- . rest) (%block (var <- forms ...) (vars ...) ! another-var <- . rest)) ;; On every exclamation otherwise, generate a var (useful for _ below) ((%block (form) (vars ...) ! . rest) (let ((var form)) (%block () (vars ... var) . rest))) ((%block (forms ...) (vars ...) ! . rest) (let ((var (%%block () forms ...))) (%block () (vars ... var) . rest))) ;; On every underscore, refer to the innermost var ((%block (forms ...) (vars ... var) _ . rest) (%block (forms ... var) (vars ... var) . rest)) ;; Otherwise, just collect the form ((%block (forms ...) (vars ...) something . rest) (%block (forms ... something) (vars ...) . rest)))) ;; Special minimal-parens syntax ;; Provided niceties: ;; Bindings with var <- form ... ;; Multiple value bindings with (var ...) <- form ... ;; Anonymous / threading / chaining / pipelining vars via _ (block + 1 2 ! * _ 3) ;; Automatic parenthetication of forms until ! mark: + 1 2 3 ! -> (+ 1 2 3) ;; Wisp inline colons: display : + 1 : * 1 -> (display (+ 1 (* 1))) ;; Omission of ! when the next form is a binding (define-syntax block (syntax-rules () ((_ . forms) (%block () () . forms)))) — block macro with line-based syntax But it’s not enough. So I’m currently in the process of brainstorming a new syntax for Scheme. Something like Wisp, but not quite . Oh, and square brackets. These come from R6RS and they are beautiful. And helpful in line-based editing, because they act as diverse anchors for regex! I want to edit my Scheme in the most Spartan circumstances. Including ed(1)—that’d be perfect. COMMUNITY-DRIVEN (#community) Common Lisp is stable. Which is good! But I sometimes wish there was some way to bring new features into the language. And make community standards. Yet, the only such attempt, CDRs … it has died out. Unlike in Scheme—where the process of SRFI (Scheme Requests for Implementation) allows to propose a new language feature and actually see it implemented! So my perfect Scheme is this: community-driven and living-breathing. My go-to list of SRFIs for practical programming is: - SRFI 1: List Library , absolute must-have for managing lists, the most fundamental Lisp data structure. - SRFI 13: String Library and SRFI 43: Vector Library , as the symmetrical libraries for managing the other two standard data structures. - SRFI 26: cut and cute , the compact syntax for throwaway lambdas, especially nice when used with square brackets: `[cut + 1 <>]'. Reminds me of Arc language! - Format strings, starting with SRFI 28: Basic Format Strings and continuing with SRFI 48: Intermediate Format Strings . I mean, it’s not Common Lisp FORMAT, but I rarely need as many Turing-complete features! - SRFI 69: Basic hash tables , because alists are not enough. I wish there was a Clojure-like hash map interface, but that’s an optional. - SRFI 113: Sets and bags , for a fundamental data structure one often needs. - SRFI 151: Bitwise Operations , because I needed these on at least one occasion. - SRFI 204: Wright-Cartwright-Shinn Pattern Matcher or any of the numerous implementations thereof, a hard requirement for functional programming language. - Data (Type-)Checking and Extensions to Data (Type-)Checking . Full disclosure: I’m the author of these, and I can’t live without them! - Further SRFIs by me, but hush! This is a sizeable list, true. But most of these are de-facto standards implemented by most major (and minor) Scheme implementations. So it’s all safe! INTERACTIVE (#interactive) Scheme implementations have REPLs—almost always. While it’s quite saddening and crude at times—it’s still better than most languages. And it’s often the case that things can be introspected from the REPL too! (If they can not in your implementation—I’m coming for you with my new introspection SRFI !) Trying to reverse-engineer an implementation is a pleasure in itself. While I still have to reach for sources of Chibi or Skint, exploring the REPL is a fun way to test and break things. Recommended as perfect exercise in playful programming! SO… PERFECT SCHEME (#perfect) So, my perfect Scheme is the one that can be strict. That stays simple and small. That’s easy to read, edit, and reason about. Community-driven and changeable by consensus. And breakable from inside the implementation too! What is your perfect Scheme? This post is non-linear and is better read in dynamic HTML at . Do not expect it to have a coherent linear narrative. Read for why I do that. CC-BY 4.0 2022-2026 by Artyom Bologov (aartaka,) with one commit remixing Claude-generated code . Any and all opinions listed here are my own and not representative of my employers; future, past and present.