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.


Reply via email to