A string interpolation inconsistency

It seems that string interpolation works differently depending on whether the string consists entirely of a single interpolation:

struct Foo
    a::String
end

import Base.string
string(F::Foo) = F.a

F = Foo("bar")

"$F" # equals "bar"
"$F$F" # equals Foo("bar")Foo("bar")
"$(F)baz" # equals Foo("bar")baz

I have figured out from other threads that I can fix this issue by defining a show method for my types, but both the behavior and the solution strike me as unexpected.

Seems like a bug to me. Can you file an issue?

No. It’s not a bug. You are overwriting function you shouldn’t have which hides the implementation. These case are impossible to eliminate for cases where we provide “dispatch functions” (functions that provide user API but dispatches to other overridable functions for custimization).

2 Likes

Ah, I missed that the example was overloading string directly and thought it was overloading show which is what you should do here.

Thanks for the clarification. I can say that the reason for my confusion is that the docs include the sentence “Both concatenation and string interpolation call string to convert objects into string form.” Given that information, overloading string seems like the right thing to do (or at least, it seems like it should work).

I thought so too at first, but once you know how output in Julia works, it makes perfect sense to overwrite show. In short, there are different displays offering possibly different rendering of different MIME types with a fallback to plain-text stdout. That’s how for example a Julia Jupyter notebook can just inline display images after loading them, without explicit show of the resulting object.

I’m familiar with the multimedia display system, but I don’t think of it as necessarily intertwined with string interpolation. In any case, there remains the question of how string(F)*string(F) and "$F$F" can possibly give different results if the latter is simply calling string.

I investigated this just now, and here’s what I found: "$F" calls string directly, while "$F$F" calls print_to_string, which in turn calls print, which in turn calls show. This defaults to show_default if no custom show method is provided, and that’s where Foo(3) comes from.

So I have two conclusions, unless I am overlooking something: (1) it still strikes me as unexpected that "$F" is treated differently from "$F$F"—though maybe this is unavoidable, and (2) the statement in the documentation about string interpolation calling string seems to not be accurate.

It is not. You can just check code_lowered or Meta.@lower. They are just string(F) and string(F, F). Unless you actually want them to return exactly the same value (I assume you don’t), this is as similar as you can get to. string is the “dispatch” function I mentioned above and no there’s no way around this.

Or basically you are treating them differently by overloading string for only one of them, not julia…

There’s no code bug here. PR to improve and/or correct docs are certainly always welcome.

1 Like

Also the doc about "string interpolation calling string" is correct that’s exactly what it use, just not on each argument individually. Since it mentions it with concatenation, there should also be little ambiguity but I can see how it could be misleading. Again, PR to improve docs are always welcome.

Ah, that makes perfect sense. string is being called in its multi-argument form, and that does not call the single-argument method. The docs are fine, I just managed to misunderstand anyway. Thanks for explaining.

The help text for string says:

  Create a string from any values, except nothing, using the print function.

This is accurate. If you define print (which falls back to show), that method will be used by string interpolation. This is done so that for many-argument string interpolations (or calls to string) the output can be allocated just once: we create an IOBuffer, print each argument into it, then turn that into a String.

1 Like