3 ms·
I think the examples I listed (2 of them anyway) actually would be considered hygienic by your standards. syntax-quote as implemented by clojure makes three gu
by emidln 10y ago
I think the examples I listed (2 of them anyway) actually would be considered hygienic by your standards.
syntax-quote as implemented by clojure makes three guarantees:
(a) symbols in the body that are postfixed by `#` will be uniformly replaced by a unique gensym in the expansion
(b) unquoted things are left alone
(c) any other symbol that is not the result of unquote or auto-gensyming, is fully namespace-qualified
In so far as is possible with a mutable function namespace (and clojure has this, you can alter the contents of vars and add/remove vars from namespaces), the syntax-quote in Clojure provides the environment within the expansion occurs as well as guaranteeing no name clashes (unless you explicitly opt into them, e.g. unquoting a quoted symbol).
If you wanted to do something similar to what the article mentions for common lisp, you end up at:
(intern (ns-name 'clojure.core)
'symbol-you-want-to-replace
(fn [x] alt-definition-here))
I guess someone technically could do that, but if they do, I would rather my language err towards giving me the power to make bad decisions than hamstring my ability to do useful things.
In summary, as long as you have lexical scoping (which FemtoLisp claims to) or namespaces of any sort, you can solve all but the most contrived problems of something like syntax-rules without the additional complexity and restrictions. Again, this can just be added to the language by the user, if they really want it.
This comes back to a larger question of, if everything about a language is interesting except its macro system, wouldn't you be well-served to just bolt on the macro system that you desire? (Not saying that FemtoLisp is for you.) Seems that a hygienic vs non-hygienic macro system is fairly trivial if you care enough about the other features.
- deleted 10y ago[deleted]
- qwertyuiop924 10y agoSyntax-quote still isn't actually hygenic by my standards, assuming you can do this (you can test). I don't have a computer in front of me, and I don't know clojure super well (like some of it, don't like some of it, HATE Java and the JVM): (fn a_func [x] (print x)) (defmacro a_macro [y] `(a_func ~y)) (let ((a_func (fn [x] (print "oh noes!")))) (a_macro "this is a string")) ;;;if I'm right, should print "oh noes!" If this does work properly, and no variant of it works, than congrats clojure, you have hygene, or close enough. >I guess someone technically could do that, but if they do, I would rather my language err towards giving me the power to make bad decisions than hamstring my ability to do useful things. Which is why I prefer low-level hygenic macros, like er/ir/sc-macro-transformers: I CAN break hygene whenever I want to, but I have hygene when I want it, and lack of hygene when I don't. Despite what Doug Hoyte may say, I don't have my hands behind my back. >In summary, as long as you have lexical scoping (which FemtoLisp claims to) or namespaces of any sort, you can solve all but the most contrived problems of something like syntax-rules without the additional complexity and restrictions. What additional complexity? With ir-macro-transformer (not mentioned in the article), my macros are as clean as syntax-quote, and it's not that hard to learn. Also, what additional restrictions? I don't see any. >This comes back to a larger question of, if everything about a language is interesting except its macro system, wouldn't you be well-served to just bolt on the macro system that you desire? (Not saying that FemtoLisp is for you.) Seems that a hygenic vs non-hygenic macro system is fairly trivial if you care enough about the other features. Well, femtolisp IS pretty cool, and if you hack together enough code, you can make a system that's almost hygenic, but I'm not sure if you can actually add hygene without the compiler's co-operation. Although gensym may be close enough as far as co-operation goes... I would need to look into this further.
- emidln 10y ago(defn a_func [x] (print x)) (defmacro a_macro [y] `(a_func ~y)) At this point, let won't allow you to rebind a_func (it changes the lexical scope, and the syntax-quote is going to refer specifically to ns/a_func). binding will also not allow it in modern versions of clojure, unless you declare the a_func var as ^:dynamic (e.g. ``` (defn ^:dynamic a_func [x] (print x)) ``` (as an aside, the compiler will also warn you for making something dynamic that doesn't have earmuffs, which is clojure convention)). (ns user (:use clojure.test)) (def a 5) (defmacro inc-by-a [x] `(+ a ~x)) (is (= (inc-by-a 0) 5)) (is (= (let [a 0] (inc-by-a 0)) 5)) ;; the is-macro implementation of thrown? doesn't catch ;; IllegalStateException... (defmacro actually-throws? [exception & body] `(try (do (do ~@body) false) (catch ~exception t# true) (catch Throwable t# false))) (is (actually-throws? java.lang.IllegalStateException (binding [a 0] (inc-by-a 0)))) ;; if we opt into this madness, all bets are off (def ^:dynamic a 5) (is (= (binding [a 0] (inc-by-a 0)) 0))
- qwertyuiop924 10y ago...so you're tying your hands by referencing any function from a macro. I thusly conclude that Clojure's macro system, while hygenic, is inferior to most of the scheme macro systems, which would do The Right Thing in this case, without you having to append every function with a hash. Except for er, of course, where rename is explicit.
- groovy2shoes 10y agoYour intuition that Clojure's macro system is unhygienic are correct. Hygiene in this context was coined by Henk Barendregt in the context of the lambda calculus: > For reasons of hygiene, it will always be assumed that the bound variables that occur in a certain expression are different from the free ones. [0] This may seem nonsensical at first, but consider two different functions, f(x) and g(x): although both f and g take an x, they are in fact different variables. So, if some expression has x free, and another has x bound, and one is substituted into the other, you must remember to differentiate between the two homonyms. One way to do this is to consistently rename one or both variables so that they no longer appear to be the same, but it's important not to get too hung up on the concept of renaming per se, because the only requirement is that they be regarded as different (Andre van Tonder's delightful implementation of syntax-case uses a painting algorithm rather than a renaming one, but the net effect is the same). In the case of syntactic transformers (macros), we're not concerned about the variables of the transformer itself, but rather the variables of the transformed code. Consider that full macro expansion occurs as a sequence of single expansions (transcription steps) until no further expansion can be done. Now, a macro is hygienic if all identifiers introduced during any given transcription step are unique to that transcription step (regardless of name). The introduced identifiers are analogized with bound variables while all other identifiers are analogized with free variables, just like how a lambda abstraction introduces its parameter variable. Note that even unhygienic macro systems are capable of expressing at least some hygienic macros: macros that do not introduce any identifiers are trivially hygienic. I contend that the explicit-renaming macro system is not actually hygienic, even though it allows you to write any hygienic macro. The reason being that, in essence, hygienic macro expansion is precisely the notion of lexical scoping applied to syntactic transformation; and just as we don't need to manually specify the scope of each variable in our basis language, we should not need to do so in our meta language. For the same reason, Clojure's requirement to gensym identifiers -- while it offers convenient shorthand for the operation, it is still explicit (and it solves only one of the two problems of unhygienic expansion, namely variable capture, while providing no protection from shadowing). In that vein, syntax-rules, syntax-case, and implicit-renaming macros all fit the bill. Just as dynamically-scoped variables can come in handy sometimes even in a lexically-scoped language (many Lisps have them, variously called "fluid variables", "special variables", or "parameters"), unhygienic injection of identifiers can come in handy sometimes (the classic example being anaphoric macros). The traditional way to do this is to simply circumvent, or "break", hygiene for a particular identifier, but Eli Barzilay and crew have also come up with the notion of "syntactic parameters" analogous to dynamically-scoped variables. [0]: Henk Barendregt, "Introduction to the Lambda Calculus".