fsharp / fsharp/fslang-suggestions
[Breaking] Warn unadorned string interpolation when the interpoland implements IFormattable / is generic
- Dominant language
- No language data
- Stars
- 373
- Forks
- 21
- PR merge metrics
- No merged PRs in 30d
Description
A quick test. Is this function pure?
```fs
let sum x y = $"{x} + {y} = {x + y}"
```
Looks like the output purely depends on its inputs. Right?
`sum 1 2` gives `1 + 2 = 3`.
`sum 1.1 1.1` gives `1.1 + 1.1 = 2.2`. Right?
```fs
let sum x y = $"{x} + {y} = {x + y}"
System.Globalization.CultureInfo.CurrentCulture <- System.Globalization.CultureInfo.CreateSpecificCulture "en-US"
printfn $"{sum 1.1 1.1}"
System.Globalization.CultureInfo.CurrentCulture <- System.Globalization.CultureInfo.CreateSpecificCulture "tk-TK"
printfn $"{sum 1.1 1.1}"
```
```
1.1 + 1.1 = 2.2
1,1 + 1,1 = 2,2
```
What a pitfall - there is action at a distance! This function is actually impure. But if we used the format specifiers:
```fs
let sum x y = $"%g{x} + %g{y} = %g{x + y}"
System.Globalization.CultureInfo.CurrentCulture <- System.Globalization.CultureInfo.CreateSpecificCulture "en-US"
printfn $"{sum 1.1 1.1}"
System.Globalization.CultureInfo.CurrentCulture <- System.Globalization.CultureInfo.CreateSpecificCulture "tk-TK"
printfn $"{sum 1.1 1.1}"
```
```
1.1 + 1.1 = 2.2
1.1 + 1.1 = 2.2
```
We shouldn't create these pitfalls for newcomers to F#.
**I propose we** turn the [warning against unadorned string interpolation](https://github.com/dotnet/fsharp/pull/15747) on if the interpoland implements IFormattable or is generic.
The warning message would instruct the user to take action using one of the code fixes:
```
assume culture invariant (change all)
assume culture invariant (change once)
assume culture aware (change all)
assume culture aware (change once)
```
Invariants would come first since that is what F# code usually assume.
With https://github.com/fsharp/fslang-suggestions/issues/897 implemented (say `=` for equal everywhere aka "culture invariance" and `#` for not equal everywhere aka "culture aware"), the new code after codefix would either look like
```fs
let sum x y = $"%={x} + %={y} = %={x + y}"
```
or
```fs
let sum x y = $"%#{x} + %#{y} = %#{x + y}"
```
which are equivalent to
```fs
let sum x y = $"%=O{x} + %=O{y} = %=O{x + y}"
```
or
```fs
let sum x y = $"%#O{x} + %#O{y} = %#O{x + y}"
```
respectively, with one difference: the version without `O` is subject to [FS3579](https://github.com/dotnet/fsharp/pull/15747) while the version with `O` is not.
The implementation can just change `.ToString()` to F# `string` function which does this properly.
This would only apply to types implementing IFormattable or generic types. Known non-IFormattable types like strings, booleans, DUs and records would be unaffected.
We can also warn for `%O` though it's not as important as the unadorned string interpolation.
**After 3 years (same timeframe as .NET LTS support), we may change the default to culture invariant and remove this warning.** (This is what makes this suggestion not purely a tooling suggestion)
**The existing way of approaching this problem in F# is** either to look out for such mistakes manually or enforce usage of % formatting everywhere.
## Pros and Cons
**The advantages of making this adjustment to F# are**
1. Easier to understand for beginners
2. Avoid a class of potential bugs
3. A better default for F#
**The disadvantage of making this adjustment to F# is** that this is a breaking change for those who turn WarnAsError on.
## Extra information
**Estimated cost (XS, S, M, L, XL, XXL):** M
**Related suggestions:**
- https://github.com/fsharp/fslang-suggestions/issues/897
- https://github.com/fsharp/fslang-suggestions/issues/1285#issuecomment-1660296244
- https://github.com/dotnet/fsharp/pull/15747
## Affidavit (please submit!)
Please tick these items by placing a cross in the box:
* [x] This is not a question (e.g. like one you might ask on [StackOverflow](http://stackoverflow.com)) and I have searched StackOverflow for discussions of this issue
* [x] This is a language change and not purely a tooling change (e.g. compiler bug, editor support, warning/error messages, new warning, non-breaking optimisation) belonging to [the compiler and tooling repository](https://github.com/dotnet/fsharp)
* [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
* [x] I have [searched both open and closed suggestions on this site](http://github.com/fsharp/fslang-suggestions/issues) and believe this is not a duplicate
Please tick all that apply:
* [ ] 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.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.