7 ms·
I'm a heavy user of Common Lisp, and I only dabble in Racket from time to time. While Common Lisp is my tool of choice for a lot of reasons stated in the post,
by reikonomusha 4y ago
I'm a heavy user of Common Lisp, and I only dabble in Racket from time to time. While Common Lisp is my tool of choice for a lot of reasons stated in the post, since the post largely skews favorably toward Common Lisp, I'll offer two things that Racket shines at:
1. Documentation
Racket has a beautiful documentation system called Scribble [0]. Almost all documentation written for Common Lisp is ugly, inconsistent, and doesn't follow any sort of standard. Common Lisp has a gazillion "documentation generators" which do little more than slurp up all the documentation strings and barf them back out as e.g. HTML. Common Lisp users tend to just rely on READMEs + jump-to-definition to learn how a project works. A few Lisp programmers (e.g., Shinmera, Edi Weitz) have made their own documentation systems that look better than average, but are still at least an order of
magnitude less capable than Racket's offering.
Racket's documentation system is aesthetically pleasing, very well organized, and makes the programmer want to write long-form documentation. Moreover, Racket (the language) sets a superlative example
of what its users can strive for in terms of quality of clear, no-nonsense writing.
2. Languages and DSLs
One of Lisp's (any dialect) defining features is the ability to build domain-specific languages, syntactic extensions, and the like by way of a special kind of function that can run prior to the code's execution. These special functions are called "macros", and across Lisp dialects, there are lots of different takes on them.
Common Lisp gives you a grab bag of facilities for writing code that lets you change syntax at different levels: DEFMACRO (scoped variant: MACROLET), DEFINE-COMPILER-MACRO, DEFINE-SYMBOL-MACRO (scoped variant: SYMBOL-MACROLET), SET-MACRO-CHARACTER, and a few others. At best, I'd consider these only loosely related, and they all work on really "raw" data representations (parsed S-expressions and character streams). In part because of this, ANSI Common Lisp doesn't provide access to any notion of environment, source location, etc. when writing new macros.
Macros defined by the above operators "live" in different places and are compartmentalized in different ways. There are some Lisp libraries that help you tame definitions and keep them fenced in, like NAMED-READTABLES which helps you organize where reader macros (macros which change how Lisp is parsed at the character-level) live and how to turn them off and on in a sane way.
Contrast with Racket which has explicit documentation on how to write macros [1,2], how to debug them [3], and how to encapsulate them a new DSL embedded in the system [4]. Racket—as a technical system—clearly defined the phases of compilation, what objects are manipulated during the phases (e.g., "syntax objects"), how to provide debugging information, and so on.
In the end, Common Lisp's system for making DSLs feels a little runny. It's supremely hacker-friendly, but it's a bit more difficult to deliver, in one fell swoop, a language as a library. Racket, on the other hand, at the expense of having increased rigidity and needing to read two or three complete chapters of a manual, you get a way to neatly bundle up a language. This is why Racket had nice DSLs like Typed Racket, Hackett, Scheme RnRS, etc.
I use Common Lisp and I develop relatively large DSLs in the language, and even if I want to, it's hard—if not impossible—to write large DSLs that have nice error messages, source locations, etc. without writing an entirely new "reader" (lisp's term for a code parser) myself every time. Smaller DSLs where user code doesn't exceed more than a few lines? Examples like:
- regex syntax
- string interpolation syntax
- a pattern matching feature
Common Lisp is great, and you get a ton of mileage out of the facilities it provides. But larger ones? Examples like:
- an embedded language for writing documentation with markup
- a complete language with lazy evaluation semantics
- a language for non-deterministic programming (like SCREAMER, Prolog)
- a language for strictly typed functional programming (like Coalton, Hackett)
Tackling these in Common Lisp requires ambition and a very steady hand, whereas Racket's overall system (language, implementation, documentation) makes that a comparatively painless exercise.
[0] https://docs.racket-lang.org/scribble/ https://docs.racket-lang.org/scribble/
[1] https://docs.racket-lang.org/guide/macros.html https://docs.racket-lang.org/guide/macros.html
[2] https://www.greghendershott.com/fear-of-macros/ https://www.greghendershott.com/fear-of-macros/
[3] https://docs.racket-lang.org/macro-debugger/index.html https://docs.racket-lang.org/macro-debugger/index.html
[4] https://docs.racket-lang.org/guide/languages.html https://docs.racket-lang.org/guide/languages.html
- db48x 4y agoI’ve never used Racket, but reading your description of the documentation (and Scribble) makes me want to check out that aspect of it. I wonder how it compares with rustdoc? I do mostly Rust these days and its generated documentation is usually really good.
- djhaskin987 4y agoThe only other equal I've seen to scribble around is Python's Sphinx. I use Sphinx for all my projects no matter what language they're in because it's so good.
- db48x 4y agoOh, I see. Scribble looks a little like TeX, but actually it’s an alternate read syntax/dsl. The first example where they show off the real utility of it is: @tabular[#:sep @hspace[1] (list (list @bold{Animal} @bold{Food}) (list "mouse" "cookie") (list "moose" "muffin"))] which really drops it in your lap. “tabular” isn’t from some fixed list of keywords, it is an ordinary racket function that you can call, with ordinary arguments that can be strings, lists, etc. Presumably there will be an example in the next page or two with @define[…] in it to close the loop. And the table of contents promises literate programming as well. Someone was really thinking when they put this together; I really like it. The documentation generated for individual functions (such as <https://docs.racket-lang.org/scribble/base.html#(def._((lib._scribble%2Fbase..rkt)._italic)) https://docs.racket-lang.org/scribble/base.html#(def._((lib....>) is fairly nice as well. Readable, linked together by hyperlinks on the type predicates as well as hyperlinks in the body of the documentation itself; it’s pretty good. Rustdoc puts links to the source code for each one though, and I would soon miss that.
- lispm 4y agoSee Scribe from the end 70s: https://en.wikipedia.org/wiki/Scribe_(markup_language) https://en.wikipedia.org/wiki/Scribe_(markup_language)
- 4y ago