[HN Gopher] Clojure macros continue to surprise me
___________________________________________________________________
Clojure macros continue to surprise me
Author : jgrodziski
Score : 105 points
Date : 2024-07-22 22:28 UTC (4 days ago)
(HTM) web link (tonsky.me)
(TXT) w3m dump (tonsky.me)
| seancorfield wrote:
| The fifth time this has been posted in just over a week... it
| must be a REALLY good article! :)
| seancorfield wrote:
| My bad... the sixth time (the fifth time was under a different
| title).
| kazinator wrote:
| > _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).
| chii wrote:
| it can't be a function because the source is only guaranteed to
| be available at compile time, rather than at runtime.
| phoe-krk wrote:
| A macro is a perfectly normal function. It's simply called at
| compile time, with its contents as its argument.
| chii wrote:
| no you're misunderstanding me.
|
| I'm saying that the macro's purpose is to read the source
| file and produce something at compile time. It's not a
| "normal" (read: runtime) function, as this feature cannot
| be implemented using a function that only executes at
| runtime.
| phoe-krk wrote:
| Sure, but the macro function _is_ a normal function - it
| has inputs, outputs, and (preferably) should be pure.
| There 's nothing special about the macro function itself,
| really.
|
| You can see it explicitly in Common Lisp, where you can
| easily access and replace a macro-function with a custom
| one that you DEFUN yourself, like this.
| CL-USER> (defun my-quote-fn (form env)
| (declare (ignore env)) (destructuring-
| bind (operator &rest body) form
| (assert (eq operator 'my-quote))
| (list* 'quote body))) MY-QUOTE-FN
|
| It's a perfectly normal function, I can call it right
| away: CL-USER> (my-quote-fn '(my-quote
| (+ 2 2)) nil) '(+ 2 2)
|
| I can also tell the compiler that this perfectly normal
| function is a macro function... CL-
| USER> (setf (macro-function 'my-quote) #'my-quote-fn)
| #<FUNCTION MY-QUOTE-FN>
|
| ...and the compiler will start using it to expand macros:
| CL-USER> (macroexpand-1 '(my-quote (+ 2 2))) '(+
| 2 2) T
|
| What is special about macros, and what you describe as
| the "feature that cannot be implemented", is the
| language's ability to call that function at compilation
| time and feed its output back into the compiler. This is
| the link that is missing in most programming languages.
| chii wrote:
| > call that function at compilation time
|
| that's why it's called a macro and not a function. You're
| using the word function in the mathematical sense, and
| not in the lisp sense.
| KingMob wrote:
| I mean, _sorta_ , but Lisp macro writers learn early on
| that macros are also functions, it's just they operate on
| a list of symbols, and are called in a different phase.
|
| So saying "it can't be a function" doesn't ring true,
| either.
| kazinator wrote:
| The irrefutable fact is that the author presents the core
| logic of the file-reading solution as a _defn_ , not a
| _defmacro_. It may be driven by a macro, but evidently
| that part is not noteworthy enough to be presented.
| lispm wrote:
| It could be a helper-function which gets called by a macro
| expansion at compile-time. One would define the feature as a
| function which is available at compile-time and the macro
| calls it during expansion.
| kazinator wrote:
| It's reading code from a file, which you can make
| arrangements for to be there at any time you want.
|
| Files are not directly relevant to macros.
|
| The code that a macro invocation has access to _by design_ is
| the datum which comprises that invocation itself. That may
| have come from an expression in a file, and may be annotated
| in some way with the location info. Other than that, macros
| do not necessarily have access to their textual source code.
| No such thing necessarily exists because macros can be
| invoked by code that was generated (possibly by other macros
| or in other ways) and so never existed as characters in a
| file.
|
| I don't have a clear picture of the architecture of the
| system that the article refers to, but the author presents,
| as his end result, a _defn_ form, which defines a function
| and not a macro.
|
| If a macro is needed to get that function to be called at
| compile time, that would be a minor hooking detail, and not
| an instance of a macro being used to perpetrate a complex
| code transformation. The function just needs a file name and
| a string.
|
| (It looks as if the author's system may be interactive; the
| code files are there all the time and can be changed, and he
| wants the view of the files to refresh?)
| josefrichter wrote:
| 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.
| compacct27 wrote:
| 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
| layer8 wrote:
| An AST can carry the original source representation on each
| node as metadata -- best of both worlds.
| pjmlp wrote:
| Just wait for using full blown CL macros then.
| packetlost wrote:
| CL macros are more or less the same as Clojure macros and
| neither hold a candle to modern Scheme or Racket macros :)
| BoingBoomTschak wrote:
| Eh? Pretty sure they're more powerful. And readable, unlike
| the Scheme ones that look as elegant and well integrated as a
| caravan being pulled by a Porsche =).
| yladiz wrote:
| Used in the way the author of the article presents, or
| generally the way you could use them in Clojure, Racket and
| Scheme macros are pretty similar. However at least in
| Racket you have a lot more control over certain parts of
| the way compilation affects the macro (look up phases for
| example). I assume Scheme has similar constructs, as does
| CL.
|
| Note this is ignoring anything about the reader, for which
| Racket has substantially more powerful functionality around
| than Clojure.
| kazinator wrote:
| 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
| fithisux wrote:
| Wow!
| lloydatkinson wrote:
| 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.
___________________________________________________________________
(page generated 2024-07-26 23:11 UTC)