-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Return "None" when doing string None
, instead of empty string
#891
Comments
Four string functions, with four different behaviours for None!
They should all return "None". |
I'm not so sure I agree. Though I do think there should be more consistency. In fact, I'd say two of the four operations you listed give surprising results (but the other two meet, at least my, expectations). Here's how I'd expect it to work: string None // "None"
sprintf "%A" None // "None"
None.ToString() // Null Reference Exception
sprintf "%O" None // Null Reference Exception |
Expectations in what sense? Are you saying that NREs are good behaviour? Presumably if you read the compiler source code you would get to the point where the current behaviour for the first two are also expectations in the sense you would expect the output given the current implementation? |
There are really two issues here:
Working backwards, the second point seems very straight-forward to me. Yes -- the Now, as to the first point... I think, everything else being equal, we'd all love for |
@pblasucci Yes for 2 and no for 1. The null issue is hard to fix, agreed, but not impossible! Working through what is needed here: #884 |
@charlesroddie I'm willing to bet #884, as it currently stands, doesn't ever get approved (though I can imagine marking |
The benefit of I can imagine a design where it gets erased entirely, but I can't imagine a design where it is replaced with a heap allocated object, it would be too detrimental to performance. The value type approach wouldn't help here either, if only because of compatibility (and we already have For user defined types, maybe it can be deprecated, but we'll still need to support it for the foreseeable next decades. Btw, on NRE, I don't think users should have to be bothered by it. We already have instance methods that don't throw, the only issue is with the ones inherited from I file that under the "principle of least surprise". |
Yup. Definitely agree.
💯 Spot on.
Can you provide an example? I'm struggling to think of one (where the target is
Probably true... but how can you enforce that in the presence of reflection? |
If this is about None then user-defined types don't come into this. Note that a non-null representation would only be a single allocation on the heap as pointed out by @Tarmil in the other thread.
Reflection breaks every assumption of the type system I believe so there is no way to make that safe and we should restrict to making non-reflection-using code work well and safely. |
> Unchecked.defaultof<option<int>>.IsNone;;
val it : bool = true
> None.IsNone;;
val it : bool = true I don't know if these are implemented as extension members or something, but certainly they "feel" as if they're on the type. Also, Also just found that you cannot do |
If I understand you correctly, than isn't that precisely what we try to prevent? |
A single allocation for all uses of That being said, I'm not sure if testing equality against this static value (which is what pattern matching on option would compile to) would be as easily optimized by the JIT as a null check. |
@abelbraaksma Thanks for the example. I checked and Aside: can we find examples of this behaviour for any other types (especially types defined outside of FSharp.Core)? |
@pblasucci No, unless you make it an extension. Point style programming is susceptible to NREs.
Interesting. But are they really instance methods in the assembly? I'll have to check. Inside Core, special care is taken that strings, even when null, won't raise. But this is only true if you call the Core functions of
@charlesroddie, I see now what you mean, one instance that's the same globally, like a module let. That would mean you could use reference equality. But in compiled jitted code, that's a pointer dereference, and that's still going to be slower than a null check. In part because of predictability for the cpu and possibly other low level stuff. (as another aside, wonder what happens if the address gets pinned and not released) Though it's easy to code this up. I'll see if I can get some numbers (but this discussion digresses a bit from the simple proposal here, as even if we can proof there's no noticeable difference, it's unlikely to make it into the language anytime soon for binary compat reasons; also, any code that relies on None being null, like a lot of C#, but also any existing ngen'ed library, would fail). |
A proper static readonly gets jitted as a constant in .net core, so reference equality could be very fast |
@NinoFloris, good to know (so not in the garbage collectible heap, but heap nonetheless, right?), it'll help interpreting the next dive into binary assembly. |
Yeah I'm not exactly sure if it works for reference types :/ |
I tried to get the JIT result from sharplab but it throws an exception on this code :/ https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AbEAzAzmgFxAENcBbAHzIgBMBXDGAAgBUZcCA6AeQAcCAlhAB2uALAAoSQQCevZgFkZfQSIA8AchYA+JgF5JTI0wpMlAORExDx00oDKEMswjYmWyZMYEmbhPpsjMmICMAALJn8AdwECMMCTJkthZjhdAEYE00dnSKY0yMkgA== |
I thought sharplab.io didn't support JIT for F# code? To get the assembler code, a handy shortcut I use is with Benchmarkdotnet, which is smart enough to show only the relevant parts (sometimes it misses bits but it usually gets it right). Use the DisassemblyDiagnoser. |
Oh, I thought I had used sharplab this way before, but I guess not :/ |
@abelbraaksma I'm marking this as approved in principle. I'll leave it up to @vzarytovskii and others to decide if the breaking change is acceptable. I'm not entirely sure of the implementation. I gather you're proposing to reflect over the type variable in the case that it is no compile-time resolved? Note that
would still always return |
I thinks its doable. This would likely be best achieved by (or any other easy means for people to optout temporarily if a mere SDK update breaks them and they need to gain some time to address necessary change) |
Print "None" when value is
None
I propose we normalize how
option
is treated when turning its value into a string, namely,string None
should return "None". Currently it returns the empty string.This would bring it more in line with this existing change (dotnet/fsharp#7693) which returns "ValueNone" for
string ValueNone
. It also brings it in line with FSI, which prints "None" to the output window when it isNone
. Conversely, when debugging,None
will appear asnull
, even though this PR was done to mitigate this.The existing way of approaching this problem in F# is:
Roll your own version to get the string value of an
option
. But this doesn't help with debugging. It's also not very trivial to re-implement a genericstring
function in such a way that it works differently foroption<_>
.Pros and Cons
The advantages of making this adjustment to F# are:
null
when they inspect a variable that isNone
. This would help the general debugging experience.None
?" Until it dawns on you after some headbanging.ValueOption
s.The disadvantages of making this adjustment to F# are :
string
, and if there are libraries out there that rely onstring None = ""
, these would failoption
is converted to anobj
(boxed), the old behavior remains, as it is impossible to detect the difference between anull
object reference and anull
value that once wasNone
. But this problem applies in other parts of the .NET ecosystem as well.Extra information
Estimated cost (XS, S, M, L, XL, XXL): XS (proto-type is ready, link to PR follows)
I remember earlier discussions on this and that there was no simple solution, but perhaps that was about
None.ToString()
, which raises an NRE. I didn't know that there was a type-safe syntax form, albeit compiler-specific, that makes this work. It is possible that in the past this syntax wasn't available, but now that it is, I suggest we make use of it.I've also checked to fix
None.ToString()
and the like, but that's a wider problem and should go in its own suggestion.Related suggestions:
ValueNone
: ToString() throws an exception for ValueOption<_>when value is ValueNone dotnet/fsharp#7693options
andValueOption
: Voption tostring and other stuff dotnet/fsharp#7712 (note that this PR doesn't work anymore forNone
)Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
The text was updated successfully, but these errors were encountered: