String Interpolation Question

HI, very simple beginner’s question about string interpolation:

If a variable is a struct that has a Base.string method. Does string interpolation use that method?

This is most curious:

struct Expression_Symbol
    data::String
end

Base.string(x::Expression_Symbol) = string(x.data)

s = Expression_Symbol("Hello")

println("$s")
println(" $s")

Notice the second println has a space in front of $s… The results are drastically different:

Hello
 Expression_Symbol("Hello")

Simply adding a space in front, or after $s the Base.string method will not be called. Why is that?

Very confusing.

The method you want to overload is actually Base.show(io::IO, x::Expression_Symbol) as described here: Types · The Julia Language

In your case, that would be:

julia> struct Expression_Symbol
           data::String
       end

julia> Base.show(io::IO, x::Expression_Symbol) = print(io, x.data)

julia> s = Expression_Symbol("Hello")
Hello

julia> println("$s")
Hello

julia> println(" $s")
 Hello
4 Likes

I see. Thanks. I knew it’s something simple I have missed.

I’d say that the method that should be defined for this use case is not Base.show, but Base.print:

julia> struct Expression_Symbol
           data::String
       end

julia> Base.print(io::IO, x::Expression_Symbol) = print(io, x.data)

julia> s = Expression_Symbol("Hello")
Expression_Symbol("Hello")

julia> println("$s")
Hello

julia> println(" $s")
 Hello

There is a subtle difference between this and @rdeits’s example. In both cases the conversion of s into a string (by the function string, via interpolation, etc.) is the same, but look at the line where s is created: overloading Base.show does not only tell how a string created out of a Expression_Symbol will look, but also what string will be used when the object itself is presented.

The recommendation of overloading print when you mean to have a particular string method for your type can be found in the documentation of string itself:

string should usually not be defined directly. Instead, define a method print(io::IO, x::MyType) .

(Likewise, Base.show is the function that should be overloaded if what you want is to change the behavior of repr.)

You can see what’s going on in the original example with the Meta.@lower macro:

julia> Meta.@lower "$s"
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─ %1 = Base.string(s)
└──      return %1
))))

julia> Meta.@lower " $s"
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─ %1 = Base.string(" ", s)
└──      return %1
))))

The string function is called in both cases, but with one argument in the first case and two in the second. Since you have only overloaded the one-argument method of the string function, your method never gets called in the second case. As others have pointed out, one generally does not want to overload string at all, but rather show.

8 Likes