Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure, can macros do something that couldn't be done with a function

I'm learning Clojure macros, and wonder why we can't use just functions for metaprogramming.

As far as I know the difference between macro and function is that arguments of macro are not evaluated but passed as data structures and symbols as they are, whereas the return value is evaluated (in the place where macro is called). Macro works as a proxy between reader and evaluator, transforming the form in an arbitrary way before the evaluation takes place. Internally they may use all the language features, including functions, special forms, literals, recursion, other macros etc.

Functions are the opposite. Arguments are evaluated before the call, return value is not after return. But the mirroring nature of macros and functions makes me wonder, couldn't we as well use functions as macros by quoting their arguments (the form), transforming the form, evaluating it inside the function, finally returning it's value. Wouldn't this logically produce the same outcome? Of course this would be inconvenient, but theoretically, is there equivalent function for every possible macro?

Here is simple infix macro

(defmacro infix
  "translate infix notation to clojure form"
  [form]
  (list (second form) (first form) (last form)))

(infix (6 + 6)) ;-> 12

Here is same logic using a function

(defn infix-fn
  "infix using a function"
  [form]
  ((eval (second form)) (eval (first form)) (eval (last form))))

(infix-fn '(6 + 6)) ;-> 12

Now, is this perception generalizable to all situations, or are there some corner cases where macro couldn't be outdone? In the end, are macros just a syntactic sugar over a function call?

like image 275
Tuomas Toivonen Avatar asked May 15 '17 07:05

Tuomas Toivonen


People also ask

Why use of macro is recommended instead of function?

Speed versus size The main benefit of using macros is faster execution time. During preprocessing, a macro is expanded (replaced by its definition) inline each time it is used. A function definition occurs only once regardless of how many times it is called.

Can macros be used for functions?

In general, a macro language function processes one or more arguments and produces a result. You can use all macro functions in both macro definitions and open code. Macro functions include character functions, evaluation functions, and quoting functions.

What are Clojure macros?

Clojure has a programmatic macro system which allows the compiler to be extended by user code. Macros can be used to define syntactic constructs which would require primitives or built-in support in other languages. Many core constructs of Clojure are not, in fact, primitives, but are normal macros.


1 Answers

It would help if I read the question before answering it.

Your infix function doesn't work except with literals:

(let [m 3, n 22] (infix-fn '(m + n)))
CompilerException java.lang.RuntimeException: 
Unable to resolve symbol: m in this context ...

This is the consequence of what @jkinski noted: by the time eval acts, m is gone.


Can macros do what functions cannot?

Yes. But if you can do it with a function, you generally should.

Macros are good for

  • deferred evaluation;
  • capturing forms;
  • re-organizing syntax;

none of which a function can do.

Deferred Evaluation

Consider (from Programming Clojure by Halloway & Bedra)

(defmacro unless [test then]
  (list 'if (list 'not test) then)))

... a partial clone of if-not. Let's use it to define

(defn safe-div [num denom]
  (unless (zero? denom) (/ num denom)))

... which prevents division by zero, returning nil:

(safe-div 10 0)
=> nil

If we tried to define it as a function:

(defn unless [test then]
  (if (not test) then))

... then

(safe-div 10 0)
ArithmeticException Divide by zero ...

The potential result is evaluated as the then argument to unless, before the body of unless ignores it.

Capturing Forms and Re-organizing Syntax

Suppose Clojure had no case form. Here is a rough-and-ready substitute:

(defmacro my-case [expr & stuff]
  (let [thunk (fn [form] `(fn [] ~form))
        pairs (partition 2 stuff)
        default (if (-> stuff count odd?)
                  (-> stuff last thunk)
                  '(constantly nil))
        [ks vs] (apply map list pairs)
        the-map (zipmap ks (map thunk vs))]
    (list (list the-map expr default))))

This

  • picks apart the keys (ks) and corresponding expressions (vs),
  • wraps the latter as parameterless fn forms,
  • constructs a map from the former to the latter,
  • returns a form that calls the function returned by looking up the map.

The details are unimportant. The point is it can be done.

When Guido van Rossum proposed adding a case statement to Python, the committee turned him down. So Python has no case statement. If Rich didn't want a case statement, but I did, I can have one.


Just for fun, let's use macros to contrive a passable clone of the if form. This is no doubt a cliche in functional programming circles, but took me by surprise. I had thought of if as an irreducible primitive of lazy evaluation.

An easy way is to piggy-back on the the my-case macro:

(defmacro if-like
  ([test then] `(if-like ~test ~then nil))
  ([test then else]
   `(my-case ~test
     false ~else
     nil ~else
     ~then)))

This is prolix and slow, and it uses stack and loses recur, which gets buried in the closures. However ...

(defn fact [n]
  (if-like (pos? n)
    (* (fact (dec n)) n)
    1))

(map fact (range 10))
=> (1 1 2 6 24 120 720 5040 40320 362880)

... it works, more or less.


Please, dear reader, point out any errors in my code.

like image 93
Thumbnail Avatar answered Oct 22 '22 17:10

Thumbnail