You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.
Please tick all that apply:
This is not a breaking change to the F# language design
For Readers
If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.
Reacted by Loïc Denuzière, Vladimir Shchur, Evgeniy Andreev, Ch13, Vladislav Prekel, Phillip Carter, Igor Bagdamyan, Vlad Khapin, Alex Berezhnykh, Jimmy Byrd and 17 more
This is handy in code instrumentation scenarios (e.g., OpenTelemetry) where you want as little overhead as possible but still want to do a little string formatting for messages on constructs (e.g., a small structured Span Event on a TelemetrySpan).
Yeah, it's solving some real problems. But my goodness I find the adhoc nature of these C# solutions to these problems really problematic (e.g. the attributes on parameters).
As an aside, one thing I'm aware of is that there's a similarity between computed list expressions and computed string expressions (aka string interpolations)
computed list expressions ideally write imperatively to a list collector
computed string expressions ideally write imperatively to a string collector
It would kind of be nice if there was more symmetry to this, e.g. why can't we use for loops in string interpolations, e.g.
$"""abc {for x in xs do yield x; yield ";"} def"""
or implicit yield:
$"""abc {for x in xs do x; ";"} def"""
and conditionals
$"""abc {if today() then header} def"""
and pattern matching
$"""abc {match day with Monday -> header | _ -> footer } def"""
Anyway it totally makes sense to emit interpolated strings to a collector, and to sort out the conditional nature of the emit.
Reacted by Mårten Rånge, Jonas A, Charles Roddie, Adrián Montesinos González and cupofc0t
Could this become an interop pain point (or even a blocking issue) if people start making libraries with methods that make use of custom InterpolatedStringHandlers for some parameters but no longer expose plain String alternative overloads?
I would love to see further development of F# string interpolation for higher performance, greater flexibility (I've already seen a C# attempt at using a custom handler to invert it into a scanf() type of function) or even just some more optimisation for simple cases e.g.
$"""value: {stringValue}"""
Which doesn't need all the full string formatting but just some fast concatenation of two strings.
We could even imagine full-blown type-provider-like code generation that would use the string fragments and quotations of interpolated values at compile time to generate arbitrary code, not necessarily calls to specific methods on a builder like InterpolatedStringHandler does. For example parsing HTML at compile time to compile something like this:
html $"""<div id="test">Hello, {name}!</div>"""
into the equivalent of Fable/WebSharper/Bolero/etc function calls:
div [ attr.id "test"][ text "Hello, "; text name; text "!"]
In fact, I can say for sure that Bolero would be able to provide better performance using the former than it currently does with the latter. Of course this would be an XXL dev effort 😄
yes and no. Yes, insofar as there will be new methods in .NET 6 (not many) that take on in as a parameter, and an F# consumer would need to construct an explicit instant of DefaultInterpolatedStringHandler as a parameter. But the answer is a "no" insofar as no existing APIs are being changed to use this instead of string parameters.
There are ~1000 uses of DefaultInterpolatedStringHandler on github code search, and the areas I see commonly are:
logging
http frameworks
tracing/telemetry
Game engines?
generated API clients that want to be as low-impact as possible
The runtime usages are also very low-level on StreamWriter et al, so writing to all kinds of streams could become more performant with the single feature.
Reacted by Jimmy Byrd, Angel D. Munoz and Cameron Taggart
Could this become an interop pain point (or even a blocking issue)
Blazor kinda figured out mixing C# with html
html $"""
Hello, {name}!
"""
That's awesome, I can make a lot of html using this approach, each generated functionally presenting information of their domain, I put them all in the same page on my website, regenerate functionally and in parallel
Some benchmarks for
HW: let world = "world" in $"hello {world}" with current and some alternative implementations
and
TT: let two = "two" in $"{two} + {2} = {4}"
Overall StringBuilder is the fastest, but DefaultInterpolatedStringHandler allocates the least. There appear to be performance costs of DefaultInterpolatedStringHandler relative to StringBuilder, at least for the typical case where custom formats are not needed.
I think a move to StringBuilder would be a good first step and would get the majority of the benefit.
Any move to DefaultInterpolatedStringHandler would build on that.
DefaultInterplolatedStringHandler is also dotnet6 so there would need to be a fallback implementation with StringBuilder anyway for netstandard2.0, or alternatively DefaultInterplolatedStringHandler can be a replacement when FSharp no longer supports netstandard2.0.
If it could iteratively optimise the interpolation then constant strings could be interpolated in at compile time, reducing the number of elements to concatenate. Speaking of which, if at the end all that's required is to concatenate some constant strings and some dynamically produced strings (up to 4 I think?) there are a bunch of overloads to String.Concat() that should be faster than using StringBuilder and only allocate for the resulting string.
Agree that a StringBuilder version is a great improvement already, and thus a fine starting point. One thing your benchmarks don't make use of (because it doesn't matter for such short strings) is the fact that we can typically estimate the minimum capacity the builder needs to allocate, sometimes even the exact length, which will give us ever so slightly more performance.
Activity
cartermp commentedon Jan 4, 2022
This is handy in code instrumentation scenarios (e.g., OpenTelemetry) where you want as little overhead as possible but still want to do a little string formatting for messages on constructs (e.g., a small structured Span Event on a TelemetrySpan).
dsyme commentedon Jan 4, 2022
Yeah, it's solving some real problems. But my goodness I find the adhoc nature of these C# solutions to these problems really problematic (e.g. the attributes on parameters).
As an aside, one thing I'm aware of is that there's a similarity between computed list expressions and computed string expressions (aka string interpolations)
It would kind of be nice if there was more symmetry to this, e.g. why can't we use
forloops in string interpolations, e.g.$"""abc {for x in xs do yield x; yield ";"} def"""or implicit yield:
$"""abc {for x in xs do x; ";"} def"""and conditionals
$"""abc {if today() then header} def"""and pattern matching
$"""abc {match day with Monday -> header | _ -> footer } def"""Anyway it totally makes sense to emit interpolated strings to a collector, and to sort out the conditional nature of the emit.
realparadyne commentedon Jan 11, 2022
Could this become an interop pain point (or even a blocking issue) if people start making libraries with methods that make use of custom InterpolatedStringHandlers for some parameters but no longer expose plain String alternative overloads?
I would love to see further development of F# string interpolation for higher performance, greater flexibility (I've already seen a C# attempt at using a custom handler to invert it into a scanf() type of function) or even just some more optimisation for simple cases e.g.
$"""value: {stringValue}"""Which doesn't need all the full string formatting but just some fast concatenation of two strings.
Tarmil commentedon Jan 11, 2022
We could even imagine full-blown type-provider-like code generation that would use the string fragments and quotations of interpolated values at compile time to generate arbitrary code, not necessarily calls to specific methods on a builder like
InterpolatedStringHandlerdoes. For example parsing HTML at compile time to compile something like this:html $"""<div id="test">Hello, {name}!</div>"""into the equivalent of Fable/WebSharper/Bolero/etc function calls:
In fact, I can say for sure that Bolero would be able to provide better performance using the former than it currently does with the latter. Of course this would be an XXL dev effort 😄
cartermp commentedon Jan 11, 2022
@realparadyne
yes and no. Yes, insofar as there will be new methods in .NET 6 (not many) that take on in as a parameter, and an F# consumer would need to construct an explicit instant of
DefaultInterpolatedStringHandleras a parameter. But the answer is a "no" insofar as no existing APIs are being changed to use this instead of string parameters.baronfel commentedon Jul 11, 2023
There are ~1000 uses of DefaultInterpolatedStringHandler on github code search, and the areas I see commonly are:
The runtime usages are also very low-level on StreamWriter et al, so writing to all kinds of streams could become more performant with the single feature.
Xyncgas commentedon Oct 18, 2023
Blazor kinda figured out mixing C# with html
That's awesome, I can make a lot of html using this approach, each generated functionally presenting information of their domain, I put them all in the same page on my website, regenerate functionally and in parallel
vzarytovskii commentedon Nov 9, 2023
Put an approval on it, since there's a general consensus that addition makes sense. Needs an rfc though.
charlesroddie commentedon Jan 9, 2024
Some benchmarks for
HW:
let world = "world" in $"hello {world}"with current and some alternative implementationsand
TT:
let two = "two" in $"{two} + {2} = {4}"https://github.com/charlesroddie/BenchmarksFsharp/tree/InterpolatedStrings
Overall
StringBuilderis the fastest, butDefaultInterpolatedStringHandlerallocates the least. There appear to be performance costs ofDefaultInterpolatedStringHandlerrelative toStringBuilder, at least for the typical case where custom formats are not needed.I think a move to
StringBuilderwould be a good first step and would get the majority of the benefit.Any move to
DefaultInterpolatedStringHandlerwould build on that.DefaultInterplolatedStringHandleris alsodotnet6so there would need to be a fallback implementation withStringBuilderanyway fornetstandard2.0, or alternativelyDefaultInterplolatedStringHandlercan be a replacement when FSharp no longer supportsnetstandard2.0.realparadyne commentedon Jan 9, 2024
If it could iteratively optimise the interpolation then constant strings could be interpolated in at compile time, reducing the number of elements to concatenate. Speaking of which, if at the end all that's required is to concatenate some constant strings and some dynamically produced strings (up to 4 I think?) there are a bunch of overloads to String.Concat() that should be faster than using StringBuilder and only allocate for the resulting string.
kerams commentedon Nov 25, 2025
Agree that a
StringBuilderversion is a great improvement already, and thus a fine starting point. One thing your benchmarks don't make use of (because it doesn't matter for such short strings) is the fact that we can typically estimate the minimum capacity the builder needs to allocate, sometimes even the exact length, which will give us ever so slightly more performance.