Today I'll take a crack at macros I think
Got this example working locally now:
$ cat corpus/macros.cljs
(ns macros)
(defn do-twice [_f _e x]
`(do ~x ~x))
$ cat corpus/macro_usage.cljs
(ns macro-usage
(:require-macros ["./macros.mjs" :refer [do-twice]]))
(do-twice (prn :hello))
$ node corpus/macro_usage.mjs
:hello
:hello
(didn't implement defmacro yet, this is why the do-twice macro uses defn )
This is what macro_usage.mjs looks like:
import { prn, keyword } from 'cherry-cljs/cljs.core.js'
prn(keyword("hello"));
prn(keyword("hello"));
export { }
> Macros need to run in the same environment as the transpiler. Therefore it makes sense to force users to declare macros in separate .cljc files: .cljc so the cherry transpiler can run in both clojure/bb and JS environments.
this would be doing it how normal (JVM-compiled) ClojureScript does it today, right?
also just came across https://github.com/mfikes/chivorcam
yes. but the above approach is different and only works in JS. This is the "transpiled macro" approach. First transpile the macro .cljs file. Then load the JS in the compiler and use the macros during compilation.
ah so you went with the second option in the issue
yes, exploring that right now
You could transpile your macros and distribute them along your libraries and it would "just work" TM with the normal cherry compiler which is small
sounds good, I guess the put them into a different file question is independent of this, right?
if you don't put them in separate files, it becomes more complicated (like in .cljs). it could be made to work, but you'll have to maintain some bookkeeping in a separate .edn file about which vars are macros, etc.
hmm, but self-hosted cljs can just declare them in the same file using defmacro
and a single file would them compile down to two separate files: one .mjs and one $macros.mjs.
> but self-hosted cljs can just declare them in the same file using This is what you think, but actually it doesn't work like "just declare them in the same file"
cljs.user=> (defmacro defp [name & body] (def name (do @body)))`
#'cljs.user/defp
cljs.user=> (defp x 1)
^
WARNING: Use of undeclared Var cljs.user/x at line 1
(def nil (do))
self-hosted CLJS compiles a single namespace also into two separate namespaces: one macro namespace and one normal .cljs namespace
it's exactly the same problem
ok, but as a user I can just declare them in the same file
yes, with caveats
as I said here: https://clojurians.slack.com/archives/C03QZH5PG6M/p1659354955084689 it's a similar problem, CLJS has done extra work to enable this. cherry could do too, but first gotta decide which approach is the most promising
thanks for explaining
If you write a .cljc file and put a println in there, and run it through self-hosted CLJS, you will see it printed twice because first it evaluates the whole thing as a macro namespace
(ns foo
(:require-macros [foo]))
(defmacro foobar [])
(prn :foo)
$ plk /tmp/foo.cljc
WARNING: foo is a single segment namespace at line 1
WARNING: foo$macros is a single segment namespace at line 1
:foo
:foo
oh, I thought you could write defmacro in a .cljs in self-host
you can but that doesn't work like you think it does
in #nbb however it does work like you think it does ;)
(ns foo)
(defmacro foobar [x]
`(do ~x))
(prn (foobar 1))
$ plk /tmp/foo.cljs
WARNING: foo is a single segment namespace at line 1
(do nil)
So with an interpreter approach (vs the transpiled macro approach), you could do the above, but then you would have to evaluate the .cljs namespace first in the interpreter so macros are loaded, and then you can transpile the .cljs file (while ignoring the macro definition but expanding the calls)
But this complicates sharing the macro between other files
In the transpiled approach you could also run through the .cljs file once looking for macros, emit only the macro stuff to a separate .mjs file, then load that, etc.
This is where the trade-offs currently are
But supporting "inline" macros is a bit tricky as you now understand, so we can defer that as an optimization: it's not impossible but too early to optimize for
I'd say we push the transpiled macro approach further so cherry becomes a more standalone thing vs relying on SCI
If that doesn't work out, we can always evaluate the SCI route then
yeah, sounds good, especially if you think you can still add support for inline macros down the road
yeah, that's unrelated to either direction
btw, this approach also allows you to write macros in JS files. I don't know why you would do that, but you could ;)
Maybe pull in preact for server side rendering in a macro or so
By convention we could have
foo.mjs
foo$macro.mjs
and when you (:require ["./foo.mjs" :refer [bar]) then foo$macros would be loaded in the compiler and there would be a check if bar is macrolikewise, inline macros would be emitted to foo$macros.mjs instead of foo.mjs
https://github.com/aidenybai/million is the new preact π
conincidentally also works well with a compiler: > While alternative libraries like https://preactjs.com/ reduce bundle sizes by efficient code design, Million takes it a step further by leveraging compilation to make a quantum leap in improving bundle size and render speed.
Yeah, this is why maybe compiling to .jsx (see channel) might be better, then the JS tooling can just do whatever like they like this week
Off topic: a different thought I had was that cherry could directly compile to .jsx so your JS tools could process the "HTML" stuff
or have a macro that compiles to jsx?
yes, exactly, but a macro is part of the compilation :)
yep
sorry, talking about the same thing then
btw @jackrusher and @mhuebert did some interesting experiments a while back using toxiβs umbrella libs like https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-html
These are very good libraries. It would be nice to have an elegant way to use them from CLJS π
@mhuebert has joined the channel
Demo of macro support: https://twitter.com/borkdude/status/1554189378464915460 and found 1 bug in cljs proper while implementing this :) https://clojurians.slack.com/archives/C07UQ678E/p1659381397764059