July 22nd, 2024

Clojure macros continue to surprise me

The author shares their journey using Clojure macros for Humble UI's component library, creating a macro to display running code alongside its source. Despite challenges, they find Clojure's flexibility rewarding for experimentation.

Read original articleLink Icon
Clojure macros continue to surprise me

The author discusses their experience with Clojure macros while working on Humble UI's component library. They wanted to document the library and use it as an integration test by showcasing every possible option. They created a macro that generates a table displaying running code alongside its source. Initially, existing formatters didn't meet their needs, leading them to consider reading the source file for formatting. Eventually, they implemented a function that reads the source file, finds the desired string, removes common indentation, and renders it. Despite acknowledging the unconventional approach, the author finds working with Clojure macros enjoyable for enabling experimentation with code in ways not possible in other languages. The flexibility and simplicity of Clojure allow for unconventional solutions like reading source files directly within the code.

Related

Igneous Linearizer: semi-structured source code

Igneous Linearizer: semi-structured source code

The Igneous Linearizer project enhances source code in Obsidian Markdown format, enabling features like links and transclusion. It sacrifices AST correctness for compatibility with text editors and Git, benefiting literate programming. Users must follow specific input file formats for optimal use.

Why Use Clojure for Machine Learning?

Why Use Clojure for Machine Learning?

Clojure's functional paradigm benefits machine learning with modular, readable, and predictable code. Leveraging JVM ensures speed and portability. Integration with Java libraries like TensorFlow and PyTorch supports deep learning. Despite being less mature, Clojure shows promise in ML projects.

It's all up for grabs, compound with glue

It's all up for grabs, compound with glue

The article explores Emacs customization using elisp, emphasizing combining utilities like symbol-overlay and multiple-cursors for enhanced workflow efficiency. It also mentions a new blogging service at lmno.lol.

It's all up for grabs, compound with glue

It's all up for grabs, compound with glue

The article explores Emacs customization using elisp, emphasizing combining utilities for efficiency. Integrating symbol-overlay and multiple-cursors enhances workflow by streamlining tasks like symbol renaming. Users modify elisp functions in Emacs for desired features. A new blogging service at lmno.lol is mentioned.

Tonsky: Don't go crazy with Clojure unless it makes you happy

Tonsky: Don't go crazy with Clojure unless it makes you happy

The author shares their journey using Clojure macros for Humble UI's component library, emphasizing the language's flexibility in enabling unconventional solutions like reading source files and fetching code seamlessly.

Link Icon 11 comments
By @lloydatkinson - 3 months
I really enjoyed reading this, and I don't know Clojure at all. This is a problem I've encountered several times while building design systems/component libraries. It's a very universal documentation problem, regardless of what platform it's targeting.

I have done the same with React - but it gets messy. Something nice like in the post would be great for that.

Too often I see very poor Storybook sites in the wild (a web based design system visual explorer, somewhat like the screenshot in the post) where no thought was given to this exact problem.

Instead of something meaningful like in the post, I see:

    () => <TextInputStorybook />
As the only avaliable code. The point of the code blocks is so people can copy paste. When I see that, I know I'm in for a rough ride because the code quality inevitably isn't any good either.

For reference, this is easily the best design system I know of: https://seek-oss.github.io/braid-design-system/components/Te... I use this as a reference when I work on my own.

By @josefrichter - 3 months
In elixir you can run docs as (init) tests too. The doctest macro is generating the tests from the docs within the module. Maybe this might be interesting together with this article.
By @seancorfield - 3 months
The fifth time this has been posted in just over a week... it must be a REALLY good article! :)
By @kazinator - 3 months
> What if our macro read the source file?

> Like, actually went to the file system, opened a file, and read its content? We already have the file name conveniently stored in file, and luckily Clojure keeps sources around.

> So this is what I ended up with:

> (defn slurp-source [file key]

Looks like a function to me (which is good).

By @compacct27 - 3 months
The real trick here is dodging ASTs, which, after trying to use in so many parse-the-code projects, really aren’t needed all the time but are put pretty highly on the pedestal
By @simply-typed - 3 months

  sometimes a vector is just a vector, but sometimes it’s a UI component and shows the structure of the UI.
Easily the worst aspect of Clojure. Everything is an untyped map or an untyped vector. Your editor can't tell the difference between a vector of keywords and some DSL for a macro. Things like this make Clojure a nightmare to refactor and scale poorly relative to languages like TypeScript.
By @pjmlp - 3 months
Just wait for using full blown CL macros then.
By @kazinator - 3 months
TXR Lisp:

  $ cat slurp-source.tl
  (defun slurp-source (path key)
    (let* ((lines (file-get-lines path))
           (match (member-if (op contains key) lines))
           (tail (rest match))
           (indent (find-min-key tail : (op match-regex @1 #/\s*/)))
           (dedent (mapcar (op drop indent) tail)))
      `@{dedent "\n"}\n`))
  $ txr -i slurp-source.tl
  1> (put-string (slurp-source "slurp-source.tl" "let*"))
       (match (member-if (op contains key) lines))
       (tail (rest match))
       (indent (find-min-key tail : (op match-regex @1 #/\s*/)))
       (dedent (mapcar (op drop indent) tail)))
  `@{dedent "\n"}\n`))
  t