Macros and type hints

315 views
Skip to first unread message

Mike Thompson

unread,
Mar 23, 2015, 3:58:27 AM3/23/15
to clojur...@googlegroups.com
I have a macro:

(defmacro m
[x]
`(if-not ^boolean js/goog.DEBUG ~x)) ;;; NOTICE the type hint


I use it:

(macroexpand-1 '(m blah))
;; => (clojure.core/if-not js/goog.DEBUG blah)

Notice how the type hint is gone. But I need that type hint there otherwise the if test on js/goog.DEBUG doesn't work.

If I put this in:
(set! *print-meta* true)

then I can see the type hint metadata in there, just not available somehow

So ... I'm stuck. Google searches show up some talk about using "with-meta" but I can't seem to make that work either.

Any help with the correct magic appreciated.

--
Mike






Colin Yates

unread,
Mar 23, 2015, 4:00:58 AM3/23/15
to clojur...@googlegroups.com
`(do
(schema.core/defn
~(with-meta 'create {:always-validate true})
...

worked for me.
> --
> Note that posts from new members are moderated - please be patient with your first post.
> ---
> You received this message because you are subscribed to the Google Groups "ClojureScript" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to clojurescrip...@googlegroups.com.
> To post to this group, send email to clojur...@googlegroups.com.
> Visit this group at http://groups.google.com/group/clojurescript.

Colin Yates

unread,
Mar 23, 2015, 4:26:57 AM3/23/15
to clojur...@googlegroups.com
Note that macroexpand won't show the meta data so you will need to
note the effect some other way (e.g. by running it).

Mike Thompson

unread,
Mar 23, 2015, 4:33:33 AM3/23/15
to clojur...@googlegroups.com
On Monday, March 23, 2015 at 7:00:58 PM UTC+11, Colin Yates wrote:
> `(do
> (schema.core/defn
> ~(with-meta 'create {:always-validate true})
> ...
>
> worked for me.


Thanks, Colin. So the version I need would look like this?

(defmacro m
[x]
`(if-not ~(with-meta 'js/goog.DEBUG {:tag boolean}) ~x))

--
Mike

Mike Thompson

unread,
Mar 23, 2015, 4:41:29 AM3/23/15
to clojur...@googlegroups.com
Sigh. Or maybe this:

(defmacro m
[x]
`(if-not (with-meta js/goog.DEBUG {:tag boolean}) ~x))


I'll rig up a test tomorrow. Thanks.

--
Mike

Colin Yates

unread,
Mar 23, 2015, 4:46:00 AM3/23/15
to clojur...@googlegroups.com
I just noticed your reference to (set! *print-meta* true) which does
actually show the meta in macroexpand (at least in my example):

(set! *print-meta* true)
(macroexpand `(do
(schema.core/defn
~(with-meta 'create {:always-validate true}) [])))
=> (do (schema.core/defn ^{:always-validate true} create []))

Secondly, I need to work a bit more to call myself a beginner with
macros, so take everything I say with a good dose of salt ;).

Karsten Schmidt

unread,
Mar 23, 2015, 5:40:37 AM3/23/15
to clojur...@googlegroups.com

Hi Mike, is that by any chance meant for re-frame? If so, I've got an updated fork with refactored logging using cljs-log[1] and will send PR later today...

Cljs-log fully elides logging calls at compile time based on log level (configurable via env vars)

Hth! K.

[1] https://github.com/postspectacular/cljs-log

Mike Thompson

unread,
Mar 23, 2015, 5:58:36 AM3/23/15
to clojur...@googlegroups.com, in...@toxi.co.uk
On Monday, March 23, 2015 at 8:40:37 PM UTC+11, Karsten Schmidt wrote:
> Hi Mike, is that by any chance meant for re-frame? If so, I've got an updated fork with refactored logging using cljs-log[1] and will send PR later today...
>
> Cljs-log fully elides logging calls at compile time based on log level (configurable via env vars)
>
> Hth! K.
>
> [1] https://github.com/postspectacular/cljs-log
>

Hey Karsten,

Thanks, that looks interesting. Would be very nice to be able to set log levels in build spec.

There has been limited discussion on this already, but it has a debugging flavour, rather than a logging flavour (subtle differences):
https://github.com/Day8/re-frame/issues/34

Back to this macro ...

No, this is not for re-frame, but instead for use in "re-com" which is an upcoming set of reagent components (will only work with a modern browser -- so it will be useful for enterprises app perhaps, or when you are doing atom shell work).

Anyway, re-com checks all params very carefully in dev mode, but we need those expensive checks gone for production. That's what I'm chasing here: a way to use goog.DEBUG in a macro (and using goog.DEBUG requires that ^Boolean metadata).

Thanks,
Mike




David Nolen

unread,
Mar 23, 2015, 6:17:52 AM3/23/15
to clojur...@googlegroups.com
type-hints in macros must be treated the same way as in Clojure, using the reader-level type-hint won't work:

(defmacro myfn [name params & body]
  `(defn ~(vary-meta assoc name :tag 'boolean) ~params ~@body))

David

Mike Thompson

unread,
Mar 24, 2015, 3:57:39 AM3/24/15
to clojur...@googlegroups.com
On Monday, March 23, 2015 at 6:58:27 PM UTC+11, Mike Thompson wrote:
> I have a macro:
>
> (defmacro m
> [x]
> `(if-not ^boolean js/goog.DEBUG ~x)) ;;; NOTICE the type hint
>
>
> I use it:
>
> (macroexpand-1 '(m blah))
> ;; => (clojure.core/if-not js/goog.DEBUG blah)
>
> Notice how the type hint is gone. But I need that type hint there otherwise the if test on js/goog.DEBUG doesn't work.
>


For the record ... after waaaaaaay too much messing about, I had to do it this way:


(ns some.namespace)

;; wrap in a function, with the right type hint
(defn ^boolean debug?
[]
js/goog.DEBUG)


;; use this in the macro ...

(defmacro m
[x]
`(if (some.namespace/debug?) ~x))


The Google Closure compiler does deadcode eliminate the "x" if you do it this way. And it avoids the complete nightmare of getting a type hint added to js/goog.DEBUG through a macro (which seems impossible to me).

Question: why exactly is this type hint needed when js/goog.DEBUG is used in an "if"? As far as I can see, js/goog.DEBUG is a boolean already.

--
Mike




--
Mike


Thomas Heller

unread,
Mar 24, 2015, 7:21:32 AM3/24/15
to clojur...@googlegroups.com

>
> Question: why exactly is this type hint needed when js/goog.DEBUG is used in an "if"? As far as I can see, js/goog.DEBUG is a boolean already.
>

Short answer: We don't evaluate or parse any Javascript when compiling ClojureScript. That means we don't know about goog.DEBUG and what type it may be. CLJS is parsed and analyzed so we know a whole lot about it without actually running anything.

David Nolen

unread,
Mar 24, 2015, 7:31:00 AM3/24/15
to clojur...@googlegroups.com
On Tue, Mar 24, 2015 at 3:57 AM, Mike Thompson <m.l.tho...@gmail.com> wrote:
The Google Closure compiler does deadcode eliminate the "x" if you do it this way.  And it avoids the complete nightmare of getting a type hint added to js/goog.DEBUG through a macro (which seems impossible to me).

I just tested the approach that I suggested and verified it works the same as it does in Clojure.

(defmacro when-debug [& body]
  `(when ~(vary-meta 'js/goog.DEBUG assoc :tag 'boolean)
     ~@body))

David 

Mike Thompson

unread,
Mar 24, 2015, 9:04:24 AM3/24/15
to clojur...@googlegroups.com
Many Thanks! That works. Phew, thank goodness.

I can now see that your original code had "assoc" "name" incorrectly transposed and that, combined with my rookie macro-debugging-skills, meant a lot of thrashing about, wild tangents and hair pulling. I can't wait to get better at this stuff.

--
Mike

Marc Fawzi

unread,
Mar 24, 2015, 10:10:31 AM3/24/15
to clojur...@googlegroups.com
looks like magic to a beginner, trying to parse 

` is quote while ~ is unquote but what is ' compared to ` 

also, does :tag in meta have a special meaning reserved for type hints?

is there a good reference that explains how macros work in clojure? like why we need to quote and unquote (assuming that's not particular to macros but macros is only place i've seen it used)? 







Jamie Orchard-Hays

unread,
Mar 24, 2015, 10:16:35 AM3/24/15
to clojur...@googlegroups.com

Alex Eberts

unread,
Mar 24, 2015, 10:28:42 AM3/24/15
to clojur...@googlegroups.com
There's also this introduction to Clojure macros presentation by Gary Fredericks from Clojure West 2013:

http://www.infoq.com/presentations/macros-clojure-west-2013

HTH,
Alex

AndyR

unread,
Mar 24, 2015, 2:57:42 PM3/24/15
to clojur...@googlegroups.com
On Tuesday, March 24, 2015 at 7:21:32 AM UTC-4, Thomas Heller wrote:
> >
> > Question: why exactly is this type hint needed when js/goog.DEBUG is used in an "if"? As far as I can see, js/goog.DEBUG is a boolean already.
> >
>
> Short answer: We don't evaluate or parse any Javascript when compiling ClojureScript. That means we don't know about goog.DEBUG and what type it may be. CLJS is parsed and analyzed so we know a whole lot about it without actually running anything.

I was curious and used the cljs compiler from [1] and compiled the code of the type annotated vs the non-type annotated. Using :advanced to compile this:


(if js/goog.DEBUG (println "(if goog.DEBUG...")) ;; no type hint
(if-not js/goog.DEBUG (println "(if-not goog.DEBUG...")) ;; no type hint
(when-debug (println "when-debug")) ;; when-debug from David

inspect the output you can see this output:

[... omitted ...]
$cljs$core$truth_$$(!1) && console.log("(if goog.DEBUG...");
!$cljs$core$truth_$$(!1) && console.log("(if-not goog.DEBUG...");

where the (when-debug ..) call is missing (as it should be). However the other two check cljs.core.truth_ for truthiness. Now the interesting part: Pasting the following code:

function $cljs$core$truth_$$($x$$54$$) {
return null != $x$$54$$ && !1 !== $x$$54$$;
}
function $cljs$core$not$$($x$$58$$) {
return $cljs$core$truth_$$($x$$58$$) ? !1 : !0;
}
$cljs$core$truth_$$(!1) && console.log("(if goog.DEBUG...");
!$cljs$core$truth_$$(!1) && console.log("(if-not goog.DEBUG...");
// Sometime also produces:
$cljs$core$not$$(!1) && console.log("(if-not goog.DEBUG...");

into:
https://closure-compiler.appspot.com/home
with advanced compilation will actually get rid of the truth_ call and elide it. HOWEVER, pasting the _entire_ javascript output produced by the cljs compiler into the same window (appspot) and the call remains in place. So does that mean there is potential to improve the google closure compiler? Or does it mean that there is other produced code by cljs that prevents this inlining to happen?


[1] http://swannodette.github.io/2015/03/16/optimizing-clojurescript-function-invocation/
Reply all
Reply to author
Forward
0 new messages