This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-11-19
Channels
- # announcements (3)
- # asami (3)
- # babashka (39)
- # beginners (65)
- # calva (13)
- # cider (4)
- # clj-kondo (69)
- # cljdoc (19)
- # cljs-dev (2)
- # clojure (90)
- # clojure-dev (10)
- # clojure-europe (61)
- # clojure-france (15)
- # clojure-nl (8)
- # clojure-uk (2)
- # clojurescript (28)
- # conjure (2)
- # core-logic (4)
- # cursive (8)
- # datalevin (5)
- # datascript (7)
- # datomic (14)
- # depstar (4)
- # events (1)
- # graphql (7)
- # holy-lambda (5)
- # jobs (5)
- # kaocha (1)
- # malli (14)
- # membrane-term (13)
- # missionary (13)
- # nextjournal (6)
- # off-topic (1)
- # polylith (15)
- # portal (10)
- # re-frame (35)
- # reitit (1)
- # remote-jobs (3)
- # schema (3)
- # sci (121)
- # spacemacs (6)
- # tools-build (8)
- # tools-deps (74)
- # xtdb (7)
I think I'm going to see if it is practical to use sci
instead of the bootstrap build we're currently using in my project. Initial tests are looking good but I have more work to do.
one other benefit is that SCI can also work with advanced compilation, so the rest of your libs will be small
The biggest challenge for me isn't really sci's fault, but it's that you can't enumerate cljs namespaces.
So I need to come up with a solution for that on my own and maintain a list of namespaces (and then walk them, to give sci access to the vars).
@pmooser A macro is invoked in the JVM, you can use that phase to enumerate namespaces and then generate the CLJS code for the SCI config
That's an interesting idea except I'm not sure exactly at what phase all of the cljs namespaces will be visible to the clj macro (since it's not like they exist as actual clj namespaces).
Ah ok - that is a good hint. I will give it a look - I've never dug into it, and know very little about it. But thank you so much!
Having said that, I often just enumerate things manually because that always works ;)
Ok last question - to experiment, should I just be able to require that cljs.analyzer.api
and experiment in a clj namespace ?
Don't take up your time doing this, please - I can experiment and read the code and stuff. And as you say, manually enumerating isn't a bad option. Or I could probably make my own ns
macro with some side-effects that does the enumeration for me, if I only care about my own namespaces.
When I add a macro in .clj
file, e.g. I added one in sci.core:
(require '[cljs.analyzer.api :as api])
#?(:clj
(defmacro foo []
(println (api/all-ns))
nil))
And then in a CLJS repl:
cljs.user=> (require '[sci.core :refer-macros [foo]])
nil
cljs.user=> (foo)
I get a ton of namespaces:
(sci.impl.opts cljs.env.macros sci.impl.macros cljs.tools.reader.impl.commons sci.impl.types cljs.tools.reader.edn
Ok perfect. So I guess we can rely on the analyzer in our cljs macros and all the namespaces have already been analyzed or whatever. Thank you, borkdude.
To be specific: this is in normal CLJS and the macro is running in the JVM, calling the cljs.analyzer.api in the JVM.
Then all-ns
enumerates all CLJS namespaces create so far, after having already loaded/compiled those.
So if I do this:
#?(:clj
(do
(require '[cljs.analyzer.api :as api])
(defmacro foo []
(prn (api/ns-publics (first (api/all-ns))))
nil)
))
I will get back a map of all public CLJS "vars" of the first namespace:
{default-classes {:name sci.impl.opts/default-classes, :file "/Users/borkdude/Dropbox/dev/clojure/babashka/sci/src/sci/impl/opts.cljc", :line 47, :column 1, :end-line 47, :end-column 21, :meta {:file "/Users/borkdude/Dropbox/dev/clojure/babashka/sci/src/sci/impl/opts.cljc", :line 47, :column 6, :end-line 47, :end-column 21}, :tag cljs.core/IMap},
so this way you can get the names of all the public vars of all the namespaces at compile time
This is perfect - I think it's a great trick potentially. When you said before that all-ns
enumerates all namespaces created "so far", I'm not exactly sure what that means - maybe just so far in terms of that point in the compilation or macroexpansion of all the namespaces?
I expect that when you don't load any namespaces, it doesn't go out and read stuff from disk.
So you first have to load namespaces and then call all-ns
to see what namespaces exist
Yes, makes sense. I find cljs macros so confusing (compared to clj). I'm still puzzling out certain issues, but this will certainly work! Thank you again.
yes, it's confusing because they run at compile time in the JVM but then target CLJS code
Yea but I mean like ... ok, here's a macro:
(defmacro cljs-namespaces
[]
(let [namespaces (map ns-name (ana/all-ns))]
`(list [email protected])))
So I always find things a bit more "weird" even than in normal macros, which are sometimes substantially weird to begin with.
Yeah, I just need to quote the symbols, because in the macro the symbol gets expanded to the actual namespace object itself.
I misunderstood the doc string for ns-name, since it ends with "a symbol", but it clearly means the ns
argument is a symbol. And yeah, it all makes sense.
(Like most things in software, when it seems like something crazy is happening, it's usually my fault)
I think I've got something:
cljs.user=> (def ns-map (copy-ns clojure.edn (sci.core/create-ns 'clojure.edn nil)))
#'cljs.user/ns-map
cljs.user=> (def read-string (get ns-map 'read-string))
#'cljs.user/read-string
cljs.user=> (read-string "{:a 1}")
{:a 1}
I'm still struggling a bit in cljs macro universe, but I know this will all eventually work.
This is the impl:
(defn copy-ns-fn [ns-publics-map sci-ns]
(reduce (fn [ns-map [var-name var]]
(let [m #?(:clj (meta var)
:cljs (:meta var))
no-doc (:no-doc m)
doc (:doc m)
arglists (:arglists m)]
(if no-doc ns-map
(assoc ns-map var-name
(new-var (symbol var-name) #?(:clj @var
:cljs (:val var))
(cond-> {:ns sci-ns
:name (:name m)}
(:macro m) (assoc :macro true)
doc (assoc :doc doc)
arglists (assoc :arglists arglists)))))))
{}
ns-publics-map))
#?(:clj
(defmacro copy-ns [ns-sym sci-ns]
(macros/? :clj `(copy-ns-fn (ns-publics ~ns-sym) ~sci-ns )
:cljs (let [publics-map (cljs.analyzer.api/ns-publics ns-sym)
publics-map (zipmap (map (fn [k]
(list 'quote k))
(keys publics-map))
(map (fn [m]
{:name (list 'quote (:name m))
:val (:name m)
:meta (select-keys (:meta m) [:arglists
:no-doc
:doc])})
(vals publics-map)))]
;; (prn publics-map)
`(copy-ns-fn ~publics-map ~sci-ns)))))
I'll give it a try on my side as well while trying to banish the bootstrap build from my application.
@pmooser I added copy-ns
to the sci.core namespace now.
(defmacro copy-ns
"Returns map of names to SCI vars as a result of copying public
Clojure vars from ns-sym (a quoted symbol). Attaches sci-ns (result
of sci/create-ns) to meta. Copies :name, :macro :doc, :no-doc
and :argslists metadata."
([ns-sym sci-ns] `(copy-ns ~ns-sym ~sci-ns nil))
([ns-sym sci-ns _opts]
It still requires you to provide the name of the namespace.
(copy-ns 'clojure.edn (sci/create-ns 'clojure.edn))
Added `copy-ns` to the sci.core namespace now.
(defmacro copy-ns
"Returns map of names to SCI vars as a result of copying public
Clojure vars from ns-sym (a quoted symbol). Attaches sci-ns (result
of sci/create-ns) to meta. Copies :name, :macro :doc, :no-doc
and :argslists metadata."
([ns-sym sci-ns] `(copy-ns ~ns-sym ~sci-ns nil))
([ns-sym sci-ns _opts]
Usage:
(copy-ns 'clojure.edn (sci/create-ns 'clojure.edn))
It's a macro to provide the same interface on CLJS as in CLJ.Ah, the feeling of deleting stuff. https://github.com/babashka/babashka/commit/1ebe1426557b5a28841a1e359fd1180d9b7dbba9

it works for macros in CLJ, but not for macros in CLJS since they are (normally, not-self-hosted) defined in the JVM environment, so SCI/CLJS cannot see them.
So this is quite interesting:
(ns foo.bar
#?(:cljs (:require-macros [foo.bar :refer [foo]])))
(defmacro foo []
`(println :hello))
(ns foo.foo
(:require [foo.bar] :reload
[sci.core :as sci]))
(def sci-ns (sci/copy-ns 'foo.bar (sci/create-ns 'foo.bar)))
$ clj -Sdeps '{:deps {org.clojure/clojurescript {:mvn/version "1.10.879"}}}' -M -m cljs.main -re node
ClojureScript 1.10.879
cljs.user=> (require '[foo.foo] :reload)
nil
cljs.user=> foo.foo/sci-ns
{foo #'foo}
cljs.user=> ((get foo.foo/sci-ns 'foo))
(cljs.core/println :hello)
cljs.user=> (sci/binding [sci/print-fn *print-fn*] (sci/eval-string "(foo.bar/foo)" {:namespaces {'foo.bar foo.foo/sci-ns}}))
:hello
nil
So it even seems to work for macros if they don't do weird compile time things in the JVM
Oh, if you put those in reader conditionals then they aren't copied :)
(defmacro foo []
#?(:clj (prn (System/getProperty "user.dir")))
`(println :hello))
I changed the syntax. it's now `
(sci/copy-ns foo.bar (sci/create-ns 'foo.bar))
for reasons. I also introduces options; {:include [foo] :exclude [bar]}
Is there a reason why it only copies the :macro
,`:doc` ,and :arglists
metadata keys?
because those are usually the only ones I'm interested in... do you have a reason to copy more?
I use it for membrane in the mobiletest project
since I use metadata for the component library
It's not a huge deal, but it was somewhat of a surprise.
that the metadata was missing
https://github.com/phronmophobic/mobiletest/blob/main/src/com/phronemophobic/mobiletest/scify.clj#L46 I created a similar function based off the example code in the sci project
theoretically, I could consider replacing my implementation with the builtin one, but not without the metadata
yes, but this was after your surprise. I'm trying to learn what you used at the moment of your surprise
I used this example code:
(reduce (fn [ns-map [var-name var]]
(let [m (meta var)
no-doc (:no-doc m)
doc (:doc m)
arglists (:arglists m)]
(if no-doc ns-map
(assoc ns-map var-name
(sci/new-var (symbol var-name) @var
(cond-> {:ns fns
:name (:name m)}
(:macro m) (assoc :macro true)
doc (assoc :doc doc)
arglists (assoc :arglists arglists)))))))
{}
(ns-publics 'foobar))
yea, I was checking out the implementation and recognized it! 😄
one consideration is that in some envs you don't want to copy the docstrings for example
yea, that makes sense
bundle size for common sci targets like graalvm and cljs both make sense to exclude unnecessary/misleading metadata
I might also suggest a 1-arity call to copy-ns
that defaults to (sci/create-ns ns-sym)
as the sci-ns
yeah, I also thought of that. would it be ok if you could pass a set of metadata keys or an option to select all? since this is a macro the options are kinda static
I guess if you choose all metadata you can postprocess the vars as well (could expose alter-meta! in the sci API)
Oh I guess it already works with regular alter-meta!
user=> (meta (doto (sci/new-var 'foo) (alter-meta! assoc :foo 1)))
{:foo 1}
sound great
it doesn't look like my original ns->ns-map
got checked into git
(defmacro copy-ns
"Returns map of names to SCI vars as a result of copying public
Clojure vars from ns-sym (a symbol). Attaches sci-ns (result
of sci/create-ns) to meta. Copies :name, :macro :doc, :no-doc
and :argslists metadata.
Options:
- :include: a seqable of names to include from the namespace. Defaults to all.
- :exclude: a seqable of names to exclude from the namespace. Defaults to none.
- :copy-meta: a seqable of keywords to copy from the original var meta.
Use :all instead of a seqable to copy all. Defaults to [:doc :arglists :macro].
- :exclude-when-meta: seqable of keywords; vars with meta matching these keys are excluded.
Defaults to [:no-doc :skip-wiki]"
I probably won't get a chance to look at it soon, but looks :thumbsup: