Thanks everyone for your answers. I must say I'm quite mystified as to why Stuart's version works.
I ended up defining a function that has the same signature as the protocol, and whose first argument wraps a function that contains the appropriate code. Vincent On Wednesday, 5 June 2013 23:57:16 UTC+2, Stuart Sierra wrote: > > Hi Vincent, > > `defprotocol` is a top-level form, not really meant to be mixed with > value-returning expressions like `fn`. Protocols are always global because > of how they compile into Java interfaces. > > Here's one way to make it work, by defining a symbol instead of returning > a function: > > (defmacro create-protocol [protocol symbol implementation] > (let [[protocol-name signature] protocol] > `(do > (defprotocol ~protocol-name ~signature) > (defn ~symbol [] (reify ~protocol-name ~implementation))))) > > (create-protocol [P (method [this])] > constructor > (method [this] (println "method"))) > > (method (constructor)) > > -S > > > On Wednesday, June 5, 2013 9:16:05 AM UTC-4, Vincent wrote: >> >> I’m trying to write a macro that defines a protocol and a function that, >> when called, returns an implementation of that protocol. >> >> I’ve reduced the code to the following example: >> (defmacro create-protocol [protocol implementation] >> (let [[protocol-name signature] protocol] >> `(do >> (defprotocol ~protocol-name ~signature) >> (fn [] (reify ~protocol-name ~implementation))))) >> >> (let [f (create-protocol [P (method [this])] >> (method [this] (println "method")))] >> (method (f))) >> >> >> The original code is more complicated, where the function will read a >> value from a file and, depending on that value, return the appropriate >> implementation of the protocol. >> >> When I run Clojure 1.5.1 on that code I get the following exception: >> Exception in thread "main" java.lang.NullPointerException, >> compiling:(protocol.clj:7:9) >> at clojure.lang.Compiler.analyzeSeq(Compiler.java:6567) >> at clojure.lang.Compiler.analyze(Compiler.java:6361) >> at clojure.lang.Compiler.analyzeSeq(Compiler.java:6548) >> at clojure.lang.Compiler.analyze(Compiler.java:6361) >> at clojure.lang.Compiler.analyze(Compiler.java:6322) >> at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:5708) >> at clojure.lang.Compiler$FnMethod.parse(Compiler.java:5139) >> at clojure.lang.Compiler$FnExpr.parse(Compiler.java:3751) >> at clojure.lang.Compiler.analyzeSeq(Compiler.java:6558) >> at clojure.lang.Compiler.analyze(Compiler.java:6361) >> at clojure.lang.Compiler.analyzeSeq(Compiler.java:6548) >> at clojure.lang.Compiler.analyze(Compiler.java:6361) >> at clojure.lang.Compiler.analyze(Compiler.java:6322) >> at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:5708) >> at clojure.lang.Compiler.analyzeSeq(Compiler.java:6560) >> at clojure.lang.Compiler.analyze(Compiler.java:6361) >> at clojure.lang.Compiler.analyzeSeq(Compiler.java:6548) >> at clojure.lang.Compiler.analyze(Compiler.java:6361) >> at clojure.lang.Compiler.access$100(Compiler.java:37) >> at clojure.lang.Compiler$LetExpr$Parser.parse(Compiler.java:5973) >> at clojure.lang.Compiler.analyzeSeq(Compiler.java:6560) >> at clojure.lang.Compiler.analyze(Compiler.java:6361) >> at clojure.lang.Compiler.analyze(Compiler.java:6322) >> at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:5708) >> at clojure.lang.Compiler$FnMethod.parse(Compiler.java:5139) >> at clojure.lang.Compiler$FnExpr.parse(Compiler.java:3751) >> at clojure.lang.Compiler.analyzeSeq(Compiler.java:6558) >> at clojure.lang.Compiler.analyze(Compiler.java:6361) >> at clojure.lang.Compiler.eval(Compiler.java:6616) >> at clojure.lang.Compiler.load(Compiler.java:7064) >> at clojure.lang.Compiler.loadFile(Compiler.java:7020) >> at clojure.main$load_script.invoke(main.clj:294) >> at clojure.main$script_opt.invoke(main.clj:356) >> at clojure.main$main.doInvoke(main.clj:440) >> at clojure.lang.RestFn.invoke(RestFn.java:408) >> at clojure.lang.Var.invoke(Var.java:415) >> at clojure.lang.AFn.applyToHelper(AFn.java:161) >> at clojure.lang.Var.applyTo(Var.java:532) >> at clojure.main.main(main.java:37) >> Caused by: java.lang.NullPointerException >> at clojure.lang.Compiler.resolveIn(Compiler.java:6840) >> at clojure.lang.Compiler.resolve(Compiler.java:6818) >> at clojure.lang.Compiler$NewInstanceExpr.build(Compiler.java:7427) >> at >> clojure.lang.Compiler$NewInstanceExpr$ReifyParser.parse(Compiler.java:7377) >> at clojure.lang.Compiler.analyzeSeq(Compiler.java:6560) >> ... 38 more >> >> From what I understood by tracing through the compiler it seems that the >> P var is created at macro expansion time but bound at execution time only. >> When expanding the ‘reify’ macro, P is still unbound, which yields a nil >> interface, which triggers the NPE. >> >> I could solve the problem by redefining the macro in the following way: >> (defmacro create-protocol [protocol implementation] >> (let [[protocol-name signature] protocol] >> (eval `(defprotocol ~protocol-name ~signature)) >> `(fn [] (reify ~protocol-name ~implementation)))) >> >> Using eval doesn’t feel right though. >> >> I guess I could modify my code to avoid using protocols, but it seemed to >> me to be the most natural way of achieving what I wanted. >> >> I was just wondering if anybody had any opinion or suggestion about that. >> Am I going off track? Is there a more idiomatic way of doing things that I >> missed? >> >> Thanks, >> Vincent >> > -- -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to [email protected] Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to [email protected] For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. For more options, visit https://groups.google.com/groups/opt_out.
