Becaue of the @, both are macros, right? The first contains the (), which makes it closer to a function; the second contains even no (), so looks very casual.
What is similarity and difference between functions and macros?
Functions and macros serve to build abstractions on totally different levels. At the highest level a function captures some behavior of the program, while a macro captures some behavior of the code of the program. That is if your program has to do something repeatedly you use a function. If your code looks repetitive, then you can use a macro to abstract that repetition away. I recommend reading a bit through the manual page I linked above to learn more about metaprogramming and macros in Julia.
A general word of warning though: Never use a macro, if an ordinary function can achieve similar results.
Functions receive values as parameters, are evaluated at runtime, and return a value to the surrounding expression.
Macros receive source code (abstract-syntax-tree expressions) as arguments, are evaluated at compile time, and return source code to the compiler, which will then compile it.
For example, the macro @show a knows the name of the variable it received, and therefore can output a = 5, whereas the function show(a) knowns only the value of variable a, not its name, and therefore can only output 5.
Can I add on to this and ask why so many packages (ex. Query.jl, Pipe.jl, DataFramesMeta.jl) use nearly exclusively macros when it appears that functions (DataFrames.jl) would work fine?
Everything I have seen so far suggests that where functions suffice, macros should be avoided, but in practice that seems impossible for data-related tasks.
If this should be it’s own topic, I’m happy to do that.
Systems for querying tabular data use macros because they want to make it possible to write code that looks like normal Julia expressions, but which operates on variables whose names are actually columns in a table. So something like @select(df, :x2 = 2 * :x, :y) let you refer to x and y like normal variables, even though their binding comes from df, not from the scope in which the macro is called.
There’s nothing inherently wrong with using macros. Most macros just provide “syntax sugar” for things that you could do with functions. So, they’re an optional convenience to make your code more readable or to reduce your typing.
However, some new users jump to implementing their own macros when all they really need is to implement a function. If you are an end user writing scripts or application code, then you probably don’t need to implement your own macros. But it is pretty common for packages to implement macros for their users to use.
The thing is that macros and function are meant to solve quite different problems (abstract behavior of program vs. abstract structure in code). A macro can be abused to act as function though and that is why people say “never write a macro if a function can to the job”. That does not say “never write a macro” though as there are many use-cases that a function cannot do (because it lives on a totally different abstraction level).
Since macros abstract patterns in the code, most commonly macros are used to provide syntactic sugar (as pointed out by @CameronBieganek above). To make this explicit:
In DataFrames.jl you write something like
subset(df, :A => a -> a .< 10, :C => c -> isodd.(c))
But typing all these arrows get old quickly and I’d argue it adds quite a bit of visual noise when reading. So with DataFramesMeta.jl you can write:
@subset(df, :A .< 10, isodd.(:C))
@rsubset(df, :A < 10, isodd(:C)) # equivalent perhaps even more readable
which contains the same information just with much less visual noise. The important thing here is that the macros don’t know anything about the dataframe and just perform a transformation on the code! So for me macros are also about efficiency: I just want to put the necessary information into the code as easily and compactly as possible without having to type too much or even duplicating information at different places.