[RFC FS-1060 - Nullable Reference Types] discussion #339
Replies: 57 comments 12 replies
-
There are other examples, e.g. compare
and
Here the reduction to perform on Requiring parenthesization may be feasible but definitely fiddly to implement
|
Beta Was this translation helpful? Give feedback.
-
Other options might be let len (str: Nullable<string>) = ... // for reference types compoiled as `string`, for value types Nullable<_>
let len (str: string or null) = ...
let len (str: string || null) = ... |
Beta Was this translation helpful? Give feedback.
-
Do option types "None" still map to Null in C# world? And therefore Some t maps to this new Non Nullable? That would be a breaking change in many places in my code base |
Beta Was this translation helpful? Give feedback.
-
@jamessdixon You can read about this a bit here: https://github.com/fsharp/fslang-design/blob/master/RFCs/FS-1060-nullable-reference-types.md#interaction-with-usenullastruevalue In short, |
Beta Was this translation helpful? Give feedback.
-
My feedback: I really like the "?" notation because it is concise, and I'm already used to it from C#: Unimportant side question: would we would have to If below is also valid, then does that mean we could also substitute I really like the twist that the emitted warnings can also be tuned to emit as errors. I would definitely want that to be the case all the time, but I can understand why it might not be the default. Nice to have the option though. |
Beta Was this translation helpful? Give feedback.
-
No, it would have to be available without opening that namespace.
Perhaps. We're considering how to rationalize nullable reference types with nullable value types ( |
Beta Was this translation helpful? Give feedback.
-
@dsyme We'll also need to consider nullable operators: https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/symbol-and-operator-reference/nullable-operators I think I'd discourage their usage, but it could feel weird that they didn't work with nullable reference types. |
Beta Was this translation helpful? Give feedback.
-
@cartermp Nullable operators are primarily present for query expressions. Indeed Nullable in C# is primarily there for nulls in databases and LINQ It may well raise lots of questions about nullability and database queries and database ORM mappers. And type providers too |
Beta Was this translation helpful? Give feedback.
-
Yeah, I'm not keen on them being used outside of that context (lacking any data, my impression is they're rarely used already). But it'd be good to keep track of nonetheless. |
Beta Was this translation helpful? Give feedback.
-
A note on inference subtleties... In current F#, code such as let f x = match x with null -> 1 | _ -> 2 places a > let f x = match x with null -> 1 | _ -> 2;;
val f : x:'a -> int when 'a : null That is, in the absence of other information F# 4.5 infers a definition-site annotation on the type variable itself. If the input type is known to have a use-site nullable annotation, then it seems reasonable to allow this: let f (x: string?) = match x with null -> 1 | _ -> 2;;
val f : x:string? -> int This is not so bad and is likely the behaviour we will expect, but it requires type annotations in a way that is unusual for F# code: adding the type annotation fundamentally changes what the code is about (from definition-side nullability to use-site nullability). This is somewhat consistent with how units of measure work in in F#. Note that in a fully fledged version of a use-site nullness feature you would expect > let f x = match x with null -> 1 | _ -> 2;;
val f : x:'a? -> int However this would be a breaking change and in any case nullable annotation code can't be generic like this. If we did allow it (eg. in inlined code) then a type annotation would be needed: > let inline f (x: 'T?) = match x with null -> 1 | _ -> 2;;
val f : x:'T? -> int Indeed this should be allowed (it is compilable once 'T is known to be either a struct or a not-struct) > let f (x: 'T? when 'T : not struct) = match x with null -> 1 | _ -> 2;;
val f : x:'T? -> int when 'T : not struct
> let f (x: 'T? when 'T : struct) = match x with null -> 1 | _ -> 2;;
val f : x:'T? -> int when 'T : struct |
Beta Was this translation helpful? Give feedback.
-
A note on inference... In the prototype the following gives a warning because let y0 = isNull null I suppose we should use In contrast the following does not give a warning: let y0 = isNull (null: obj?) |
Beta Was this translation helpful? Give feedback.
-
A note on From CompilerLocationUtils.fs with nullable checking on:
Here is RegQueryValueExW accepting nullable strings or not? |
Beta Was this translation helpful? Give feedback.
-
I'm currently working on compiling A note on val unbox : value:obj? -> 'T
val box : value:'T -> obj?
val tryUnbox : value:obj? -> 'T option However I actually feel that for most F# programming the type |
Beta Was this translation helpful? Give feedback.
-
Now compiling up FSharp.Compiler.Private using nullness checking prototype. Just looking at the first few messages. Here's one example of a place where we need to consider how we will update FSharp.COre: type StructuredFormatDisplayAttribute =
inherit Attribute
new : value:string-> StructuredFormatDisplayAttribute
member Value: string SHould |
Beta Was this translation helpful? Give feedback.
-
Mixing optional params and nullable params with the ? in member definitions is going to lead to a lot of confusion, as in the optional syntax:
|
Beta Was this translation helpful? Give feedback.
-
In error messages, e.g. "Nullness warning: Go direct to Tony Hoare, if you pass go collect £1B" |
Beta Was this translation helpful? Give feedback.
-
I think I would prefer not having any kind of text-based classification of the warning here. I'd much prefer the message itself just contain the relevant information, such as a type mismatch. But I'll give this a feel with a prototype. |
Beta Was this translation helpful? Give feedback.
-
Yes it's partly for my own purposes right now, the |
Beta Was this translation helpful? Give feedback.
-
I don't know if this is still a thing, but I just saw the talk Philip gave to NDC about this. In the talk the first question he gets is why not treat them as Option values instead of building a whole new thing. He answers that it will be a lot of work, especially in the interop layer, and I can see that. But I just wanted to say that it really feels like a wasted opportunity to build a new thing when we already have the Option type that, for all intents and purposes, does the same thing. Regarding the interop code, when handled a null now it will already crash right? There is just a convention that you won't be handled a null, it is not enforced. So if you treat the nullable type as an Option it would be possible to do an Option.bind on all the parameters and when a null is found it will just crash like it would do now. There are probably smarter ways of doing this, I am by no means a great programmer or anything. As for backwards compat, these types are only in C# 8 right if I understand correctly? So they are only found in the most recent .Net versions, can't we do a version check somewhere. I am not an expert by any means, just my 2 cents. |
Beta Was this translation helpful? Give feedback.
-
@boeremak Implicitly treating any nullable reference type as an Additionally, the way that the option type is compiled into .NET metadata means that it must be a nullable reference type, since it uses |
Beta Was this translation helpful? Give feedback.
-
@cartermp I did some more reading about the C# rationale and it seems they want to transition to a place where F# already is, namely things can't be null and have to be initialised when you use/instance them. At least that is what I got from reading this: https://devblogs.microsoft.com/dotnet/nullable-reference-types-in-csharp/ Is that correct? Another thing, seems like quite a change especially if you keep the IL output the same. So it is purely enforced in the tooling? A I did some more reading and these are good points: fsharp/fslang-suggestions#577 (comment) And is exactly my thinking. Edit: Another option: why not keep it the way it is, everyone already does null checks ( i hope :) ) when things come from C# and when it comes from F# it can't be null anyway and just forgo this completely? Maybe add some smarts to intellisense to say "this has been compiled in C# 8 so you probably don't need to do null checks on this". I don't know the feasibility of this though. One think I would really dislike is for F# to accommodate C# in a way that makes the core language cumbersome to use. I really do like the language. Edit2: If this has already been discussed, I am sorry I just saw the talk today. |
Beta Was this translation helpful? Give feedback.
-
I suggest reading the Motivation and Principles sections detailed here for a thorough rationale of the feature from an F# and .NET perspective: https://github.com/fsharp/fslang-design/blob/master/RFCs/FS-1060-nullable-reference-types.md#motivation Much of the rest of the spec is concerned more with details on what needs to be done and what they design will be, but this prelude is the framing for the feature. |
Beta Was this translation helpful? Give feedback.
-
but I think it more forward-looking |
Beta Was this translation helpful? Give feedback.
This comment has been hidden.
This comment has been hidden.
-
The indications are that F# may not support NRTs at the launch of .Net 5 and that's going to be a large problem for F# as this is the recommended timeline for libraries to adopt NRTs. The suggestions in the RFC are good, but not all are necessary to support in V1 of this feature. It's important to get this feature to a minimal spec to reduce the effort to get V1 out. Suggestions for cuts to the RFC / moves to a "Future" section"Partial alignment of nullable value types and nullable reference types."This can be cut. Value types don't need a Omitted NonNull in pattern matching
Having Null ambivalence (obliviousness)If necessary this can be dropped from V1 since it could add a lot of complexity in implementation. Especially droppable if there is a rollout phase in F# where the feature is opt-in: early adopters can see if obliviousness causes significant difficulty. Unresolved question: nullability warnings on a per-file basis?This can easily be scoped out of V1. Unresolved question: UseNullAsTrueValueThis is an F# flaw and not C#/.Net's responsibiltiy to fix. Have these type exposed as NRTs and have a migration path for Option. |
Beta Was this translation helpful? Give feedback.
-
I think we should adopt the same syntax as https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1092-anonymous-type-tagged-unions.md with the required parentheses like |
Beta Was this translation helpful? Give feedback.
-
One major reason why I'll probably never adopt F# is a wide range of C# libraries that use NRTs to mark "optional" properties. F# uses Option type but it seems useless and counter-intuitive when you consume C# libraries just forcing you to use I think it would be interesting if NRTs could be interpreted as |
Beta Was this translation helpful? Give feedback.
-
For me it’s never been that much of an issue turning a potential null into F# friendly usage, it’s not very often and when I do encounter it it’s just a Option.ofNull etc. I suppose it depends on the domain?
… On 21 Jun 2022, at 00:06, bugproof ***@***.***> wrote:
I think if someone consumes a C# library that makes use of NRT from F# it would benefit F# users if they were treated as Option<T>. NRTs were added to C# only because there was no way to tell if a property should really be null but it doesn't do much else.
In F# there was always Option<T> type which is and will always be much better than NRT and anything C# team will come up with in the future like required keyword.
Would you mind to expand on the other reasons for never adopting F#
I find that bridge between C# -> F# too convoluted. I'd like to use Option<T> everywhere in my code base but I know it's simply impossible because there will always be some C# lib I will need to use and it will feel just like writing C# again but with a weirder syntax. If there was a way to write code without using null ever again just like Rust but .NET ecosystem that would be nice.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.
|
Beta Was this translation helpful? Give feedback.
-
ILMerge does that.
…Sent from my iPhone
On 25 Jun 2022, at 11:06, bugproof ***@***.***> wrote:
It would also be nice if F# types could be consumed from C# without a dependency on FSharp.Core. Maybe if there was a tool based on Mono.Cecil or dnLib that removes the dependency and converts Option<T> to NRT that would be great. I maintain one library that has a lot of data only classes and a lot of optional properties. If I could re-write it in F# and make it usable from both it would be a win-win for everyone.
Or maybe write a tool that does the opposite? Generate F# compatible packages that convert NRT/required stuff to Option<T>. That would be interesting.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.
|
Beta Was this translation helpful? Give feedback.
-
To work around the issue, I did the following: https://github.com/ken-okabe/vanfs?tab=readme-ov-file#nullable NullableTtype NullableT<'a> =
| Null
| T of 'a
member this.Value
= match this with
| Null -> failwith "Value is null"
| T a -> a
let NullableT a =
match box a with
| :? NullableT<'a> as nullable -> nullable
| _ -> T a Usagelet nullable1 =
Null
let nullable2 =
NullableT "hello"
log nullable1
// Null
log nullable2
// T hello
log nullable2.Value
// hello |
Beta Was this translation helpful? Give feedback.
-
Discussion for https://github.com/fsharp/fslang-design/blob/master/RFCs/FS-1060-nullable-reference-types.md
The biggest user-facing change in the RFC is the discovery that syntax like this:
Would be a breaking change. For example, this code is valid F# today:
And there are likely other variants of this that people could conceivably have in their code.
Thus, we are currently thinking that we will match the C# syntax:
Since it is not a source-breaking change.
Beta Was this translation helpful? Give feedback.
All reactions