-
Notifications
You must be signed in to change notification settings - Fork 22
Description
Allow use of 'string' everywhere, and optimize generated IL
I propose:
-
that we change
^Tto'Tfrom theOperators.stringfunction. This allows its usage in places where you would now get an error that it "escapes its scope" (report: Usingstring objvs obj.ToString() leads to problems: the type parameter can escape its scope dotnet/fsharp#7958). Consider:type Either<'L, 'R> = | Left of 'L | Right of 'R override this.ToString() = match this with | Left x -> string x // not possible | Right x -> string x // not possible
this code fails to compile with:
FS0670 This code is not sufficiently generic. The type variable ^T could not be generalized because it would escape its scope.
-
that we optimize the generated IL. Currently, due to what can be considered a bug, regardless of input, the generated IL is huge (report: The 'string' operator produces suboptimal code when the object's type is known. dotnet/fsharp#9153 Since this is a statically-inlined function, each time you call
string, it expands to this:object obj = 4; if (obj != null) { IFormattable formattable = obj as IFormattable; if (formattable != null) { IFormattable formattable2 = formattable; return formattable2.ToString(null, CultureInfo.InvariantCulture); } object obj2 = obj; return obj2.ToString(); } return "";
But need to be only this in most cases:
myValue.ToString(null, CultureInfo.InvariantCulture);
-
that we improve speed. Currently, a double null-check is done. This is unnecessary, in fact, many code paths don't need a null-check at all. It would also be nice to remove
callvirtand usecallinstead (or aconstrained callvirt), in cases where this is possible.
I was already implementing this (see dotnet/fsharp#9549), but while doing so, and discussing it with @KevinRansom, we found that there's a tension between the above requests and that not all of them can be supported for all scenarios. This is in part due to the nature of enum: it's treated as one of eight integer types.
The existing way of approaching this problem in F# is: roll your own string function (this has long been my approach in my own code, and this is easy if you don't have to deal with case-printing for enum).
Details are in a tentative RFC.
Pros and Cons
The advantages of making this adjustment to F# are:
- Able to use
stringeverywhere (except with refs, but that's another discussion) - Removal of dead code: current implementation has special paths for
int,floatetc that are never hit - Improved performance in most scenarios
- Inlining will be done by the JIT in most cases (I tested this, this is true)
- Much, much shorter generated IL code
The disadvantages of making this adjustment to F# are:
- Some people may frown on losing
inline - Code that ends with
string x, wherexis an input parameter, if never used will be inferred as typeobj. After this change, it will remain generic (this is more of advantage than a disadvantage, I think)
Extra information
Estimated cost (XS, S, M, L, XL, XXL): XS
While researching this was more challenging than I thought, the final changes will be just a few lines, and probably some new test cases.
Related suggestions: None (but see the linked issues for the bug reports that lead to this)
Of note:
- @dsyme reacted positive to my earlier proposal of such PR: Using
string objvs obj.ToString() leads to problems: the type parameter can escape its scope dotnet/fsharp#7958 (comment) - After this change, using compiler-specific syntax, native and known struct types will resolve statically, unknown types will retain current behavior, albeit with slightly optimized code.
- I decided to put this through the lang-suggestions because technically it is a change in behavior, albeit slightly, but also to gather input in case I'm on the wrong path with the PR.
Affidavit (please submit!)
Please tick this by placing a cross in the box:
- This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
- I have searched both open and closed suggestions on this site and believe this is not a duplicate
- 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
- I or my company would be willing to help implement and/or test this: PR is already underway, see Optimize 'string', remove boxing where possible, prevent FS0670, simplify generic code (RFC-1089) dotnet/fsharp#9549