I had a terrible time trying to write a convenience macro for reading from a core.async channel.
The idea was to make it easier to do the common "dispatch on first part of a message" pattern, as in:
(let [[msg & args] (<! my-channel)]
(case msg
:msg1 (let [[a b] args] ...)
:msg2 (let [[x] args] ...)))
After a long struggle I came to the conclusion that there is some problem with using macros inside a go block, or at least using <! inside a macro. Is that right? Is this documented anywhere?
Thanks
Tom
(defn <!"takes a val from port. Must be called inside a (go ...) block. Willreturn nil if closed. Will park if nothing is available.Returns true unless port is already closed"[port](assert nil "<! used not in (go ...) block"))
Tom
--
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.
Indeed, clojure.walk contains macroexpand-all specifically for this kind of situation, or that's what I had understood.
Thanks for your reply. That's pretty much what I'd come to understand. I'm a bit puzzled why this restriction exists though. Of course function calls can't be resolved at macro expansion time, so it makes sense that >! & <! inside functions can't be found. But I don't understand why the go macro cannot first expand all nested macros and then apply its transformations to the result.
Indeed, clojure.walk contains macroexpand-all specifically for this kind of situation, or that's what I had understood.
;; ======
(require '[clojure.core.async :as async])
(defmacro dispatch-on [ch & cases]
(let [argssym (gensym "args__")
keysym (gensym "key__")
ressym (gensym "res__")
default-case (when (odd? (count cases)) (last cases))
default-case (when default-case
(let [[[res] & body] default-case]
`(let [~res ~ressym] ~@body)))
cases (if default-case (butlast cases) cases)
case-body (apply concat (for [[key [args & body]] (partition 2 cases)]
[key `(let [~args ~argssym] ~@body)]))
case-body (if default-case
(conj (vec case-body) default-case)
case-body)]
`(let [ch# ~ch
~ressym (async/<! ch#)]
(if (and (vector? ~ressym) (seq ~ressym))
(let [~keysym (first ~ressym)
~argssym (next ~ressym)]
(case ~keysym ~@case-body))
~default-case))))
(def ch (async/chan))
(async/go
(loop []
(dispatch-on ch
:add ([a b] (println a "plus" b "is" (+ a b)) (recur))
:sqrt ([x] (println "The square root of" x "is" (Math/sqrt x)) (recur))
;default
([x] (when-not (nil? x)
(println "unknown operation:" x)
(recur))))))
(async/put! ch [:add 40 2])
(async/put! ch [:sqrt 1764])
(async/put! ch [:foo :bar])
(async/put! ch :foobar)
(async/close! ch)
;; =====
This prints:
40 plus 2 is 42
The square root of 1764 is 42.0
unknown operation: [:foo :bar]
unknown operation: :foobar
While it's true that the go-macro can't peek beyond a function's boundaries, it actually *does* perform macroexpansion before applying its transformations.
Cheers,
Kevin
Yes that's exactly what I was after, although that is Clojure, right? I've tried it in ClojureScript (not much to change - just requiring the right namespaces / macros) and it doesn't work. I get:
No implementation of method: :emit-instruction of protocol: #'cljs.core.async.impl.ioc-macros/IEmittableInstruction found for class: cljs.core.async.impl.ioc_macros.Jmp
I've tried playing around moving the <! and the (recur) outside of the on-dispatch macro, and, while I can avoid the above error and get it to compile, the behaviour of the test code makes no sense at all. It seems like mixing the go macro with your own macros is very fragile in cljs.
Tom
(defmacro dispatch-on [ch & cases]
(let [argssym (gensym "args__")
keysym (gensym "key__")
ressym (gensym "res__")
default-case (when (odd? (count cases)) (last cases))
default-case (when default-case
(let [[[res] & body] default-case]
`(cljs.core/let [~res ~ressym] ~@body)))
cases (if default-case (butlast cases) cases)
case-body (apply concat (for [[key [args & body]] (partition 2 cases)]
[key `(cljs.core/let [~args ~argssym] ~@body)]))
case-body (if default-case
(conj (vec case-body) default-case)
case-body)]
`(cljs.core/let [ch# ~ch
~ressym (cljs.core.async/<! ch#)]
(if (cljs.core/and (cljs.core/vector? ~ressym) (cljs.core/seq ~ressym)) ;; <- still problematic
(cljs.core/let [~keysym (cljs.core/first ~ressym)
~argssym (cljs.core/next ~ressym)]
(cljs.core/condp = ~keysym ~@case-body))
~default-case))))
However, this still gives an error if you put :foobar onto the channel. This, too, appears to be a core.async bug: in the line highlighted above, *both* arguments to "and" get evaluated, regardless of whether the first argument is false or not.
(defmacro dispatch-on [ch & cases]
(let [argssym (gensym "args__")
keysym (gensym "key__")
ressym (gensym "res__")
default-case (when (odd? (count cases)) (last cases))
default-case (when default-case
(let [[[res] & body] default-case]
`(cljs.core/let [~res ~ressym] ~@body)))
cases (if default-case (butlast cases) cases)
case-body (apply concat (for [[key [args & body]] (partition 2 cases)]
[key `(cljs.core/let [~args ~argssym] ~@body)]))
case-body (if default-case
(conj (vec case-body) default-case)
case-body)]
`(cljs.core/let [ch# ~ch
~ressym (cljs.core.async/<! ch#)
isvec# (cljs.core/vector? ~ressym)]
(if (cljs.core/and isvec# (cljs.core/seq ~ressym))
I've had a look on the core.async jira and can only see one open issue that looks like it could possibly be related, with the .. macro
http://dev.clojure.org/jira/browse/ASYNC-49
I'm going to work on a minimal test case and file a new issue.
@Kyle - it's not a deep problem with Clojure extensibility but just a bug in the ClojureScript implementation.
Tom
http://dev.clojure.org/jira/browse/ASYNC-79
In trying to create a minimal test case I tried a trivial macro
(defmacro my-case [expr & cases] `(case ~expr ~@cases))
inside a go block, and that worked fine, so it's more subtle than just "case in a macro is broken".
I didn't try to reproduce the (and) bug spotted by Kevin M.
The thing that's still confusing to me, is why the go macro doesn't just macroexpand the body before it does it's thing.
I haven't tried it but I wouldn't be surprised if using ~'and would also solve the problem with (and ...) noted above.
It might even be a solution for http://dev.clojure.org/jira/browse/ASYNC-49