Macros and procs are two vastly different tools. A procedure is for writing
code that runs(at runtime or compile time). A macro is for writing code that
expands code at compile time. Macros enable introspection and automation of
writing code. Due to this you can do many magical things with macros, say you
have a serialization library and want to only serialized fields of an object
that are tagged, this can be done with a macro and a pragma. Macros should not
be used in place of procedures, they should be used since procedures cannot be.
The following is a small example that is hopefully easily understandable to
showcase what macros bring that procedures cannot do. Unpacking tuples/arrays
into proc calls for less redundant code.
import std/macros
macro `<-`(p: proc, args: tuple): untyped =
## Takes a proc and varargs, unpacking the varargs to the proc call if
it's a tuple or array
result = newCall(p) # Makes result `p()`
echo args.treeRepr # Shows the input tuple
for arg in args:
let
typ = arg.getType # Gets arg typ
isSym = typ.kind == nnkSym
echo typ.treeRepr # Shows the type we got
if not isSym and typ[0].eqIdent("tuple"): # This is a tuple so unpack
values from arg
for i, _ in typ[0..^2]:
result.add nnkBracketExpr.newTree(arg, newLit(i))
elif not isSym and typ[0].eqIdent("array"): # This is a tuple so unpack
values from arg
for i in typ[1][1].intVal .. typ[1][2].intVal:
result.add nnkBracketExpr.newTree(arg, newLit(i))
else: # Otherwise we just dumbly add the value
result.add arg
proc doThing(a, b: int) = echo a, " ", b
proc doOtherThing(a, b: int, c, d: float) =
doThing <- (a, b)
echo c, " ", d
doThing <- (10, 20)
doThing <- ([10, 20],)
doThing <- ([10], (20,))
doOtherThing <- ([10, 20], (30d, 40d))
doOtherThing <- (10, 20, 30d, 40d)
Run