-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
New API proposal: null replacement with Option, Some and None #14024
Comments
Having Covariance for Rather than My proposed API is slightly different: public interface Option<out T> /* or Maybe<out T> */
{
T Value { get; }
bool HasValue {get;}
Option<R> Select(Func<T,R> select);
}
struct Some<T> : Option<T>
{
Some(T value);
T Value { get { ... } }
bool HasValue { get { return true; } }
Option<R> Select(Func<T,R> select);
}
struct Nothing<T> : Option<T>
{
Nothing();
T Value { get { throws } }
bool HasValue { get { return false; } }
Option<R> Select(Func<T,R> select); // returns Nothing<R>.None
public static readonly Nothing<T> None = new Nothing<T>();
} |
@Eyas if I unstrand you correctly you would change two different independent things: Option as interface and select rather than match? InterfaceSo if I understand everything correctly either we get variance or performance?
And that's exacly what IMHO should be avoided by adding the Option type. Match vs SelectWithout the Match method the consumer then would looks like this:
Honestly I prefer the |
Maybe F# activists like @forki, @theimowski or @mexx have thoughts to share here? |
Right, Option as interface doesn't protect against bad code, but bad code in this case is deliberate. Regarding match vs select and
To me, |
Another thing to add about " |
Interface vs StructI would love to have both, type variance and "not nullable" 😄 The bad code is not deliberate if it's written by beginners or devs which don't really know the Option concept. I am really 50/50 here, would love to hear other thoughts about this! Match/Select/Get.../....Ahh, now I understand
@Eyas how do you thought the signature to be for |
True, although my argument is here is that, as long as you have a Regarding Select/GetOrElse/GetOrDefault, looks like C# itself is inconsistent. I wouldn't call it |
@yannisgu Thanks for the title ;) In F# Actually we should add an attribute like |
We should keep in mind that dotnet/roslyn#98 discusses allowing the language to disallow null in C# 7. The usefulness of |
Thanks @yannisgu for referring to my implementation of Option 👍 The primary reason I used a struct was to get around the null reference problem. If the proposed Option<T> Foo() {}
var x = Foo();
val y = Foo(); The big problem I was trying to solve was the multiple return values from a method and the fact that C# doesn't force you to consider them: A method with a return type 'class of T' can return Being able to call So really, it's not the Option type that's important, it's a way of propagating concrete values that needs work. I could imagine the proposed record syntax could help. Perhaps if records are immutable and non-nullable by default. If the struct route is taken, then some work needs doing to structs so that a default ctor can be called. It works OK for |
I suggest you look at Sasa's Option type. I've been using it for 8 years, so it's been refined quite a bit, and integrates quite well with C#'s typical idioms. It used to have a match-like function, but it wasn't very useful. TryGetValue generally sufficed as a high performance primitive, and when I wanted purely functional semantics, chaining Select and || worked quite well, and is more efficient:
The above is equivalent to the following in Sasa:
Since Sasa's option supports logical-or's short circuit semantics, the right hand side of || isn't actually built unless the left hand side evaluates to None. Contrast with the Match method where the None case must always be allocated just to immediately throw it away in the general case. |
@naasking I like that much more. The use of
I do agree that short circuit ors might be more lightweight than functions, but I don't thick this is necessarily true. It is true that the anonymous function in the |
Whether it's cheaper is an empirical question that can be settled if this proposal moves forward; I suspect it is, but time will tell. And to refine my last post, the Sasa version is actually cheaper than I portrayed because the returned value from GetCacheValue is itself a string:
So neither the left or right hand sides need a delegate, which is considerably cheaper than the Match method. |
@naasking - I'm trying to follow your comments, but they don't seem to match up with the linked Sasa source-code. This doesn't compile: string value = GetCacheValue(key) || "this is the default value"; Nor does this: string value = GetCacheValue(key).Select(v => v) || "this is the default value"; It should be: Option<string> value = GetCacheValue(key) || "this is the default value"; The OR coalescing doesn't return a string, it returns an What Sasa does allow (because of operator overloading) is equality/non-equality checking with the underlying type, i.e: Option<string> value = GetCacheValue(key) || "this is the default value";
if( value == "this is the default value" )
{
string x = value.Value;
} Which creates a new Option<string> value = GetCacheValue(key) || "this is the default value";
string str;
if( value.TryGetValue(out str) )
{
...
} Which I agree is faster than the LanguageExt implementation, but overall I think the type doesn't have the safeguards that I personally think an Option type should have. The type doesn't particularly promote expression based programming, which I think is what most people (who want an option type) are looking for, and it doesn't force the user to consider both options and provide a considered code path for both. Just my 2 pence worth, interested in other opinions. I actually like the OR coallescing, and will grab that for LanguageExt, but it doesn't change the fact that the type should force you to consider the options. |
Please don't reinvent. Please use the option type that's already in fsharp.core. It works like a charme and it will keep things binary compatible. We already had this trouble with tuple. It was a nightmare. |
@forki There's a number of not friendly aspects to that:
var x = FSharpOption<int>.Some(123)
var y = FSharpOption<int>.None`
The benefits I see to using the
Overall I think currently there are too many negatives. |
@louthy You are correct that the return type is not string, I remembered this belatedly but the difference is minimal:
I've waffled on specifically what the semantics of | should be, because it's trivial to provide the other semantics too:
However, this doesn't permit chaining option computations because operators can't take generic parameters, ie.
Since the compiler forbids both overloads, I just went with the more general semantics IIRC. This is also the only reason I provide a Value property. I could be persuaded to stick with the more restricted operator above, since this would then also eliminate the Value property. Edit: never mind, C# requires all parameter and return types to be the same for short-circuiting operators, so Sasa's current semantics is the only viable one. Re: safeguards, any truly safe patterns in C# either discourage the use of options because they're non-idiomatic or scale poorly (like Match), or they're mostly safe but still admit error if you really try (like TryGetValue). |
For years c# devs referenced that VB dll
|
@louthy we're talking about adding stuff to .NET core here, right? Not about access in C#. Just be very very careful with that one - it might have very negative impact. |
@naasking - Thanks for the clarification :)
In what sense are you using 'scale'? Match clearly isn't idiomatic, totally agree there. But I think that the idioms are due a refresh anyway (I personally despise the @forki - Yeah I understand, but I don't see how this is an accurate statement:
I listed the reasons why I personally don't think it works like a charm. A new .NET core type would need to work like a charm in C#, F# and VB. |
@louthy "Scale" in syntactic sense, where it's unnecessarily noisy and doesn't nest cleanly, and in the runtime sense, where option computations create lots of unnecessary thunks. If option is going to be a viable alternative to Code Contracts static analysis or other candidates, it needs to be almost zero overhead. MS spent a lot of time optimizing how the CLR handles Nullable in this way too. I agree idioms can and should change. C# programs after LINQ are quite different than those before, in a good way. A pattern matching/deconstruction feature in C# would be great, but I'm not holding my breath. Pattern matching is necessarily closed under extensions, but classes are open, so accomodating this tension isn't trivial. Re: out parameters, I disagree that out and ref params simply exist due to lack of proper tuple deconstruction support (I agree this is why TryGetValue exists though). ref/out are essentially safe/scoped pointers, which permit many more types of computations than mere tuples. For instance, a method taking an out param can pass in an array slot:
I've never seen this in pattern matching languages:
I actually wish ref/out were more general, as they are in Ada, because pointers are pretty powerful. |
Agreed.
Indeed, I'm hoping it gets combined with a record type that allows for more control. But yeah, we'll have to wait a while!
It really is personal preference for me. I'm not a fan of in-place modification any more, I prefer immutable types and pure functions. Obviously there are times (primarily for performance) where in-place updates are useful, so I don't suggest throwing them away. I'd just prefer that the core libraries continue on a path that is more functional/expression-like in approach. My main gripe with |
@Eyas Regarding match vs select, I'd have both. You get match syntax so that you can use the same paradigm you're used to from functional programming, but |
@Richiban: definitely agreed about implementing I'm still not convinced by
|
Definitely a firm "no" on implementing ICollection. Option does not satisfy the ICollection contract in any meaningful sense. I personally don't even see the advantage of Option implementing IEnumerable. Sure, you get some of the LINQ methods to use the query syntax, but it's absolutely trivial to just add those extensions for Option, which is what Sasa's Option does. No boxing overhead. |
Implementing IEnumerable means it can be used in LINQ expressions with I agree that ICollection is the wrong interface, IEnumerable is fine. On Thu Feb 05 2015 at 14:11:16 naasking notifications@github.com wrote:
|
I'm skeptical that using Option in list-like contexts actually happens at all in practice. I'd like to hear a realistic scenario you've actually encountered. If you want integration with the full LINQ API, it's trivial to provide an AsEnumerable() overload that returns an IEnumerable from an Option. That's arguably more idiomatic too. Otherwise, I'd recommend reproducing the LINQ API directly on Option to avoid the boxing overheads. These methods are trivial one liners for option types. |
@naasking One of the first computation expressions that learners of F# write is an optional monad. Many people have asked for it to be in the F# core library. E.g. Given a domain with type A which has an optional B, which has an optional C which has a name, in F# using the maybe monad you could write
If Option implements IEnumerable then in C# you can write:
It gives you nice feature parity between the two languages, I feel. |
@Richiban Except that C# 6 already has a way to write that in a better way: var maybeName = a?.B?.C?.Name; Or, if you want to keep the intermediate variables: var b = a?.B;
var c = b?.C;
var maybeName = c?.Name; So I don't see what would LINQ support add here. |
@svick -
So because you've never done it, nobody could possibly ever want it, right? If you take a more functional approach to writing code, then this is a very valuable property which removes the clutter of ifs and matches. My most recent use was what writing a parser monad. If you want a trivial example, how about wrapping
I do that on Language-Ext for completeness, but I don't think making |
@louthy I was not arguing against |
@Richiban The option/maybe monad pattern doesn't need Option to implement IEnumerable, it simply needs Option to provide extension methods for Where, Select, and SelectMany. Sasa's option type already does this, and the rest of the LINQ operators aren't really useful in this case, ie. ordering and grouping on options, while not exactly an error, doesn't have much utility either. @louthy "If you take a more functional approach to writing code, then this is a very valuable property which removes the clutter of ifs and matches." As above, you don't need option to implement IEnumerable to achieve this. Your parser would perform better if it didn't have to box those options every time you call into the IEnumerable LINQ operators. @louthy "If you want a trivial example, how about wrapping Request.QueryString[x] in an Option, and then making the output of a request (a list) dependant on a property being provided in the request." So you're suggesting that some IEnumerable is generated from that option? Let's say the request is to generate a list of numbers starting from 0 up to the value bound to x. I just added two extension methods to Sasa's option to mix with IEnumerable LINQ which enables that the following to work:
This produces an IEnumerable as expected, with no unboxing overhead that Option would incur if it implemented IEnumerable. Each extension is two lines of code, so hardly a burden. I think the performance advantage is worth it, particularly if options are to become standard. |
@naasking |
It will compile if you implement a SelectMany method on Option which takes an IEnumerable parameter. I agree that implementing IEnumerable on Option is both counter-intuitive and unnecessary.
|
@louthy, as @eatdrinksleepcode mentioned, you simply need two new overloads, which I mentioned in my last post that I added. I simply didn't push them to the sourceforge repo. They are up now. |
If this Interface could be restricted to the follow interface and types. Public Interface IMayBe
End Interface
Public Interface ISome
Inherits IMayBe
End Interface
Public Interface ISome(Of T)
Inherits ISome
ReadOnly Property Value As T
End Interface Concrete implementation. Public Structure None
Implements IMayBe
Private Sub New()
End Sub
End Structure
Public Structure Some(Of T)
Implements ISome(Of T)
Private Sub New()
End Sub
Friend Sub New(Value As T)
Me._Value = Value
End Sub
Public ReadOnly Property Value As T Implements ISome(Of T).Value
End Structure
Private Shared _None As New None()
Public Shared Function Nowt() As None
Return _None
End Function
Public Shared Function _Some(Of T)(Value As T) As MayBe.Some(Of T)
Return New Some(Of T)(Value)
End Function
End Class
Public Module Exts
<Runtime.CompilerServices.Extension>
Public Function IsNone(x As IMayBe) As Boolean
Return TypeOf x Is MayBe.None
End Function
<Runtime.CompilerServices.Extension >
Public Function IsSome(x As IMayBe) As Boolean
Return TypeOf x Is MayBe.ISome
End Function
End Module |
This work is somewhat related to the nullabiltiy work C# is doing right now. What are your thoughts on whether this type is still useful, if C# would expose the notion of non-Nullable/Nullable reference types? |
Non-nullable/nullable references types are great, but I'd agree with @Eyas that this proposal is complementary rather than an alternative, as it's still very useful to have a first class expression that something might present or absent. Nullable means something might not be there, while Option means something is or isn't there. Even with the additions of the elvis and null coalescing operator, it'd still be "better" to have a separate API and mindset for dealing with present or absent values, much like F#. |
It sounds like this might make more sense in the context of a language feature. Since there is an issue tracking that proposed language feature I am going to close this for now. If/when the language design is complete and the shape of the additional type understood, we should create a new issue tracking implementing that type. |
* manifest file for 1.0.4 * Include Windows Server Hosting bundle in 1.0.4 and 1.1.1 downloads * Include Windows Server Hosting bundle link and install command in 1.0.4 and 1.1.1 downloads
The .NET framework and C# have implemented a lot of ideas of functional programming. LINQ, lambdas and immutability are only a few examples. In my eyes this makes the real difference between Java and .NET
Solving the null reference problem would be the next great addition to the .NET framework borrowed from functional languages.
So in this issue I want to propose the addition of a Option type to the .NET Framework.
Background
Taken from https://github.com/louthy/language-ext#null-reference-problem:
Rationale and Usage
So basically the
Option
type enforces developers to check if a return value has really a value.An example function returning an option would be something like this:
The consumer code would then look like this:
Proposed API
Details
Open questions
Value
property as an alternative to Match?Option<string>
is then also anOption<object>
)?Thoughts?
The text was updated successfully, but these errors were encountered: