fslang-suggestions icon indicating copy to clipboard operation
fslang-suggestions copied to clipboard

Suggestion: Add ways to disable not typed interpolated strings

Open vincentremond opened this issue 2 years ago • 6 comments

I propose we have a way to improve interpolated string.

Currently with interpolated strings can be done with or without the type identifier ( %s for example ). It can be harmful when we do some code refactoring, not-typed interpolated strings can produce unexpected results.

For example :

let hello = "Hello World"
printf $"{hello}" // ✅ Builds fine
printf $"%s{hello}"  // ✅ Builds fine, same as `printf "%s" hello`

After some code refactoring we could be in this situation :

type HelloWorld = HelloWorld of string
let hello = HelloWorld "Hello World"
printf $"{hello}"  // ❌ Builds fine, prints `HelloWorld "Hello World"`
printf $"%s{hello}" // ✅ Does not build ( Error FS0001 : This expression was expected to have type 'string' but here has type 'HelloWorld' )

I would suggest adding a warning when using not-typed interpolated strings. And an optionnal way to force the usage of typed interpolated string for a project (WarningsAsErrors ?).

Pros and Cons

The advantages of making this adjustment to F# are :

  • More safety when using interpolated strings
  • Less errors when refactoring code
  • Errors detected at compile time

The disadvantages of making this adjustment to F# are :

  • It can be a pain to add the type identifier to all interpolated strings
  • Warnings can be annoying when using not-typed interpolated strings

Extra information

Estimated cost (XS, S, M, L, XL, XXL): : M (?)

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • [x] This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • [x] I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • [x] 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:

  • [x] This is not a breaking change to the F# language design
  • [x] I or my company would be willing to help implement and/or test this

For Readers

If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.

vincentremond avatar Jul 05 '23 16:07 vincentremond

We could use a GH flag "this would not be needed if F# had Analyzer SDK". I think this suggestion is the kind of coding preference which would very well fit being an own analyzer.

T-Gro avatar Jul 07 '23 15:07 T-Gro

I chatted with @vzarytovskii and we like the idea - whether by analyzer of built-in. So approving this as either an off-by-default warning or a built-in analyzer. It's so simple to implement an off-by-default warning would be just fine I think

dsyme avatar Aug 01 '23 13:08 dsyme

Does type identifier affect performance in any way? I could be mistaken, but as far as I remember there was a benchmark where type identifier makes interpolation slower

Lanayx avatar Aug 04 '23 05:08 Lanayx

Yes - more characters that need to be processed at run time.

$"%d{1}"
PrintfModule.PrintFormatToStringThen(new PrintfFormat<string, Unit, string, string, int>("%d%P()", array, null))

$"{1}"
PrintfModule.PrintFormatToStringThen(new PrintfFormat<string, Unit, string, string, int>("%P()", array, null))

It's probably only marginally slower. If performance is a serious concern, I imagine you'd want to avoid sprintf and F# interpolations altogether.

kerams avatar Aug 04 '23 08:08 kerams

I've decided to verify my memory and run benchmark. Good news is that I was wrong :)

|           Method |     Mean |   Error |  StdDev |   Gen0 | Allocated |
|----------------- |---------:|--------:|--------:|-------:|----------:|
|          Sprintf | 418.2 ns | 5.12 ns | 4.54 ns | 0.0620 |     520 B |
|      Interpolate | 282.8 ns | 5.33 ns | 4.99 ns | 0.0467 |     392 B |
|     StringFormat | 149.3 ns | 3.00 ns | 3.09 ns | 0.0114 |      96 B |
| InterpolateTyped | 248.5 ns | 4.34 ns | 4.46 ns | 0.0458 |     384 B |

Source code can be found here

Lanayx avatar Aug 04 '23 17:08 Lanayx

It's not just for refactoring. It's also for the pitfall of implicit current culture formatting for IFormattable values.

Happypig375 avatar Aug 16 '23 17:08 Happypig375