Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Macro expand step for (s)printf #9243

Closed
wants to merge 1 commit into from
Closed

Macro expand step for (s)printf #9243

wants to merge 1 commit into from

Conversation

MikeInnes
Copy link
Member

This just makes @printf and @sprintf macroexpand their arguments. I don't think there should be any horrible consequences to changing the order of macro expansion, but we could always check that the expression is a macro call before doing this and/or only replace those that expand to strings.

The idea of this is to allow, for example

macro fmt()
  "%.2d"
end
@sprintf(@fmt, n)

Which is obviously useful when you want to use the same format string more than once.

@simonbyrne
Copy link
Contributor

Does this also allow the use of multiline (triple-quoted) strings?

@JeffBezanson
Copy link
Member

This seems like a very heavy-handed approach just to allow reusing format strings. Needing to define the format string as a macro is not great either.

I think a better approach would be to allow a symbol as the format string, and call eval on it. That would let you use a normal global constant.

Of course you can also do

@eval function foo()
    ...
    @sprintf($fmt, n)
    ...
end

right now. However that is a bit awkward, and it would be better to have syntax to force evaluation of macro arguments before expansion.

The larger point is, this should not need to be specific to printf, and defining constants using macros is not ideal.

@Keno
Copy link
Member

Keno commented Dec 3, 2014

Wouldn't this also allow

@sprintf(@eval fmt, n)

for some global constant fmt?

@StefanKarpinski
Copy link
Member

I think we just need a better overall approach to printf. The current macro is kind of awkward.

@simonster
Copy link
Member

Wouldn't calling eval on the format string evaluate it in Base, instead of wherever @printf is being used? That was the problem I had thinking about how to implement #5892.

@JeffBezanson
Copy link
Member

Yes, that's a problem. Of course I've always said not to call eval in macros anyway, so that idea can't really be taken seriously.

@StefanKarpinski might be right here. For example we could have fmt"%.2d" construct a formatting object that can be reused.

@StefanKarpinski
Copy link
Member

For what it's worth, I was planning on revisiting this after strings are redone as part of the I/O revamp but I felt it didn't make much sense to do it before that since performance is such a major consideration and all that is going to change a lot. But we can certainly think about and/or improve the api now.

@GunnarFarneback
Copy link
Contributor

I haven't actually tried staged functions at all yet, but somehow I get the feeling that they could have a role to play here.

@nalimilan
Copy link
Member

FWIW let me revive my previous proposal about using non-standard string literals and a function instead of a macro: #5747

@MikeInnes
Copy link
Member Author

@Keno @eval fmt expands to eval(:fmt) so @printf would reject it.

@simonster You can use eval(current_module(), :fmt) to get around the module issue easily enough. I once had a @preval(x) macro which did a similar thing to enable compile-time evaluation.

@JeffBezanson But there's nothing wrong with macros evaluating global constants, right? I mean, that must be how function definitions must work; the compiler works with the type itself, not the symbol you pass in.

FWIW there's a precedent for this; ccall works this way and I'm using the same trick with it. It's definitely ugly so I'm very open to other solutions if there are any.

You can reuse format strings by wrapping @sprintf in a function, so this isn't really that necessary, but it might still be good to have a solution to the general problem.

@MikeInnes
Copy link
Member Author

Another option would be to have @sprintf, and similar macros, able to interpret $ syntax. e.g.

fmt = "$.2d"
@sprintf($fmt, 5)

Conceptually this makes a certain amount of sense, and technically it's not hard to do; just have @sprintf walk the expression tree and evaluate any Expr(:$, ...)s. The downside is that it's probably not going to be clearer to beginners.

I can turn this PR into a proof-of-concept for this if there's interest.

@simonbyrne
Copy link
Contributor

If big changes are going to be made to printf, it would be nice if it was user-extendable to allow for new formats: e.g. a format with a thousands separator (this has come up on the list at least once).

@tonyhffong
Copy link

For the record, https://github.com/lindahua/Formatting.jl already has thousand separator, and runtime format arguments e.g.

using Formatting
fmt = "%'d"
@assert sprintf1( fmt, 1000 ) == "1,000"

@JeffBezanson
Copy link
Member

Using $ for various other purposes is a problem since it doesn't compose with surrounding uses of quote.

@ivarne
Copy link
Member

ivarne commented Dec 4, 2014

I really like the fmt"%.2d" approach, because reuses a pattern that we already have for regexes.

@MikeInnes
Copy link
Member Author

@JeffBezanson It's not impossible for them to work together, e.g.

quote
  @sprintf($(Expr(:$, :fmt)), 5)
end

would be the quoted version of the above call.

And this is not unprecedented either; I've already had to do this before to get various bits of quoting to play nicely with each other. It's ugly but at the same time it's very unlikely you'd want to do this, since if you're already quoting @sprintf you can splice the value of the format string in directly (as you showed above).

@StefanKarpinski
Copy link
Member

The custom fmt string approach is just syntactic sugar for calling a macro, so it doesn't really help this situation as far as I can tell.

@JeffBezanson
Copy link
Member

No, it would enable reusing format strings:

f = fmt"%.2d"
f(x)
f(y)
...

@ivarne
Copy link
Member

ivarne commented Dec 4, 2014

But much more importantly:

It will be semantically equal to a function call, so that we can have f = fmt("%d brown $animal jumps " * "over the fence") to compose the format strings runtime. ( Just as you can compose a regex runtime)

@nalimilan
Copy link
Member

Yeah, that's why I advocated that solution in the old issue. I really find this suits better in the other patterns found in Julia.

@MikeInnes MikeInnes closed this Dec 10, 2014
@MikeInnes MikeInnes deleted the omm/sprintf branch December 10, 2014 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants