Skip to content
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

Closed
3 tasks
yannisgu opened this issue Jan 28, 2015 · 39 comments
Closed
3 tasks

New API proposal: null replacement with Option, Some and None #14024

yannisgu opened this issue Jan 28, 2015 · 39 comments
Assignees
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Runtime
Milestone

Comments

@yannisgu
Copy link

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:

null must be the biggest mistake in the whole of computer language history. I realise the original designers of C# had to make pragmatic decisions, it's a shame this one slipped through though. So, what to do about the 'null problem'?

null is often used to indicate 'no value'. i.e. the method called can't produce a value of the type it said it was going to produce, and therefore it gives you 'no value'. The thing is that when 'no value' is passed to the consuming code, it gets assigned to a variable of type T, the same type that the function said it was going to return, except this variable now has a timebomb in it. You must continually check if the value is null, if it's passed around it must be checked too.

Option works in a very similar way to Nullable except it works with all types rather than just value types. It's a struct and therefore can't be null. An instance can be created by either calling Some(value), which represents a positive 'I have a value' response; Or None, which is the equivalent of returning null.

So why is it any better than returning T and using null? It seems we can have a non-value response again right? Yes, that's true, however you're forced to acknowledge that fact, and write code to handle both possible outcomes because you can't get to the underlying value without acknowledging the possibility of the two states that the value could be in. This bulletproofs your code. You're also explicitly telling any other programmers that: "This method might not return a value, make sure you deal with that". This explicit declaration is very powerful.

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:

public Option<string> GetCacheValue(string key) 
{
    if(cache.Keys.Contains(key))
    {
        return cache[key];
        //return new Some(cache[key]) 
        //return new Option<string>(cache[key]);
    }
    else 
    {
        return Option<string>.None;
        //return new Option<string>();
    }
}

The consumer code would then look like this:

string value = GetCacheValue(key).Match(
    some: v => v,
    none: () => "this is the default value"
)

Proposed API

public struct Option<T>
{
    // For creating a None
    public Option();
    public Option(T value) 

    public static readonly Option<T> None = new Option<T>();

    public bool IsNone {get; };
    public bool IsSome { get; } 

    public static implicit operator Option<T>(T value)
    public static implicit operator Option<T>(OptionNone none)
    public TReturn Match(Func<T, TReturn> some, Func<TReturn> none);
    public static Option<T> Some(T value);
}

Details

Open questions

  • Should there be a public Value property as an alternative to Match?
  • In which assembly would this be correct? Namespace?
  • Should Option be a struct (security, since it cannot be null) or an interface (usability, since Option<string> is then also an Option<object>)?

Thoughts?

@Eyas
Copy link

Eyas commented Jan 28, 2015

Having Option<T> be a struct is great, but I'd rather have it be an interface that is implemented by a struct Some<T> and a struct None<T> to achieve type variance (Option<T> should be covariant in T). There are problems with having structs implementing interfaces, namely: (a) the cost of boxing a struct when referring to it as an interface, and (b) unpredictable results when using mutable structs that implement interfaces. Option implementers should be immutable, so the only issue is (a).

Covariance for Option is quite important. If I have a method that accepts Option<ICollection>, I should be able to pass it Option<IList> or Option<ImmutableList>.

Rather than Match, I'd rather have public Option<R> Select(Func<T,R> map) and a Value property that throws is None.

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>();
}

@yannisgu
Copy link
Author

@Eyas if I unstrand you correctly you would change two different independent things: Option as interface and select rather than match?

Interface

So if I understand everything correctly either we get variance or performance?
Thinking about it, does it makes sense to have Some<T> and Nothing<T> being structs if there is an interface which will be used by consumers most of the time? Code like would still be possible (it would compile):

public Option<string> Foo()
{
    return null;
}

And that's exacly what IMHO should be avoided by adding the Option type.
So the question here is usability (with variance) versus performance and security?

Match vs Select

Without the Match method the consumer then would looks like this:

Option<string> option = GetCacheKey(key);
string value = option.HasValue ? option.Value : "this is the default value";

Honestly I prefer the Match code 😄
I have not really unstrand the purpose of Select, so I am probably missing something.

@yannisgu
Copy link
Author

Maybe F# activists like @forki, @theimowski or @mexx have thoughts to share here?

@Eyas
Copy link

Eyas commented Jan 28, 2015

Right, struct gives us not-nullable, which is a great property. That said, I don't think an Option is usable at all if it had no type variance. Do you disagree? It would be bad to have to instantiate a new Option just to up/cast or pass things around.

Option as interface doesn't protect against bad code, but bad code in this case is deliberate.


Regarding match vs select and HasValue:

  • Perhaps a .GetOrDefault method would be useful here.
  • I'm using Select as the LINQ-like name, but Select is essentially a map higher order function. Often times, when you get an Option result, you want to apply a transformation on it if it is a "Some" and don't care about the "None" part. Only at the end of the transformation chain, when you consume the option, would you check for the "None" case. Feeding default Func<TResult> functions for that purpose doesn't seem to make sense.

To me, Match combines Select with .Value/'.HasValue'. I think Select, GetOrElse, and `Value' would be more idiomatic and consistent C#.

@Eyas
Copy link

Eyas commented Jan 28, 2015

Another thing to add about "Option as interface"-- that is basically what Scala does; they have a great covariant option type, which is actually nullable itself; (val x: Option[Int] = null is a valid scala program). That, however, is a deliberate behavior. Option in Scala is still useful as it allows you to check if something is valid and make a lot of common errors more deliberate.

@yannisgu
Copy link
Author

Interface vs Struct

I 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 Select! I still think that addionally to the Select(Func<T,R> select) proposed by you, there is the need of a method like Match, but I don't know what the best name and signature would be:

Select(Func<T,R> select, Func<R> none);
GetOrElse(Func<T,R> get, Func<R> else);
GetOrDefault(Func<T,R> get, Func<R> default);

@Eyas how do you thought the signature to be for GetOrElseor GetOrDefault?

@Eyas
Copy link

Eyas commented Jan 28, 2015

The bad code is not deliberate if it's written by beginners or devs which don't really know the Option concept

True, although my argument is here is that, as long as you have a .Value property that throws (which I think is perfectly reasonable, but it might be debatable), then you are also prone to runtime errors/exceptions.

Regarding Select/GetOrElse/GetOrDefault, looks like C# itself is inconsistent. Dictionary seems to have a FirstOrDefault which takes a default value (not a function). Enumerable seems to have FirstOrDefault which returns the "default(T)" value (useless here--that's null for reference types).

I wouldn't call it Select, though-- the Select I suggested above returns type R. If we have a function that returns a "default" for the None, case, then the signature of your Match makes sense, and Select would be misleading here. I still like GetOrElse..

@mexx
Copy link

mexx commented Jan 28, 2015

@yannisgu Thanks for the title ;)

In F# None is actually represented as null in IL. So your example of Foo function would actually mean to return None.

Actually we should add an attribute like DisallowNullLiteral or ActLikeStruct, which can be applied to a type, and with it in place the compiler should disallow code like in your example, and force the programmer to provide a value of the type.

@Eyas
Copy link

Eyas commented Jan 28, 2015

We should keep in mind that dotnet/roslyn#98 discusses allowing the language to disallow null in C# 7. The usefulness of Option types, in my mind, is orthogonal (or, at least, complimentary) to that.

@louthy
Copy link

louthy commented Jan 28, 2015

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 val keyword came in then that would be amazing, I am assuming however that it will make the assigned 'variable' reference immutable, rather than force the result of a method to be immutable. For example I am assuming both the examples below would be valid, in which case having Option<T> as a class probably won't have much of an impact (other than it's declarative usefulness):

   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 T|null|Exception. But the variable that holds the result doesn't elegantly handle those outcomes - it still presents an interface to T even when the value is null. Obviously Exception is a special case (which I deal with in the TryOption<T> delegate in LanguageExt), but T|null needs language support in my opinion.

Being able to call x.Value doesn't get around the problem. You may as well not use an Option type at all if you're going to go ahead and de-ref it without checking its state first. That's why Match is important (I chose match in LanguageExt to be closer to F#; the name isn't super important, but its role is, it should really be handled by pattern matching). I also implement Select, SelectMany, Where, and allow it to be used as an IEnumerable<T>. The GetValueOrDefault() route is fine (I use the non-idomatic name Failure to handle all of the variants). It implicitly does the checking of the Option's state on your behalf and is expected to give you a concrete value.

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 Option<T> because an uninitialised struct is None, but for other similar types like Either<L,R> then being able to initialise a struct with a default (or making the ctor private so unitialised instantiation isn't allowed, is essential.

@naasking
Copy link

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:

string value = GetCacheValue(key).Match(
    some: v => v,
    none: () => "this is the default value"
)

The above is equivalent to the following in Sasa:

string value = GetCacheValue(key).Select(v => v) || "this is the default value";

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.

@Eyas
Copy link

Eyas commented Jan 29, 2015

@naasking I like that much more. The use of II for None-matching pretty neat as well.

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.

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 none case will always be allocated, but nothing within. The right hand side of an || had a new expression, etc., then you would be right. In the case of a string literal, though, I'm not sure you are.

@naasking
Copy link

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:

string value = GetCacheValue(key) || "this is the default value";

So neither the left or right hand sides need a delegate, which is considerably cheaper than the Match method.

@louthy
Copy link

louthy commented Jan 29, 2015

@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 Option<string>, which you then need to check if it is Some or None. Obviously not on the very next line, but if you pass that Option type around then you have the same problems of people dereferencing without checking (because Value isn't protected).

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<T> to wrap the right-hand-side of the ==, so you still have the allocation. You could do this:

    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.

@forki
Copy link

forki commented Jan 29, 2015

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.

@louthy
Copy link

louthy commented Jan 29, 2015

@forki There's a number of not friendly aspects to that:

  1. Including FSharp.Core.dll -- not the end of the world, but for anyone working solely in C#, most people don't want to include another language's core library
  2. It's verbose and again the language prefix on the name is ugly:
    var x = FSharpOption<int>.Some(123)
    var y = FSharpOption<int>.None`
  1. There's no real API to it, nothing to support matching, or for use in LINQ, no implicit type conversions, etc.
  2. There's no safety to it. It doesn't prevent access to Value. That's not really a problem in F#, where the inertia is pretty much always to use match, but that inertia isn't there in C#, so the core benefit of using an option type isn't there, you may as well just return null for None.
  3. FSharpOption<int>.None is null, so to check if the value is Some you must call: FSharpOption<T>.get_IsSome(option) gaah.

The benefits I see to using the FSharpOption<T> in C# are:

  1. Interop - just works
  2. It's declarative, so coders consuming methods that return an FSharpOption<T> can tell that they may not get a value back.

Overall I think currently there are too many negatives.

@naasking
Copy link

@louthy You are correct that the return type is not string, I remembered this belatedly but the difference is minimal:

string value = (GetCacheValue(key) || "this is the default value").Value;

I've waffled on specifically what the semantics of | should be, because it's trivial to provide the other semantics too:

public static T operator |(Option<T> left, T right)
{
    return left ? left.Value : right;
}

However, this doesn't permit chaining option computations because operators can't take generic parameters, ie.

var x = TryGetFoo() || TryGetBar() || "default";

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).

@nydehi
Copy link

nydehi commented Jan 29, 2015

For years c# devs referenced that VB dll
https://msdn.microsoft.com/en-us/library/ms173136.aspx

@forki There's a number of not friendly aspects to that:

Including FSharp.Core.dll -- not the end of the world, but for anyone working solely in C#, most >>people don't want to include another language's core library

@forki
Copy link

forki commented Jan 29, 2015

@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.

@louthy
Copy link

louthy commented Jan 29, 2015

@naasking - Thanks for the clarification :)

any truly safe patterns in C# either discourage the use of options because they're non-idiomatic or scale poorly (like Match)

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 TryGetValue approach, but that's just personal preference, I think that out is an awful hack for what should have been proper tuple support), C# is clearly becoming more functional, so I think it's time we accepted some of the functional idioms. I think match deserves to be a language feature, and I hope that the new C# 7 feature list focusses on this. That would obviously affect any implementation of Option<T> and may even facilitate the direct usage of FSharpOption<T> (renamed)

@forki - Yeah I understand, but I don't see how this is an accurate statement:

Please don't reinvent. Please use the option type that's already in fsharp.core. It works like a charm

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.

@naasking
Copy link

@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:

var array = new int[] { ... };
var foo = DoSomething(out array[2]);

I've never seen this in pattern matching languages:

var array = new int[] { ... };
(var foo, array[2]) = DoSomething();

I actually wish ref/out were more general, as they are in Ada, because pointers are pretty powerful.

@louthy
Copy link

louthy commented Jan 29, 2015

"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.

Agreed.

Pattern matching is necessarily closed under extensions, but classes are open, so accomodating this tension isn't trivial.

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!

Re: out parameters,

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 out is a perception of equation imbalance. It just 'feels' wrong.

@Richiban
Copy link

Richiban commented Feb 5, 2015

@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 Option<T> should also implement IEnumerable<T>, or even ICollection<T>. Think about it, isn't Option<T> merely a 'collection' that contains either 0 or 1 elements of type T?

@Eyas
Copy link

Eyas commented Feb 5, 2015

@Richiban: definitely agreed about implementing ICollection<T>. As an aside, that would be another (minor) reason why I wouldn't want Option to be a struct-- the cost of boxing/unboxing of the struct every time we call a LINQ extension method on it.

I'm still not convinced by Match, though. I think anything the API does here would be un-ideal, and given the fact that the C# team is discussing pattern matching for C# 7.0 (dotnet/roslyn#98), I feel like anything done here would cause unnecessary confusion.

Option<T> itself is good. Pattern matching is great, but we can do that the right way by waiting for language support for pattern matching to come to fruition.

@naasking
Copy link

naasking commented Feb 5, 2015

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.

@louthy
Copy link

louthy commented Feb 5, 2015

Implementing IEnumerable means it can be used in LINQ expressions with
other IEnumerables, and it can be used as a 'switch' to turn subsequent
'from' clauses on and off. That means for many tasks where you're
processing lists, you don't need to use match, or if/then/else.

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:

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.


Reply to this email directly or view it on GitHub
https://github.com/dotnet/corefx/issues/538#issuecomment-73051321.

@naasking
Copy link

naasking commented Feb 5, 2015

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.

@Richiban
Copy link

Richiban commented Feb 5, 2015

@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

let maybeName =
    maybe {
        let! b = a.b
        let! c = b.c
        return! c.name
    }

If Option implements IEnumerable then in C# you can write:

var maybeName =
    from b in a.B
    from c in b.C
    select c.Name;

It gives you nice feature parity between the two languages, I feel.

@svick
Copy link
Contributor

svick commented Feb 5, 2015

@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.

@louthy
Copy link

louthy commented Feb 5, 2015

@svick - null is a dangerous property for a variable to have. It says it honours the contract of MyType, but when you go to use it your application throws an exception. The reason for Option is to never present the MyType contract unless you've confirmed (through matching) that it has a value. There is no explicit declaration in a nullable variable that it may be null, and that lack of declarative intent leads to bugs.

@naasking

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.

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 Request.QueryString[x] in an Option<T>, and then making the output of a request (a list) dependant on a property being provided in the request.

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.

I do that on Language-Ext for completeness, but I don't think making Option<T> implement IEnumerable<T> is non-idomatic. If it fully implements the IEnumerable<T> interface, and it performs as expected (a sequence of values, in this case a sequence of 0 or 1 value), then what's the problem?

@svick
Copy link
Contributor

svick commented Feb 5, 2015

@louthy I was not arguing against Option, I was arguing against using LINQ syntax for it, when a better syntax is already available.

@naasking
Copy link

naasking commented Feb 5, 2015

@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:

var results = from z in Request.QueryString[x].ToOption()
              from y in 0.UpTo(int.Parse(z))
              select y;

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.

@louthy
Copy link

louthy commented Feb 5, 2015

@naasking
As with earlier, your examples don't stack up. That wouldn't compile unless Option<T> is also an IEnumerable<T> You can't have an Option and an IEnumerable in subsequent from clauses. That's why Option<T> implementing IEnumerable<T> is so useful!

@eatdrinksleepcode
Copy link

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.

On Feb 5, 2015, at 3:24 PM, Paul Louth notifications@github.com wrote:

@naasking
As with earlier, your examples don't stack up. That wouldn't compile unless Option is also an IEnumerable You can't have an Option and an IEnumerable in subsequent from clauses. That's why Option implementing IEnumerable is so useful!


Reply to this email directly or view it on GitHub.

@naasking
Copy link

naasking commented Feb 5, 2015

@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.

@AdamSpeight2008
Copy link
Contributor

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

@terrajobst
Copy link
Member

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?

@RichiCoder1
Copy link

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#.

@AlexGhiondea
Copy link
Contributor

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.

Olafski referenced this issue in Olafski/corefx Jun 15, 2017
* 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
@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 2.0.0 milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Jan 7, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Runtime
Projects
None yet
Development

No branches or pull requests