4 ms·
I found that a rather good introduction to code as data, but I am not sure whether I am supposed to have been hit by the enlightenment he describes… :-)
by secure 14y ago
I found that a rather good introduction to code as data, but I am not sure whether I am supposed to have been hit by the enlightenment he describes… :-)
- S4M 14y agoYes, for me the first time I read about the lisp syntax I was thinking: "oh cool, it makes (+ 2 2) exactly equivalent to the syntaxic tree + / \ 2 2 " But I don't find it particularly enlightening and I still don't see what cool stuff you can do with macros that you can't do elsewhere.
- ataggart 14y agoSyntactic abstraction usually requires a language change. For example, Python's "with" statement. In languages with macros, you don't need to wait for anyone to change the language because the entirety of the language is constructed from a few special operators, and you have the ability to continue constructing. Like Python, Clojure has a "with-open" macro. If Rich hadn't already added it, you could build it yourself: ; Very simplified version (defmacro with-open [bindings & body] `(let ~bindings (try ~@body (finally (.close ~(first bindings)))))) Thus this: (with-open [in (reader "/usr/share/dict/words")] (count (line-seq in))) Expands into this: (clojure.core/let [in (reader "/usr/share/dict/words")] (try (count (line-seq in)) (finally (.close in))))
- Peaker 14y agoIn Haskell, there's the "bracket" function with generalizes the "with" concept. withFile fileName mode = bracket (openFile fileName mode) hClose Then: withFile "/usr/share/dict/words" ReadMode $ \h -> do contents <- hGetContents h count (lines contents) No need for macros for this. Just passing anonymous code blocks easily. Interestingly, the type of withFile, after its given the filename and filemode args is: (Handle -> IO a) -> IO a Which is the type of a CPS'd computation. CPS'd computations are called the Cont monad in Haskell, which is defined as: data Cont r a = Cont ((a -> r) -> r) So the above type of withFile can be written as: Cont (IO a) Handle And if we have, for example, multiple resources we're bracketing over, we can represent them as multiple Cont values. Then we can monadically compose them, which is equivalent to Python's "nested" function (Except we also have type safety).
- ataggart 14y agoMy comment intended to show how macros can allow one to create syntactic abstraction. That one can accomplish X without creating new syntactic abstraction, or that some language already has syntactic abstraction for X, is wholly irrelevant.
- Peaker 14y agoWell, I'm always looking for problems that are uniquely well solved by macros. Lisps pay a dear price to have macros. If their capabilities are covered otherwise (in ways that are not as costly), why have them?
- ced 14y agoMacros extend the power of the language way beyond its core primitives. For instance, I wrote a macro, TEMPORARY-ASSIGN. I use it like this: (TEMPORARY-ASSIGN ((traversing obj) true) ... do stuff ...) In Python, the equivalent code would be. old_trav = obj.traversing obj.traversing = True try: ... do stuff... finally: obj.traversing = old_trav There's no way to abstract out that pattern in Python. Every time you want to temporarily assign a field or variable, you're stuck writing the above code. Another example: (defun foo (x y) ...) is how you define a function in Common Lisp. I wrote a macro, DEFUN-CACHE (defun-cache foo (x y) ...) which is the cached version. In Python, you can do the same with decorators, but that's one more tacked-on feature. Lisp programmers have been writing defun-cache since 40 years. If you want to learn more, Paul Graham's On Lisp is the definitive book on the topic. You can download it for free http://www.paulgraham.com/onlisp.html http://www.paulgraham.com/onlisp.html, and it's very readable, even if you're not a Lisper.
- reinhardt 14y ago> There's no way to abstract out that pattern in Python. I'm sure there are macros that can't be abstracted out in Python but this isn't one of them: from contextlib import contextmanager @contextmanager def temp_assign(obj, attr, val): old_val = getattr(obj, attr) setattr(obj, attr, val) yield setattr(obj, attr, old_val) class X: pass x = X() x.a = 1 with temp_assign(x, "a", 2): print x.a # prints 2 print x.a # prints 1
- hythloday 14y agoI think it's an extraordinary strength of Python that I hadn't seen your code when writing mine but that other than two variable names they're identical. Leaving my comment up for demonstration of this.
- reinhardt 14y agoHa, awesome! I went for an exact transliteration although if I were to use this idea for real I would probably do the assignment explicitly in the body. I think this looks a bit more pythonic: @contextmanager def restoring(obj, attr): old_val = getattr(obj, attr) yield setattr(obj, attr, old_val) x.a = 1 with restoring(x, "a"): print x.a x.a = 2 print x.a print x.a
- orthecreedence 14y agoIf you've ever used C#, imaging being able to implement LINQ in pure C# (as in, it's not part of the language, but the language itself gives you the ability to add it with the exact same syntax as it exists as part of the language). That is what macros give you. You can extend the language's syntax to your liking. If you haven't used LINQ, then I'd have a hard time thinking of another example, since most languages have fairly uniform syntax.
- kenjackson 14y agoAlthough LINQ is basically pure syntatic sugar. I actually prefer the method syntax as it's consistent with the rest of the language. Extension methods are all that is needed for it. http://msdn.microsoft.com/en-us/library/bb397947.aspx http://msdn.microsoft.com/en-us/library/bb397947.aspx