At that point you're emulating lisp without the elegance, and the first approach is only possible because functions are first class objects. If you were trying to rearrange an expression that had control flow or keywords in it (eg. Modify the behaviour of an if) then you would have to reify the "if" (change the original code so it had a class to represent the if not use the keyword). So you're kind of reinventing lisp by wrapping every part of your language as an object
(defun swap (x y) (y x))
Then you can call it like:
(swap (2 3)) => (3 2)
You could do `(defn swap [the-list] ((second the-list) (first the-list)))` (which would invoke the second item as a function on the first item). It comes down to is the list a literal list as a parameter to something or is the first item a function to be invoked.
(defn swap [x]
(list (second x) (first x) (last x)))
(swap (1 + 2))
=>Exception! 1 isn't a function.
Clojure tried to evaluate it's argument to swap, and the argument was (1 + 2), which is a function call, where the function is 1 and the arguments are + and 2.So we quoted it in the function call by putting ' in front of the list '(1 + 2):
(swap '(1 + 2))
=> (+ 1 2)
Here, we stll didn't get 3 as our output... We got (+ 1 2), which is a list. Because the function returned a list, it didn't return code! It might look like code, but it's not code! It's a list.So if I was to
(+ (swap '(1 + 2)) (swap '(3 + 4)))
=> Crashes! Can't convert alist to a number.
Because what it actually runs is (+ '(+ 1 2) '(+ 3 4))
Whereas with the macro (+ (swap (1 + 2)) (swap (3 + 4)))
=> 10
Works because the macro gets expanded BEFORE compile time, and our swap code gets replaced out with the code the macro generates! (swap (1 + 2))
actually compiles as: (+ 1 2)
SO at runtime, that will be 3.The main point of macros is when you use a regular function in Clojure you have applicative order evaluation. Macros do not, as macros are designed to transform and generate code.
A better example would have been the (when) macro.
In Clojure you have (cond) and (if) for conditional evaluations. If has the form
(if (cond)
(when-true)
(when-false))
e.g. (if (= 1 1)
(println "1 = 1")
(println "uh-oh the unviverse is broken!"))
=> "1 = 1"
Now what if we want to do more than 1 statement on the true path and we dont care about the false path? (if (= 1 1)
(println "1 = 1")
(println "also hello"))
This won't work as now "also hello" is the false path.. We can use (do) to specify multiple things to be done. (if (= 1 1)
(do
(println "1=1")
(println "Also hello")))
1=1
Also hello
But writing (do) is a bit of a pain, so an alternatie would be (when) (when (= 1 1)
(println "1=1")
(println "Also hello"))
Will print, when run, "1=1"
"Also hello"
COuld we implement this as a function, sure? BUt we would have to quote the function calls when we pass them so they aren't evaluated and then eval them if true. You could do it, but it would be messy.Essentially, we want to write
(when (= 1 1)
(println "1=1")
(println "Also hello"))
But want the code that gets compiled to be: (if (= 1 1)
(do
(println "1=1")
(println "Also hello")))
So we want to extend the language so we can write (when) and the compiler will write us an (if (do))This is incredibly easy in Clojure. We just need to create a list where the first item in the list is `if`, the second item is the conditional we pass to the macro, the 3rd item is a sublist, of which its first item is `do` followed by the list / functions to execute when our test evaluates to true!
(defmacro when [test & body]
(list 'if test (cons 'do body)))
This is just the source code for the actual clojure core when macro. But you can see how easy this is!If we run the macro expansion on when we can see the code that it generates:
(macroexpand '(when (= 1 1)
(println "1=1")
(println "Also hello")))
=> (if (= 1 1) (do (println "1=1") (println "Also hello")))
Another example would be the reader macro:Say we have the nested function calls:
(reduce + (filter even? (range 1 11)))
=> 30
After you have a lot of nesting this can get difficult to read, so you have a macro ->> which is the threading macro. This takes a series of functions and threads the result of each function as the input to the next. (->> (range 1 11)
(filter even?)
(reduce +))
The source code for this is almost as simple as (when) (defmacro ->>
[x & forms]
(loop [x x, forms forms]
(if forms
(let [form (first forms)
threaded (if (seq? form)
(with-meta `(~(first form) ~@(next form) ~x) (meta form))
(list form x))]
(recur threaded (next forms)))
x)))
The ` ~ and ~@ are just doing some quoting and quote splicing to determine when we want stuff evaluated.Basically, using macros you do things like control symbolic resolution time, extend the compiler to create a DSL spcific to your domain and reduce boiler plate code.
That's before you start getting into properly weird stuff like anaphoric macros.
I've read a few Lisp books and dozens of internet blog posts, so I know about macros and why people use them without getting the full understanding which comes with actually writing code.
Macro arguments are not evaluated, and so this works.
You could make it a function, and pass the arguments quoted, but it'd be more cumbersome.
cljs.user=> (defmacro swap [x] (list (second x) (first x) (last x)))
#'cljs.user/swap
cljs.user=> (swap (1 + 2))
Execution error (Error) at (<cljs repl>:1).
1.call is not a function