cherry 2022-08-01

Today I'll take a crack at macros I think

πŸ’ 2

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

πŸ’ͺ 2

(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?

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

πŸ‘ 1

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"

πŸ€” 1

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

πŸ‘ 1

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

πŸ‘ 1

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 ;)

🀌 1
πŸ™Œ 1

(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 macro

likewise, inline macros would be emitted to foo$macros.mjs instead of foo.mjs

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 :)

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

πŸ‘€ 1

These are very good libraries. It would be nice to have an elegant way to use them from CLJS πŸ™‚

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

πŸ‘ 5