Juleps icon indicating copy to clipboard operation
Juleps copied to clipboard

WIP: string formatting Julep

Open simonbyrne opened this issue 7 years ago • 11 comments

simonbyrne avatar Feb 05 '18 18:02 simonbyrne

Thanks to @andyferris for the idea.

simonbyrne avatar Feb 05 '18 18:02 simonbyrne

I was wondering - do we need a new stringformat function or can we just add keyword arguments to our string methods? E.g. https://github.com/JuliaLang/julia/pull/25804

andyferris avatar Feb 05 '18 23:02 andyferris

We could for keyword arguments, but not for positional (since string(a,b) is equivalent to string(a)*string(b)).

simonbyrne avatar Feb 05 '18 23:02 simonbyrne

Just to pull together my (now hidden) suggestions from above, what if we made "π is approximately $(pi, fracdigits=4)." lower to something like:

LazyString(("π is approximately ", StringFormat(pi, fracdigits=4), "."))

with the definitions:

# String-like object formed by lazily concatenating printed args
struct LazyString{T<:Tuple} <: AbstractString
    args::T
end
function write(io::IO, ls::LazyString)
    # write this loop more cleverly to unroll at compile-time
    for arg in ls.args; print(buf, arg); end 
end
String(ls::LazyString) = let buf=IOBuffer(); write(buf, ls); String(take!(buf)); end
# ... other string methods...

struct StringFormat{T,KW}
    x::T # the value to be printed
    kws::KW # NamedTuple of keyword arguments
end
write(io::IO, f::StringFormat) = print(io, f.x) # fallback method

Then you could write specialized StringFormat methods as desired to handle various keyword options for specific types T.

Using a LazyString object would allow common things like print(io, "foo $bar") to be fast, allocating minimal temporary storage and making no string copies. On the other hand, other operations on a LazyString object would potentially be slow, especially if it is stored and re-used … in such cases, you should convert to a String. The construction of a LazyString might also be surprising to new users.

(This would be a breaking change since "foo $bar" would also no longer produce String.)

stevengj avatar Feb 06 '18 02:02 stevengj

I can see why we'd want LazyString in principle, but to have a = "foo $bar" not produce a string would be pretty surprising for casual use. That kind of seems like a bad user experience to me and a bit overly complex without a really compelling benchmark. On the whole eager formatting seems like a better default to me. It seems reasonable that the relatively small amount of performance critical string printing code can pay the syntax burden of using Simon's @print macro or something similar.

c42f avatar Feb 06 '18 06:02 c42f

I agree that from a usability perspective, strings should be the default result. Another option would be to make the lazy version available via a string macro?

simonbyrne avatar Feb 06 '18 19:02 simonbyrne

A negative thing about having the formatting inline is that it makes it harder to read the string:

"π is approximately $(e, fracdigits=6, someother=2), while e is approximately $(e, fracdigits=6, someother=2) ."

vs

"π is approximately ${pi}, while e is approximately ${e}.".format(
    pi = fmt(pi, fracdigits = 6, someother = 6,
     e = fmt(e,  fracdigits = 6, someother = 6)

Separating content and formatting is usually also a good idea.

KristofferC avatar Feb 13 '18 13:02 KristofferC

@KristofferC I haven't seen any other language do that sort of thing, but we could do that via let blocks

let  pi = fmt(pi, fracdigits = 6, someother = 6), e = fmt(e,  fracdigits = 6, someother = 6)
    "π is approximately $pi, while e is approximately $e."
end

simonbyrne avatar Feb 20 '18 19:02 simonbyrne

Note that all of this could potentially be implemented in a package if you are willing to use a string macro, e.g. f"...".

stevengj avatar Jul 11 '18 20:07 stevengj

You could just do "π is approximately $(fracdigits(π, 4)).", where fracdigits can return a stringformat object. Seems like this is more easily extensible in that I can define my own rounding routine, for example.

MikeInnes avatar Jul 23 '18 10:07 MikeInnes

@MikeInnes, the problem with that is that it forces the construction of a temporary string, which slows down I/O.

stevengj avatar Jul 23 '18 11:07 stevengj