Experimental browser for the Atmosphere
{ "uri": "at://did:plc:7oyzfpde4xg23u447zkp3b2i/com.whtwnd.blog.entry/3lnhsln7l662v", "cid": "bafyreifii4sgwqhnbcmplubfhfb2fzek4yzhckhpqe4t6fhftwiuck564e", "value": { "$type": "com.whtwnd.blog.entry", "theme": "github-light", "title": "On prelude modules in Racket", "content": "[Racket](https://racket-lang.org/) is an odd language.\n\nThe first thing everyone notices is the S-expressions. But that misses the _actual_ most important thing to pay attention to first: the `#lang` line at the top of the file.\n\n```\n#lang racket\n\n(define (square x)\n (* x x))\n```\n\nYou can put [almost anything you want there](https://docs.racket-lang.org/guide/hash-lang_syntax.html), as long as it's the name of a module. That module is then used to parse and compile the rest of the file. Racket, at its core, is a language for defining new programming languages, including [decidedly un-Lispy languages](https://rhombus-lang.org/).\n\nOne thing a `#lang` implementation can define is which names are in scope by default in a module written in that language. In the code above, `define` and `*` are both built-in bindings provided by `#lang racket`. But they're also defined in the `racket/base` module like any other macro or function (or operator, or class, or what have you) would be. You could import them directly with `(require racket/base)`. So how exactly does `#lang racket` bring them into scope?\n\n## Prelude modules\n\nWhen a `#lang` reads a program, it converts the raw text of the file through whatever custom parser it wants until it produces an output S-expression that looks like this:\n\n```\n(module foo prelude-module\n ... body ...)\n```\n\n...where `foo` is an arbitrary name for this new module, and `prelude-module` is the name of some installed module that should be imported into this module to provide the initial bindings for the whole module body. The module body itself might request more modules with `(require another-module)`, but that's only possible if `prelude-module` brings the `require` form into scope in the first place! Choosing a `prelude-module` that _doesn't export_ any way to require additional modules essentially creates a module that's disallowed _at the language level_ from importing other modules. Some `#lang` implementations, like [`#lang info`](https://docs.racket-lang.org/raco/info_rkt.html), use this trick to let them function more like configuration and data format languages than general purpose programming languages.\n\n\n## On naming\n\nThe Racket community doesn't have a good name for this pattern. I think that's a shame, personally. My choice of \"prelude module\" for this isn't sanctioned; you won't find it anywhere in the documentation. But I'm writing this in an idle hope that this might change someday. It's basically equivalent to how custom preludes in Haskell work, and I think it's a useful term to have.", "createdAt": "2025-04-23T18:21:47.938Z", "visibility": "public" } }