What’s the thinking behind distinguishing macro names from other identifiers? Rust has done it too. I often use another, non-programming language, which also does it and it drives me nuts.
Old lisp hacker… which bits of lisp are macros and which bits aren’t, answer: don’t know, don’t care.
Dont know much about lisp, but dont you need to denote that the input should not be evaluated by marking it some way? Isnt this kind of the same, you need to tell the language in some way to not evaluate the argument, and just send the expression it represents.
I often write my macros as a function that takes an expression, and then a macro that calls that function. I guess i could call that function directly with expressions types instead of going through the macro, but that is maybe not as convenient to generate as it is in lisp so this became a cleaner syntax?
Just gussing here, and on the phone so not going to try to hunt this down. Probably someone who knows more that can answer.
Nope… the parser can figure out for itself whether or not an identifier is a macro… by definition a macro has to be defined before being used.
It’s a namespace thing. Why is the namespace for macros different from the namespace for everything else?
Julia macros live in a namespace where all names start with @. Nothing else in the language is distinguished in such a way. All the other names a user can define, types, variables, functions all have names in one namespace, the namespace matching the syntax for an identifier.
This is a choice someone made. If it were only in Julia, I’d just think it was a silly choice made by someone but it’s not. Rust also does it. So someone has [maybe] thought about it.
I’d like to know why because I don’t get it.
barfo.
Yes, they behave differently. Why do I need to care? Types behave differently from variables but I don’t have to start all type names with # or some other random symbol.
No I think it’s important because f(g(h(x))) evaluates h first, then g, then f. But @f(@g(@h(x))) evaluates @f first, then whatever remains of @g if @f didn’t remove it, and then whatever remains of @h. So I think it’s very important to make that highly visible with the @ prefix.
If you reason about some block of function code, you can go inside out and be sure the outer calls cannot influence the inner ones. With macros you have to understand exactly what the macro is doing to the syntax inside. If macros looked like functions you’d never be able to pick out inner parts and analyze them on their own.
One of the main critiques non-Lispers have of Lisp is that (a) macros are over-used and (b) their overuse is not obvious since you can’t immediately tell where there’s an invocation, which I think is why newer languages have tried to resolve (b).
Yes, they behave differently. Why do I need to care? Types behave differently from variables but I don’t have to start all type names with # or some other random symbol.
While this is true, note that the Julia community strongly prefers that types have uppercase-first names and variables do not, so they are also lexically distinguished in practice, although not by full syntactic force.
That just takes me back to my initial position of don’t know, don’t care.
People who worry about the overuse of macros in lisp should just stop caring. When everything is s-exp’s it makes no difference to the consumer of an API.
And now you’ve added polish notation for types as a preferred style to the reasons to avoid julia, bugger.
barfo.
What do you mean by “Polish notation for types”? Lisp has polish notation, hasn’t it? Julia uses infix notation for operators, but what is the connection to this?
As for @ in macros, yes, it’s just to make the code more readable, it might not be as important in Lisp.
an explicit indication that <type> is a type by the _t suffix or the Type preference previously noted for Julia.
So you might have “int i_i;” so you know i is an int wherever is it used etc etc.
Unnecessary cruft which makes some people feel comfortable but otherwise just makes the code less readable.
Maybe it was called Hungarian, I’d bad at remembering that kinda nonsense. Either way it’s guaranteed to be a politically incorrect slight on the way someone behaved upon a time. Very bad of me, apologises to the world, mea culpa.
And sorry but @ makes it much less readable for me.
barfo.
Well, for one thing, as @jules said, the order of evaluation is completely different. It transforms syntax, so that code that doesn’t make sense ordinarily suddenly does, see e.g. the Tullio.jl package:
@tullio (*) Z[j] := X[ind[k],j] * exp(-Y[k])
Take away the macro call, and this is just gibberish.
Also, you cannot assign a macro to a variable, like you can with basically all other variables, like functions, types and modules. E.g.:
julia> x = println; # assign the function prinln to the variable x
julia> x("Hello") # works
Hello
julia> t = Int # no problem
Int64
julia> b = Base # assign Base module to b
Base
julia> b.cos(pi) # can call functions in base
-1.0
julia> m = @time # oops
ERROR: LoadError: MethodError: no method matching var"@time"(::LineNumberNode, ::Module)
Edit: Hey. I can actually do
m = var"@btime"
But can’t call it. Oh well. But they just seem really different to me, and if they weren’t visually distinguished, I would probably be confused much of the time.
They are different.
The kinds of thing a macro can do vs a function are different things.
For example if i have a thing
foo(a()+c)
I know that a()+c will run.
And the result will depend only on the (typed) values of a() and c (and the definition of +)
If i did
b = c
d() = a()
foo(d() + b)
then i can be confident that i would get the same result.
In contrast, none of these things hold for the macro.
The macro could very well never run any of these functions but instead run totally different functions (@fastmath is a macro that does exactly this).
Or it might defer the functions til later (ChainRulesCore.@thunk) is a macro that does this.
Or it might print the text of there names (@show does something like that, printing then running).
The behavour of
b = c
d() = a()
@foo(d() + b)
could be totally different to @foo(a() + c)
(Distributed.@everywhere would for example error if the aliases were not also defined on remote machine)
When trying to understand a program I know there is a list of things a function can do.
It is fairly short, it consumes its input values and it returns an output value, and maybe it has side-effects.
Macros consume the lexical content, generated a new statement which is then evaluated.
So it does everything a function can do, and also a whole bunch of other things.
So when trying to understand unfamilar code i can quickly guess how something written with a function works.
With a macro i know that i am going to have to look at docs or source to understand.
Well, the win part is that it tells you that it is a macro, and if you wonder why strange stuff is happening, the @ sign makes it clear. I mean, do you agree that it does do that?
I do not know the original reason, if you want the original reason you should mark the founders which are active in this forum or open this as an issue in JuliaLang GitHub.
I was writing an answer but @oxinabox beat me to it. The main issue for me is that not forcibly distinguishing macros make much harder to reason about code, what is a legibility issue. If you have a function you have a much more restricted and easier to reason about mental model about how any expression inside the parenthesizes call will behave. This is very important for debugging. On the other hand, if you have a macro call many assumptions are called off, you need to be extra careful when debugging (and sometimes even writing). You may be forced to use a local variable to save an expression before passing it to a macro, or may be forced to pass the expression without evaluating it to a variable first. This is never the case in a function, because it never makes a difference for a function if an argument was in a local variable or it was just an expression inside the call parenthesis, you can immediately rule out this as a source of problems.