Legal but comparable to a hidden side effect with all the pitfalls that can derive from this.
It's not a compiler issue, more a discipline issue. Having a helper macro like Stuart suggest is acceptable to me if it's called at top level, it's always done. There's less ambiguity. Using def/defn in a function is less desirable, the definition/redefinition occurs when you call the fn and it may occur at less desirable times or could be hard to spot. If you need a local fn, let-fn is exactly for this purpose. The scope is limited to the let-fn body. Luc P. > *`defprotocol` is a top-level form...* > > This is interesting, and it's something I've wondered about. As far as I > can tell, there's really no distinction between top-level forms and other > forms, for example this is legal and works: > > (defn define-my-functions [] > (defn test-1 [] > 1) > (defn test-2 [] > 2)) > => #=(var plugin.performance.project/define-my-functions) > test-1 > => Unbound: #'plugin.performance.project/test-1 > test-2 > => Unbound: #'plugin.performance.project/test-2 > (define-my-functions) > => #=(var plugin.performance.project/test-2) > test-1 > => plugin.performance.project$define_my_functions$test_1__3112@30e63c09 > test-2 > => plugin.performance.project$define_my_functions$test_2__3114@24ae6e0a > > Given this, are there any forms that are genuinely top-level from the > compiler's point of view? I'm assuming defining functions like this is > generally discouraged, but defining them inside of let-forms seems > relatively common: > > (let [a 1] > (defn get-a [] > a)) > => #=(var plugin.performance.project/get-a) > (get-a) > => 1 > > > > > On 6 June 2013 09:57, Stuart Sierra <[email protected]> > > > > > 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. > > > > > > > > -- > -- > 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. > > > -- Softaddicts<[email protected]> sent by ibisMail from my ipad! -- -- 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.
