Why distinguish macro names with @?

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.

What’s the motivation?

barfo

1 Like

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.

1 Like

Julia’s guiding design principles basically include:

  • ease-of-use
  • interactivity
  • performance
  • numeric precision

I’d guess this one is (1) - to make it easy to tell the difference between functions and macros.

3 Likes

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.

barfo.

1 Like

Why does anyone care?

Because macros behave differently from functions, and it’s nice to differentiate them, that’s pretty much it.

6 Likes

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.

It’s polish notation but just for macros.
barfo

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.

27 Likes

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.

19 Likes

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.

1 Like

I mean

<type>_t

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.

I see. “Polish notation” is a similar name with a different meaning, that is also specifically a Lisp thing, so you can see how it caused confusion.

Hungarian notation is a style which is not encouraged in Julia, in fact it is discouraged. Not sure how you got this idea.

Eh… Not sure what you are talking about here.

Sorry to hear that. I certainly find that it helps, since macros are so different in behaviour from other parts of the language.

I guess that’s my problem here… why does anyone think macros are different?

They just happen at a different time. As a consumer of the API they define, that doesn’t make them different.
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.

5 Likes

why does anyone think macros are different?

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.

27 Likes

yeah well there is the @f … versus @f(…) syntax thing.

I was wondering when someone would bring that up.

I was kinda ignoring it because it seems like an unnecessary complication to the language to me.

I don’t see what having it wins.
barfo.

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?

The negative side is, I guess, aesthetics?

3 Likes

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.

3 Likes

The negative side is code which looks like modem noise… [if anyone remembers modem noise]

The @macro … syntax would win more without the @ because then you could a set of macros which processed a basically arbitrary trailing syntax.

And yes then you would have RTFM.
barfo.