To expand on this further, there's also a thing known as "vau" which are basically just functions that don't evaluate their arguments. If Arc had vaus, you might write "let" like this:
(vau let (x y . body) env
(eval `((fn (,x) ,@body) ,y) env))
Notice that this is exactly like the macro version, except that it has an extra argument "env" and it calls eval explicitly. Every macro can be easily rewritten into a vau, and in fact you can even implement "mac" using vau:
(vau mac (name args . body) env
(w/uniq e
(eval `(vau ,name ,args ,e
(eval (do ,@body) ,e))
env)))
What's the difference between vau and mac, then? They both receive their arguments unevaluated, but:
1) Vaus also receive an extra environment argument.
2) Vaus return a value, just like functions, which means you need to call eval explicitly. Macros, however, return a list which is then evaluated automatically.
3) Macros (conceptually) run at compile-time, but vaus (conceptually) run at run-time, just like functions.
Vaus are a much cleaner and more consistent system, especially since it's trivial to define "fn" in terms of vau, but you can't do that with macros. But macros have the benefit that they can be run at compile-time, so in Arc they would be faster than vaus.
It's surprising that macros destroy functions rather than generate them.
Thanks for vau. It seems it's defun (and def) that are redundant. Compile-time can be modeled as run-time where t = 0, which means one could have macros at run-time. Some such macros could choose to not evaluate their results.
I don't understand what you mean by macros "destroying" functions. They are completely different concepts. They behave differently and they are used for very different purposes: they are orthogonal.
---
It is possible to design a Lisp that doesn't have a function/macro distinction. Such a Lisp would just use vau for everything. An example is the language Kernel:
So, to answer your original question: no, it is not necessary to have both mac and def, but that's how most Lisps (including Arc) do it. I think the primary reason is historical, since I think the speed argument is actually bogus for most programs.
Well, I'm not sure I'd say that... what vau does is remove the need for mac, but def is still there, to make it more convenient to define functions.
That does mean that functions can be implemented in user-land, which is very nice, but it doesn't really change functions very much.
---
Also, I prefer to not think of vau as being "the ultimate macro", I prefer to see it as a construct which is very different from macros, but happens to serve the same purpose in most cases, which is why vau can replace macros in most (but not all!) cases.
Then again, I also prefer to see functions as being separate from vaus, even if they're implemented using vau... So take what I say with a grain of salt.
It took me a while to realize that I shouldn't think of vau as a macro-like thing. Macros and functions are different kinds of evaluation policies; vau just lets you construct arbitrary evaluation policies. It's like discovering the quarks that all fundamental particles are made up of; quarks don't preferentially constitute an electron more than a neutron.
The challenge now is to build a runtime where vau can have performance comparable to existing lisp systems.