Runtime: Option<T>/Maybe<T> and Either<TLeft, TRight>

Created on 10 Apr 2017  ·  62Comments  ·  Source: dotnet/runtime

public Option<Employee> GetById(string id)
    => new DbContext().Find(id);

instead of

public Employee GetById(string id)
    => new DbContext().Find(id);

The proposed method signature reveals much better the real intent of this method/function. Right?

Option<T> could provide a Match function....

Here is a very basic implementation:

namespace ElemarJR.FunctionalCSharp
{
    using static Helpers;

    public struct Option<T>
    {
        internal T Value { get; }
        public bool IsSome { get; }
        public bool IsNone => !IsSome;

        internal Option(T value, bool isSome)
        {
            Value = value;
            IsSome = isSome;
        }

        public TR Match<TR>(Func<T, TR> some, Func<TR> none)
            => IsSome ? some(Value) : none();

        public Unit Match(Action<T> some, Action none)
            => Match(ToFunc(some), ToFunc(none));

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

        public static implicit operator Option<T>(T value)
            => Some(value);

        public static implicit operator Option<T>(NoneType _)
            => None;
    }
}

with some additional operators:

namespace ElemarJR.FunctionalCSharp
{
    using static Helpers;

    public static class Option
    {
        #region Of
        public static Option<T> Of<T>(T value)
            => new Option<T>(value, value != null);
        #endregion

        #region Apply
        public static Option<TResult> Apply<T, TResult>
            (this Option<Func<T, TResult>> @this, Option<T> arg)
            => @this.Bind(f => arg.Map(f));

        public static Option<Func<TB, TResult>> Apply<TA, TB, TResult>
             (this Option<Func<TA, TB, TResult>> @this, Option<TA> arg)
             => Apply(@this.Map(Helpers.Curry), arg);
        #endregion

        #region Map
        public static Option<TR> Map<T, TR>(
            this Option<T> @this,
            Func<T, TR> mapfunc
            ) =>
                @this.IsSome
                    ? Some(mapfunc(@this.Value))
                    : None;

        public static Option<Func<TB, TResult>> Map<TA, TB, TResult>(
            this Option<TA> @this,
            Func<TA, TB, TResult> func
        ) => @this.Map(func.Curry());
        #endregion

        #region Bind
        public static Option<TR> Bind<T, TR>(
                this Option<T> @this,
                Func<T, Option<TR>> bindfunc
            ) =>
            @this.IsSome
                ? bindfunc(@this.Value)
                : None;
        #endregion

        #region GetOrElse
        public static T GetOrElse<T>(
            this Option<T> @this,
            Func<T> fallback
            ) =>
                @this.Match(
                    some: value => value,
                    none: fallback
                    );

        public static T GetOrElse<T>(
            this Option<T> @this,
            T @else
            ) =>
                GetOrElse(@this, () => @else);
        #endregion

        #region OrElse
        public static Option<T> OrElse<T>(
            this Option<T> @this,
            Option<T> @else
        ) => @this.Match(
            some: _ => @this,
            none: () => @else
        );


        public static Option<T> OrElse<T>(
            this Option<T> @this,
            Func<Option<T>> fallback
        ) => @this.Match(
            some: _ => @this,
            none: fallback
        );
        #endregion
    }
}

and some LINQ support:

namespace System.Linq
{
    using static Helpers;
    public static partial class LinqExtensions
    {
        public static Option<TResult> Select<T, TResult>(
                this Option<T> @this,
                Func<T, TResult> func)
            => @this.Map(func);

        public static Option<TResult> SelectMany<T, TB, TResult>(
            this Option<T> @this,
            Func<T, Option<TB>> binder,
            Func<T, TB, TResult> projector
        ) => @this.Match(
            none: () => None,
            some: (t) => @this.Bind(binder).Match(
                none: () => None,
                some: (tb) => Some(projector(t, tb))
            )
        );

        public static Option<T> Where<T>(
            this Option<T> option,
            Func<T, bool> predicate
        ) => option.Match(
            none: () => None,
            some: o => predicate(o) ? option : None
        );
    }
}

This proposal does not incur any CLR changes.

Design Discussion api-needs-work area-System.Runtime

Most helpful comment

I don't think it makes sense to add Option to C#. If you want to make nullability explicit, what you need is to remove null, which is what non-nullable reference types (https://github.com/dotnet/csharplang/issues/36) are about. I don't see how renaming null to None helps.

To expand upon this some more:

For a reference type T, the possible values are:

  • null
  • an instance of T

For Option<T>, the possible values are:*

  • None
  • Some holding an instance of T

The two are effectively identical, so adding Option<T> does not bring much benefit. It makes the possibility of not having an instance of T more explicit, but since it does not remove null, it also adds confusion: if you see T, does it mean it's a "new-style T", so it shouldn't be null (but nobody is checking that) or is it an "old-style T", in which case null should be expected as a possible value?

And adding Either also does not make much sense to me. For better or worse, the error reporting mechanism in .Net is exceptions. Adding a second mechanism to do the same thing would require for it to bring significant benefits, which I don't see, especially not without other features, like not being able to ignore the return value of a method accidentally.


* If I'm reading the code right, your implementation of Option<T> also allows a third possibility: Some holding null. I'm going to assume that's not intended.

All 62 comments

FYI: Here's our API process
Could you please provide brief description/motivation of the API? What is the problem you're trying to solve? Why do you need API in BCL?

C# (and VB) are evolving and providing support for functional programming. Today we have pattern matching, better support for tuples and local functions. The BCL should provide some common functional programming containers. I suggested Maybe/Option, but we could have Either<TLeft, TRight> as well.

Today, I've been using my implementations (https://github.com/ElemarJR/ElemarJR.FunctionalCSharp), but
I really would love to have some BCL support for functional programming.

@ElemarJR I'm sorry, but I still don't get it. Can you please be more specific? Where exactly would it help? Which patterns does it enable?
Imagine you have to convince 100 C# developers who don't have the functional programming background you do. Pitch your idea to them - motivate them.

Consider the following method:

public Employee GetById(string id)
    => new DbContext().Find(id);

What result should be expected from this? An Employee instance, right? Yes, but not only this! The result of this method could be null if there are no records that satisfy the specified criteria or an exception (if it is no possible to connect with the database, for example)

Actually, the programmer who will consume this method will have no idea of what results he would get invoking it.

Much better would be:

public Either<Exception, Option<Employee>> GetById(string id)
{
  try
  {
     return (new DbContext().Find(id));
  }
  catch (Exception e)  {  return e;  }
}

Now, it's clear what results this method could provide. No more NullReferenceExceptions or unhandled exceptions.

Talking about patterns, we would be able to adopt a much more expressive (and declarative) coding style:

public Try<Exception, Unit> RaiseSalary(string id, decimal amount) => 
    _employeeRepository
        .GeyById(id)
        .IfNone(() => 
            new InvalidOperationException("There is no employee with the specified id.")
        )
        .Then(employee => employee.RaiseSalary(amount))
        .Then(employee => _employeeRepository.Save(employee));

Expressiveness and "NO-MORE-EXCEPTIONS"

.... Would they even be "Exceptions" anymore? You're turning them into error codes (not that I disagree that many exception cases should be switched to error codes or pre-emptive checks, especially in I/O cases).

@ElemarJR your latest examples of Try and Either are going above the original scope. Is that intentional?
Personally, I don't like such editions to C# -- it might be best to experiment with such things outside of CoreFX first and see if they gain momentum and interest from larger community. Would that make sense?

Your original Option<T> proposal looks like a spin on non-nullable reference types - see UserVoice issue. Or did I miss your point?

Overall it would help to stay focused on one thing and properly provide motivation.

This has been a common request in the past, see https://github.com/dotnet/corefx/issues/538, https://github.com/dotnet/corefx/issues/748, https://github.com/dotnet/corefx/issues/1291

Previously, we've said that this should be tied to new C# language features, in order for it to be usable / efficient. @jaredpar Are there any C# proposals covering this topic? Perhaps a pattern-matching-related feature?

I don't think it makes sense to add Option to C#. If you want to make nullability explicit, what you need is to remove null, which is what non-nullable reference types (https://github.com/dotnet/csharplang/issues/36) are about. I don't see how renaming null to None helps.

To expand upon this some more:

For a reference type T, the possible values are:

  • null
  • an instance of T

For Option<T>, the possible values are:*

  • None
  • Some holding an instance of T

The two are effectively identical, so adding Option<T> does not bring much benefit. It makes the possibility of not having an instance of T more explicit, but since it does not remove null, it also adds confusion: if you see T, does it mean it's a "new-style T", so it shouldn't be null (but nobody is checking that) or is it an "old-style T", in which case null should be expected as a possible value?

And adding Either also does not make much sense to me. For better or worse, the error reporting mechanism in .Net is exceptions. Adding a second mechanism to do the same thing would require for it to bring significant benefits, which I don't see, especially not without other features, like not being able to ignore the return value of a method accidentally.


* If I'm reading the code right, your implementation of Option<T> also allows a third possibility: Some holding null. I'm going to assume that's not intended.

@mellinoe yes. C# is actively working on a non-null proptotype feature. It's still in the early phases so don't expect concecrete feedback anytime soon. But it is an idea we are persuing.

@Clockwork-Muse Not simple error codes. If you use Either/Try you would force the "dev client" to handle it. I like to think Exceptions as result value from failed executions. The idea is to avoid dangerous side-effects.

@karelz It's not simple non-nullable objects. There are contexts where you want to return None indicating you have no value related. The Maybe meta-type is important because it indicates that the result could be None (It's different of null that is implicit possible all the time). About the momentum, Functional Programming and Reactive Programming are two very hot topics. F#, as a (almost) functional language already provides it.

@mellinoe I first created an issue in the csharlang repo. They recommended me to create an issue here.

@svick None is not a "renamed null". There are very different use cases and semantic implications. Today, there is no way to explicitly indicate that a function/method could return no value - so, we are forced to follow a defensive way every time.

About Either it is not only for errors. It could be applied to any function that could return two different result types . Exceptions are a very expensive way to indicate that something went wrong. Don't you think?

Finally, I have a separated assembly for my high-level/functional types. It's not possible to create a Some(null).

In the end of the day, I just want to write more resilient code. I've been adopting these types for years with very good results.

@ElemarJR thanks for the info. It gives me some high-level idea what you're trying to achieve. I think it is an interesting (and bold) idea.

Regarding momentum - I was not talking about functional languages (e.g. F#) momentum, but FunctionalCSharp momentum. Given that you work on these for years now, maybe it would be best to create and release your library as separate NuGet package and see how popular it becomes.
Feel free to use this issue to flush out basic idea / direction.

For context, here is our overall bar for new CoreFX repo APIs:

  1. useful for implementing CoreFX itself (e.g. PriorityQueue - dotnet/runtime#14032), or
  2. natural extensions of APIs already present in CoreFX (adding methods, similar types), or
  3. types ported from .NET Framework (although not all would make the cut and some might be chased away into separate repos), or
  4. deeply thought-trough, and ideally proven, API which is truly 'core' (e.g. Span<T> & friends experiments in CoreFXLab repo)

Example of types we believe do not belong into CoreFX repo are e.g. PowerCollections - while useful, we think they can be shipped in separate package from separate repo (something like CoreFXExtensions - plans are not finalized yet).
Examples of some interesting controversial/border-line cases: SemanticVersion, Disposables, nearly-sorted sorting algo

If C# get this feature what would be the propose of an exception?

For anyone curious about how types such as Option<T>, Either<L, R> and others work I recommend checking out @louthy's excellent LanguageExt package (https://github.com/louthy/language-ext, https://www.nuget.org/packages/LanguageExt.Core/). It's mature and feature-rich; we've been using it in production for some time now. I love the explicitness that Optional<T> gives you in an API:

public Optional<Person> GetById(PersonId personId);

It's super clear from the signature alone what happens if the person with that ID is not found. It also prevents exceptions because Option<Person> is _not_ the same type as Person (as is the case with null):

var person = GetById(personId);

Console.WriteLine(person.Name); // Error: Option<Person> doesn't have the member "Name"
match(GetById(personId),
    Some: person => Console.WriteLine(person.Name), // This is safe because the wrapped Person instance is guaranteed non-null
    None: () => HandleMissingPerson() // I *must* explicitly handle the case where the Person was not found
)

While @louthy's library is great and all, I really do feel that something like this should be baked into the language. Maybe it's something that will, one day, be superseded by nullable reference types being baked into the language but I think we're a long way away from that, to be honest.

For anyone curious about how types such as Option, Either and others work I recommend checking out @louthy's excellent LanguageExt package (https://github.com/louthy/language-ext, https://www.nuget.org/packages/LanguageExt.Core/). It's mature and feature-rich; we've been using it in production for some time now.

@Richiban Hi Richard, thanks for the kind words :)

One comment I'd make on the usefulness of Option in the BCL is that when I created my first functional project (csharp-monad) which was just simple monadic types like Option Either and nothing else; I found it to be much less useful. Not totally useless, but I find the real power of Option (and similar ADTs) to really come to life when there's a framework that is bought into the idea. That's why my language-ext project is more than just the monadic types, it's trying to be a functional BCL.

For example gettting a value from a Map (dictionary):
c# var result = map.Find(thing).Match( Some: x => UseValue(x), None: () => DefaultValue );

So I think unless the BCL is going to retrofit Option everywhere that currently returns null then it's of limited use. Because some parts of the BCL will be 'lying' (return null), some will be telling the truth (have Option in their signatures). If no parts of the BCL uses the Option type, then it's probably best left to third party libraries like mine. My gut feeling is that the BCL is too big of a ship to turn here.

@ElemarJR

None is not a "renamed null". There are very different use cases and semantic implications.

Your very first example suggests to use None instead of null. How does that make the use cases and semantic implications different? Could you explain what does that actually mean?

Today, there is no way to explicitly indicate that a function/method could return no value - so, we are forced to follow a defensive way every time.

Yes, but my point is that adding Option<T> doesn't really help. You add a way to indicate that None is a valid value, but you still have no way to indicate that null is not a valid value. So if you have a method that returns Employee, you still don't know if null is valid. And even if you change all your code, you still have to work with other code (including old code), which won't use Option.

And even if all code you use is changed, then a method that returns Employee can still return null, because of a bug, because there is nothing checking that it doesn't.

Exceptions are a very expensive way to indicate that something went wrong. Don't you think?

That depends. Sometimes exceptions are indeed inappropriate, which is why .Net also has the bool Try*(out T) pattern. I don't see how adding a third pattern to the mix helps much.

I think this proposal is a real "nice-to-have", In the past years C# brought to us some really nice FP features (eg. whole LINQ,) which is a differential against other languages. C# is walking to a multi-paradigm approach (as stated in Developer on Fire ep. 224 with Mads Torgensen) and I guess those suggestions are welcome.

@svick

Answering your questions here would be too long. I just wrote two blog posts about my point about exceptions and nulls.

I looked for Option / Maybe on the second day of trying out C#. It's a natural complement to the various LINQ features I'm now exploring.

What I'm sensing is that C# is incorporating useful functional programming features, but there might be a gap in the holistic understanding of FP.

With an Option, you can for example use it functor fashion, e.g. in pseudocode:

Option<Street> maybeStreet = maybePerson.Select(Address).Select(Street)

You don't need to check whether the maybePerson actually had Some person or None in there - you just chain away, and in the case of Option, the None is propagated. No person, address or street? You got no street.

Something is going on that's similar in some ways to internal iteration. With external iteration you have a loop, an index, and a collection to iterate over (by indexing) say. Internal iteration offers you a way to provide a function to apply to each element, and the mechanics of iteration are brought inside the type system. Here optionality is brought inside, and you don't need to be keeping track of whether you have something or not. These are examples of Functor and friends.

In Haskell, my ghci is reporting 10 typeclass instances for Maybe. There are lots of ways in which would you wish a computation to proceed when given a type that potentially contains no value. What about a basic equality check?: null.Equals("bad"). Meh, null simply causes your program to fall over; it's a programming dead-end.

With respect to the ongoing non-nullable proposal, I really like the idea of preventing nulls (by default!). But I don't see that the proposal will be giving null the capacity to act like a real type / value.

Either can be used for error handing, but actually it is a canonical sum type (as in abstract data types). It's useful when something can be one thing or another, and you could use it to build all the other sum types.

@fish-ter

With an Option, you can for example use it functor fashion, e.g. in pseudocode:

c# Option<Street> maybeStreet = maybePerson.Select(Address).Select(Street)

Real code would have to use lambdas, which would make it even more verbose and less readable. Also, to be consistent with other monads, it should be SelectMany, not Select. But with null, you can do:

c# Street? nullableStreet = nullablePerson?.Address?.Street;

Which I think is much better than even your pseudocode.

(Okay, you can't write Street? right now, since it's most likely not a value type, but hopefully you will in C# 8. Otherwise, that code already works.)

Hi @svick, SelectManylooks right, thanks.

And what you said about lambdas was interesting. I see now that an Address getter function would have to be defined outside of Person for a function reference to work. Know of any plans to add support for class methods and the like?

Java8 is quite flexible in that regard (though composing the plethora of function types in Java can be "interesting").

Your null safe code looks a sound improvement in practical terms, but it does leak the null concept, as per my "internal" vs "external" point. My pseudocode example needed to join Options (implicitly fixing it as the monadic type), but generally I'd prefer to operate via common abstractions like IEnumerable<T>.

The null safe code looked like adding Swifty terseness to the checks, and I'll include some info here:

 The Swift language defines the postfix ? as syntactic sugar for the named type Optional<Wrapped>, which is defined in the Swift standard library. In other words, the following two declarations are equivalent:

    var optionalInteger: Int?
    var optionalInteger: Optional<Int>

In both cases, the variable optionalInteger is declared to have the type of an optional integer. Note that no whitespace may appear between the type and the ?.

The type Optional<Wrapped> is an enumeration with two cases, none and some(Wrapped), which are used to represent values that may or may not be present. Any type can be explicitly declared to be (or implicitly converted to) an optional type. If you don’t provide an initial value when you declare an optional variable or property, its value automatically defaults to nil.

If an instance of an optional type contains a value, you can access that value using the postfix operator !, as shown below:

    optionalInteger = 42
    optionalInteger! // 42

Using the ! operator to unwrap an optional that has a value of nil results in a runtime error.

You can also use optional chaining and optional binding to conditionally perform an operation on an optional expression. If the value is nil, no operation is performed and therefore no runtime error is produced. 

What are your thoughts on that approach? I'm not aware of the discussions that led them to it, but I can imagine similar rumblings went on.

@svick

Real code would have to use lambdas, which would make it even more verbose and less readable.

It depends on what coding style you follow. Right?

  • You apparently prefer the "imperative style" - with null, exceptions and side-effects. I am inclined to follow the "functional style" - I like to have functions with clearly defined inputs and outputs.
  • You are ok to write code writing sequences of statements. I prefer to have nested functions calls
  • You are ok using if to check if some argument is valid (not null, for example) - I prefer to use the type system instead.

It is important to say that the improvements I am asking for are already available in F# - which is a great language. Anyway, I love C# and I would like to continue writing code using it.

@fish-ter

I see now that an Address getter function would have to be defined outside of Person for a function reference to work. Know of any plans to add support for class methods and the like?

Yes. It would probably require using static and would be very unusual coding style for C#.

I'm not sure what you mean by "class methods", how would they be different from static methods?

@ElemarJR

C# started as an imperative language. It's never going to be a functional-first language, like F#, and I don't think it makes sense to try to move it in that direction. Adding functional features to C# can be very useful, but it should be done carefully, to stay consistent with the rest of C#. If you want to program in a functional style, I think you should use F# instead of trying to twist C# to make it as functional as possible.

You apparently prefer the "imperative style" - with null, exceptions and side-effects.

null, exceptions and side-effects are how programming in C# is done, you can't get away from them. I don't think adding alternative systems (Option, Either) makes much sense, because you end up with a language that sometimes uses one way, sometimes the other, resulting in an inconsistent mess.

Instead, I think the existing systems should be improved, which is why I think non-nullable reference types are the way to go. I also wouldn't be opposed to well-designed checked exceptions (assuming such a thing is possible).

You are ok to write code writing sequences of statements. I prefer to have nested functions calls.

Sequence of statements is one of the most basic syntactic constructs in C#. Yes, I do think using them is okay. Even in functional languages, let is used very often, which makes you code look like a sequence of assignments.

You are ok using if to check if some argument is valid (not null, for example) - I prefer to use the type system instead.

You got this one wrong. I prefer to use the type system, when it makes sense. But I want the type system to actually solve the issue. Which is why I want non-nullable reference types and why I think Option is not a good solution. With Option, you're pretending the issue is solved, that null does not exist anymore, only None does.

But null still exists, a method that returns Person can still return null and you have to way to tell whether it does or not. Which is why I don't consider Option to be a good solution for C#.

@svick

I think that the best attribute of a good programming language would be to allow to express ideas in a clean and elegant way.

Even with non-nullable types, I would need to communicate "None" sometimes - I just want to do that being safe and expressive. That is what Option/Maybe allows me to do.

Having side-effects is an API design choice - not a language imposition. I want to be able to create side-effects free APIs - I've been doing that for years with very good results - and I think it would be amazing to make it easier for everyone.

@ElemarJR

Even with non-nullable types, I would need to communicate "None" sometimes

Why? If you had the choice of using non-nullable T and nullable T?, why would you choose Option<T> instead? What makes None different from null?

Having side-effects is an API design choice - not a language imposition. I want to be able to create side-effects free APIs - I've been doing that for years with very good results - and I think it would be amazing to make it easier for everyone.

While throwing exceptions is considered a side-effect, I think you can get most of the advantages of side-effect-free programming even with exceptions. For example, consider the Roslyn API, which avoids mutating state, but still throws exceptions.

@svick

Yes. It would probably require using static and would be very unusual coding style for C#.

I'm not sure what you mean by "class methods", how would they be different from static methods?

The following looks good to me:

c# new List<string> {"this", "works"}.ForEach(Console.WriteLine);

And it seems completely reasonable to wish for something like Select(Person.GimmeTheName), with GimmeTheName being a method defined in the class Person. But I'd like to drop that and focus on the Option issue.

@svick & @ElemarJR

What are your thoughts on the Swift approach? Why shouldn't nullable types become Option<T>s under potential nullability enhancements?

Why? If you had the choice of using non-nullable T and nullable T?, why would you choose Option instead? What makes None different from null?

Right, so nullable T would be Option<T>, with None being null. We could both get the kind of interface we desire, and interoperate.

@svick,

Why? If you had the choice of using non-nullable T and nullable T?, why would you choose Option instead? What makes None different from null?

none is an absence of a value; null is an uninitialised reference, thus has a value. none is non-zero and non-null because both of those are values. One can "hack" systems to behave like option monads, such that null (or 0) behave like none, but as the XXXOrDefault methods in linq show, this means overloading 0/null with two meanings: an absence of a value and the default value:

var x = (new List<int> {0}).FirstOrDefault();
var y = (new List()).FirstOrDefault();
// x == y; we have information loss

Whereas, if one uses Option<T>, then this becomes:

var x = (new List<int> {0}).TryDefault();
var y = (new List()).TryDefault();
// x is 0, y is none; information loss avoided.

@svick,

null, exceptions and side-effects are how programming in C# is done, you can't get away from them

Um, yes you can. I write C# code, yet those features live on the periphery for me.

If you want to program in a functional style, I think you should use F# instead of trying to twist C# to make it as functional as possible.

Why? Is this because you don't want to have to learn functional techniques, or is there a real reason for this? Why should I have to learn a new language just to use modern programming techniques? Why should Luddites stop my preferred language keeping up with my advancements in programming? C# already has plenty of functional features. I see no reason, to not add lots more.

But ultimately, this discussion is weird. It's on the corefx repo; not the C# one. Given that F# is a .NET language that has had to add its own Option<T> type (many years ago), beyond a lack of proper CLR support for discriminated unions, I see no reason whatsoever why Option<T> isn't baked into the core types. Talk of what C# devs want should be of no real consequence here.

@svick

Why? If you had the choice of using non-nullable T and nullable T?, why would you choose Option instead? What makes None different from null?

None has no contract. It doesn't honour anything. null of T claims to honour the contract of T, yet it's a lie. It's a massive hole in the type-system, and that's why null is considered the 'billion dollar mistake'. You can't dereference None, and therefore it's safer. You have to pattern-match to either extract a concrete value of T that will do what its contract says, or None which you can do nothing with. That's its value.

I still agree that it probably doesn't belong in the BCL. If Microsoft decided to develop a functional BCL (which I'd be all for, even if it would wipe out a few years of my work on language-ext), then it would definitely have a home there. I think the BCL in general is 'OO land', and it would be difficult to retrofit functional APIs onto it.

@louthy,

I still agree that it probably doesn't belong in the BCL. If Microsoft decided to develop a functional BCL (which I'd be all for, even if it would wipe out a few years of my work on language-ext), then it would definitely have a home there. I think the BCL in general is 'OO land', and it would be difficult to retrofit functional APIs onto it.

I don't agree here. I have put a lot of effort into Succinc<T>, only to recently discover that I'd duplicated a whole load of stuff that you'd already put into language-ext. And in both cases, we are duplicating work already done for F#. All of this duplication exists because core functional features, like Option<T> and option-returning TryXXX features aren't baked into the BCL.

It's really depressing to read comments from the likes of @karelz, that we must imagine we "have to convince 100 C# developers who don't have the functional programming background...". How about those 100 C# devs make the effort to learn about functional programming and then offer coherent arguments against a feature, to prevent its incorporation into the BCL? That is the way to aim the BCL at the future...

@DavidArno I do see your point, and mostly agree, but I don't really agree on the approach. I think the BCL being both a functional and OO repository is a recipe for disaster. One strength of the BCL (for all its OO-ness) is that it's consistent, there are pretty much never any surprises. The biggest surprises come from the functional types that are already in there (IEnumerable, IObservable, etc.)

If Microsoft created SystemF (pun intended) as a namespace for the functional world that'd be great, because then we could have functional IO, net, web, etc. Or Microsoft could sanction one of the functional frameworks. I was asked recently if I'd like language-ext to join the .NET Foundation, which I need to chase up at some point. Maybe that will create more traction.

I definitely don't have all the answers here, but I would be concerned about a bastardised BCL becoming less consistent and therefore a less effective core.

@louthy,

Maybe we are arguing at cross-purposes then, as SystemF... would serve nicely. Those old out p and XXXOrDefault methods could be left in place and a separate set of methods/functions could be created in new namespaces that behave in a more "modern" way. This would keep the two worlds separate, keeping both parties happy, but avoiding the need for "function C# folk" to re-invent the F# wheel.

IEnumerable and the C# support for enumerations is one of the best features
from dotnet.

I can't see any reason why BCL could not support more than a programming
style.

Em ter, 18 de abr de 2017 às 18:54, Paul Louth notifications@github.com
escreveu:

@DavidArno https://github.com/DavidArno I do see your point, and mostly
agree, but I don't really agree on the approach. I think the BCL being both
a functional and OO repository is a recipe for disaster. One strength of
the BCL (for all its OO-ness) is that it's consistent, there are pretty
much never any surprises. The biggest surprises come from the functional
types that are already in there (IEnumerable, IObservable, etc.)

If Microsoft created SystemF (pun intended) as a namespace for the
functional world that'd be great, because then we could have functional IO,
net, web, etc. Or Microsoft could sanction one of the functional
frameworks. I was asked recently if I'd like language-ext to join the .NET
Foundation, which I need to chase up at some point. Maybe that will create
more traction.

I definitely don't have all the answers here, but I would be concerned
about a bastardised BCL becoming less consistent and therefore a less
effective core.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/dotnet/corefx/issues/18159#issuecomment-294995307,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGd8o_2Sd6L04vOcdn92v4V4HGbg6zrks5rxTEcgaJpZM4M45Es
.

@ElemarJR I'm not suggesting that IEnumerable (or more precisely the LINQ extensions) aren't amazing. They are. But the interface is 'surprising' if all you know is the rest of the BCL style.

@DavidArno I guess I'm trying to be as pragmatic as possible, because it would be easy to say "But it's only an Option type", however if it's done badly it could backfire on any drive for a functional framework for C#. If the rest of the BCL methods that might return null aren't retrofitted to return Option then there's not much of a win for the BCL as a whole. If there's going to be a SystemF namespace, then I'd expect a total rethink of the way the BCL is written to be more functional in style (more static 'module' like types, more purity, more immutable structures, etc. for IO, Web, Net, Text, Environment, etc.); it could just be a layer over System (which I've actually considered doing myself, but would be an enormous task for one person).

Things I could imagine that go wrong (with Option):

  • Implementation which gives access to the Value directly, removing the core purpose of trying to get C# programmers to take the 'happy path'.
  • A GetValueOrDefault that could return null
  • Lack of consideration for laziness
  • Lack of consideration for asynchronous optional computations
  • An overly JavaStyleNamingConventionForMethods removing the inertia for usage for people who want a more functional approach (like ImmutableCollections)
  • Perhaps if SystemF doesn't exist we'd end up with maintenance of the old null returning functions in the System namespace and new ones that return Option with an Option suffix everywhere (like we see the Async suffix everywhere).
  • Use of a reference type for Option
  • Allowing null in an Option (F# allows this, as do most other functional languages, but in lang-ext I explicitly denied it to remove null from all equations and created a OptionUnsafe to make the type as declarative as possible).
  • Option ends up in the System namespace, causing breakages for everyone who already use functional frameworks like yours and mine. There's no way on earth that at BCL version of Option will ever support even a fraction of the functionality that I've put into lang-ext. Obviously it's possible to use extension methods to get to that point, but it won't be as efficient.

I don't doubt that with the political will that all of these issues could be resolved. It does seem fraught with potential pitfalls, and that's just for one type.

@DavidArno

this means overloading 0/null with two meanings: an absence of a value and the default value:

Yes, using 0 like this is problematic, because it's also a valid value of type int. But null does not have that problem, it's not a normal value now. And it won't be a possible value of a non-nullable reference type T in the future. I don't understand how your FirstOrDefault example is in any way relevant here, 0 is not null.

null, exceptions and side-effects are how programming in C# is done, you can't get away from them

Um, yes you can. I write C# code, yet those features live on the periphery for me.

You can try, but without a whole new standard library (and non-standard libraries), you still have to deal with them.

If you want to program in a functional style, I think you should use F# instead of trying to twist C# to make it as functional as possible.

Why? Is this because you don't want to have to learn functional techniques, or is there a real reason for this?

Have you tried programming in an object-oriented and imperative style in F#? (Yes, I know you don't like OOP, this is just for the sake of comparison.) It is possible, but the language does not make it easy. It's because OOP is a secondary concern in F#, you're supposed to use functional approach, and mostly use OOP only for interop.

It's similar with C#, if you try to add all functional features to it, they're never going to be as nice as in a dedicated functional language. That's why I think if you want to program in a functional style, you should use a functional language. And if you hate object-oriented programming, maybe avoid languages that have OOP as their primary paradigm.

I am all for learning functional techniques and applying them where appropriate. But I prefer not to use them all the time, which is why my primary language is C# and not F#.

Why should I have to learn a new language just to use modern programming techniques?

Because what you consider "modern programming techniques" does not align with what I think most C# programmers consider to be "modern programming techniques".

Why do you think I should learn functional techniques, but you shouldn't have to learn F#, if you want to program in a style that is different from what C# programmers like?

Why should Luddites stop my preferred language keeping up with my advancements in programming?

I never said C# should stop advancing. But it seems what you consider to be "advancements in programming" means "ignore everything related to OOP, copy everything functional languages do". That is not a direction I think makes sense.

C# already has plenty of functional features. I see no reason, to not add lots more.

When it makes sense, sure. But it should not be done blindly, ignoring what C# already has. And C# already has a way of representing absence of value, there is no reason to add a new one with a different name, just because functional languages do it that way.

Talk of what C# devs want should be of no real consequence here.

C# is used by the vast majority of .Net developers, so of course what they want matters, even though it's not the only thing that matters.

F# differs a lot in what it wants from a standard library from other .Net languages, which is why it has its own (to a certain degree). So, moving Option<T> to corefx would need a better reason than just "it's in the F# standard library".

@louthy

None has no contract. It doesn't honour anything. null of T claims to honour the contract of T, yet it's a lie. It's a massive hole in the type-system, and that's why null is considered the 'billion dollar mistake'. You can't dereference None, and therefore it's safer. You have to pattern-match to either extract a concrete value of T that will do what its contract says, or None which you can do nothing with. That's it's value.

Except for the specifics of pattern matching, don't non-nullable reference types solve all those problems?

  • null won't be assignable to T? Check.
  • You can't dereference T?? Check.
  • You have to do a null-check to extract the value? Check.

@svick

Are you happy with NullReferenceExceptions? Did you understand that Option<T> could represent the end of this nightmare?

@ElemarJR

How is Option<T> going to change all the existing code and libraries? On the other hand, all of those can be updated with non-nullable reference types, ending the nightmare of NullReferenceExceptions in a much more friendly way.

Also, like I said many times before, Option<T> on its own does not prevent NullReferenceExceptions, since null is still valid for T, it just adds a convention that null shouldn't be used. I don't think that's good enough.

BTW, Java now has Option<T>, did that solve NullPointerExceptions?

So, what I understand: Option<T> would not end the nightmare, non-nullable reference types probably will.

@svick

We could have Some(null) but it would be strange.

I've been using Option for years and creating adapters when implementing my APIs using non-functional libraries. I have no more NullReferenceException occurrences.

That's my experience. I am not talking about features that I don't have today. :)

@Patrikkk has a very good library called FunctionalSharp that implements Maybe-Monads (Option Types), Discriminated Unions and Pattern Matching. I use it in most of my projects because it is very useful and you might want to check it out before coming up with a new implementation.

@svick,

So, what I understand: Option<T> would not end the nightmare, non-nullable reference types probably will.

Seriously? Have you seen how often folk use T? to enable their structs to be null? Whilst better coders will enjoy the benefits non-nullable ref types, the vast majority of devs will just spread even more T? throughout their code...

@svick,

Yes, using 0 like this is problematic, because it's also a valid value of type int. But null does not have that problem, it's not a normal value now. And it won't be a possible value of a non-nullable reference type T in the future. I don't understand how your FirstOrDefault example is in any way relevant here, 0 is not null.

Because, with non-nullable ref types, FirstOrDefault won't work ... because it returns null. So the return type will be T?. Use an Option<T> and T can remain null-free.

Um, yes you can. I write C# code, yet those features live on the periphery for me.

You can try, but without a whole new standard library (and non-standard libraries), you still have to deal with them.

Very rarely in my experience and wrapping robust, null-free code around those weak points isn't that hard.

Have you tried programming in an object-oriented and imperative style in F#? (Yes, I know you don't like OOP, this is just for the sake of comparison.) It is possible, but the language does not make it easy.

I disagree. The syntax is as weird as anything else in F# (for folk like me who have spent too many decades using C-style languages) and the explicit interface-only rule catches me out. Aside from that, it's as usable for OOP as C#.

It's similar with C#, if you try to add all functional features to it, they're never going to be as nice as in a dedicated functional language.

Only time will tell. Of course, only by adding such features will time be able to tell. Since F# isn't a dedicated functional language (it's a multiple paradigm language with strong FP features), we could use it as a model for judging C#'s functional success.

Why should I have to learn a new language just to use modern programming techniques?

Because what you consider "modern programming techniques" does not align with what I think most C# programmers consider to be "modern programming techniques".

Since when did mob rule govern programming language evolution?

I never said C# should stop advancing. But it seems what you consider to be "advancements in programming" means "ignore everything related to OOP, copy everything functional languages do". That is not a direction I think makes sense.

C# is pretty much done as far as OO is concerned (save for copying Java's "default methods in interfaces"). And obviously, "advancement" means moving forward. Implementing failed ideas isn't advancement.

When it makes sense, sure. But it should not be done blindly, ignoring what C# already has. And C# already has a way of representing absence of value, there is no reason to add a new one with a different name, just because functional languages do it that way.

You are right "just because functional languages do it that way" would not be a reason. "Do it for the same reasons that functional languages do it that way" is a very sensible reason for doing something though.

C# is used by the vast majority of .Net developers, so of course what they want matters, even though it's not the only thing that matters.

Nope. What they will need, when they learn new techniques and advance their skills, is what matters. Language advancements lead developers into better practices. What they are asking for is often not what they need though. Just giving developers what they asked for is selling them short. Language development is no different to any other software project in that regard.

F# differs a lot in what it wants from a standard library from other .Net languages, which is why it has its own (to a certain degree). So, moving Option to corefx would need a better reason than just "it's in the F# standard library".

"It's not in the corefx library" is all the reason needed as it's of little to use to me, as someone who mainly uses C#, being tucked away in an F# library.

the vast majority of devs will just spread even more T? throughout their code...

Well, joke's on them, because if they do the compiler will warn those developers every single time they want to dereference them without a check and they'll only have themselves to blame.

Also, I seriously doubt that devs that would use T? so irresponsibly are in the top percent of NuGet packages used, so the likelyhood of pulling in an API designed like that is almost non-existent.

  • I agree with @svick completely. What he wrote here and here should be enough to end this discussion.
  • I absolutely love the concept, but am totally against adding it through a class like you propose it. The only reasonable option for this to really succeed in a language like C# is via the introduction of non-nullable reference types (what @karelz mentioned in the beginning).

Also agree with @svick.

I've used Deedle, a data frame library, extensively in C#. It depends on FSharp.Core and thus relies heavily on an Option<T> type.

The Option<T> type is unbelievable annoying to work with. While in C#, we have always had nullable value types (ex. int?), Deedle tries to express a lack of value using the F# Option type. For doubles, this leads to essentially three different, incompatible ways that lack of a value is expressed- null, NaN, and !Option.HasValue.

Adding Option into an existing ecosystem which already supports null means tons of extra boilerplate to check for both. It means more bugs, not less. It means more developer confusion, not less. It is a bad idea.

For doubles, this leads to essentially three different, incompatible ways that lack of a value is expressed- null, NaN, and !Option.HasValue.

Only three? You are forgetting the bool Foo(out double value) pattern, and having (bool valid, double value) tuples as ways of expressing whether the double has a value or not. So that's at least five...

Deedle tries to express a lack of value using the F# Option type

Since Deedle is an F# library it clearly succeeds in using options. The trouble comes when trying to use them with C#. As the latter doesn't yet have discriminated unions and has very poor pattern matching support, union types, like Option<T>, can be awkward to use in that language.

I am also looking forward to having Maybe monad in C#. I just want to do away with the nulls. I feel like half of the time I write code I spend worrying about null. The damn thing jut pops out of nowhere.

Anyway I found this blog post also explaining Maybe monad uses quite nicely. The ability to compose functions are really nice.

PS: BTW I just spent an hour trying to figure out where did null cropped in when I was doing some huge xml parsing. So apologies for rambling here.

How is the proposal affected by Nullable reference types in C# feature?

@karelz,

I'd say "quite heavily". If the feature works well, then T? can be used as a Maybe<T> in most cases. The only limitation been that eg:

string? s = null;
s.ToString();

would result in a NRE, rather than null. But 90% of a maybe type functionality is usable in 90% of cases...

How is the proposal affected by Nullable reference types in C# feature?

The proposal above adresses a bit different problem a bit different way. It helps you to deal with existing nulls and avoids breaking API changes.

What we as developers need is to explicitly say the function may or may not return result.
My todays case:
There is func that takes some input and runs several validation functions
The it wants to aggregate all errors is there are and return them upper on the stack
Every validation func may or may not return error
So, some pseudo code

Task> async ValidateOrder(Order order)
{
var errors = new List();

 var r = await ValidateCreditCard(order);
 if (r != null) {errors.Add(r);}

 var r = await ValidateProductAvailable(order);
 if (r != null) {errors.Add(r);}

}

This is how usually null is used to indicate there is no result and that is scary and ugly.
And no, I can't use yield.
Ideally I want to be able to do just the following
Task> async ValidateOrder(Order order)
{
var errors = new List();
var r = errors.Add(await ValidateCreditCard(order));
var r = errors.Add(await ValidateProductAvailable(order));
}

Where ValidateCreditCard and ValidateProductAvailable return Maybe and all collections have built in support for the Maybe so if there is result it gets added and no if no.

@yahorsi

What we as developers need is to explicitly say the function may or may not return result.

How is that different from nullable reference types? Those also let you explicitly say whether a function will always return result, or only sometimes.

Regarding your code:

  1. That's not how APIs that use Maybe<T> are usually built. For example, 'T list in F# does not have a function for appending 'T option, even though there are some functions in the List module that use option.
  2. What's preventing you from adding an extension method like AddIfNotNull that adds the value to the list if it's not null? And with C# 8.0, using the regular Add would produce a warning, which should steer people towards AddIfNotNull, if you had it in your library.
  1. That's not how APIs that use Maybe<T> are usually built

That is exactly how usually such API is built. E.g. in scala: List(1,2,3) ++ None ++ Some(4) ++ Some(5) ++ None
And it must work this way, otherwise the whole idea is almost useless.
By having Maybe/Option we want to avoid If's at all where possible, and that's the goal.

  1. or example, 'T list in F# does not have a function for appending 'T option, even though there are some functions in the List module that use option.

F# is far from being the best functional lang to take examples from as it shares BCL with .NET and BCL is not designed for this kind of functional API's

2. What's preventing you from adding an extension method like AddIfNotNull that adds the value to the list if it's not null?

Nothing stops me from doing anything myself ;) What we really want is standard API that can be used and consumed by everybody. My

There is an excellent course on PluralSight that explains the benefits of applying functional principles in C# named, aptly, "Applying functional principles in C#". The gist is that a method signature should be honest. If you return T and sometimes return null, you're lying to those that would call your method. Return Option on the other hand and your signature is honest and explicit about what it may return.

You could perhaps remedy this with a new language feature (non-nullables) -- expanding the surface area of what devs need to learn/know, BTW -- but why wouldn't you just solve it using an existing language feature: the type system? Especially since you could solve some other problems of dishonest method signatures (e.g., exceptions) the exact same way.

Option was added to Java 5+ years ago; what's taking C# so long to catch on? I get the sense that the C# community has a lack of reverence for and understanding of ideas from other communities (Haskell, Scala, Java), evidenced by the fact that svick and karelz aren't/weren't even familiar with the arguments for Option (whether you agree with them or not) that have been well documented and discussed for years.

I'm sympathetic to @louthy's point that retrofitting functional types to the core libs could be problematic, but the NRE problem is big enough that just adding Option type to the core libs seems appropriate, even if it's only used to reduce NREs in app code, not retrofitted to the core libs, and the other functional advantages @louthy mentioned of using Option with other functional types are not immediately available.

It's not just a matter of taste or style here (functional/imperative). This dishonesty of method signatures problem is an actual and practical deficiency in the way C# code is currently written, and has real-world fallout, e.g. ubiquitous NREs in C# programs. I don't buy the argument that C# shouldn't adopt functional patterns because it's hopelessly imperative. "We don't want that because it's functional" is not an argument. I believe C# can be both OO and functional, applying concepts from each where appropriate, and be better off for it.

@mrpantsuit -
We're getting nullable reference types (although I personally might prefer stronger enforcement). That takes care of the primary use case for Option<T>. We'd probably have to use static, non-extension methods for all the mapping you can do, if we wanted a more functional style, but at the very least we _are_ going to have the necessary information in the type system.

@mrpantsuit

If you return T and sometimes return null, you're lying to those that would call your method.

You're not lying. For a reference type T, null is always a valid value in C# 7. The problem is that there is no way to express that a method will never return null. And C# 8.0 nullable reference types solve that.

why wouldn't you just solve it using an existing language feature: the type system?

In my opinion, solving this just by adding the Option<T> type is not good enough. Adding nullable reference types is more work, but the end result is much better.

"We don't want that because it's functional" is not an argument.

No, it's not. But "we want it because it's functional" is also not an argument. That's why C# 8.0 is going to have nullable reference types: it recognizes that null-safety is a problem, it realizes that retrofitting Option<T> into the existing ecosystem would not work well, and so it creates its own solution: one that is designed specifically for an OOP-first language with more than a decade of history.

So then how will you solve the further dishonesty of exceptions, i.e., method signatures lie by pretending that they'll never throw? (The dishonesty here is lying by omission.) Another language feature, like, God forbid, checked exceptions? :-O Alternatively, you could just solve it by returning a Result type (holds a result OR an error/exception), similar to how you solved the null dishonesty with Option. Seems a simpler, more explicit, holistic solution.

No one was making the argument to use Option simply because it's functional. And it should be noted that C# is getting more functional with each release, which is great: pattern matching, throws in expressions, lambdas, LINQ, switch expressions. C# strayed from it's OO-first history long ago, so no need to draw a line in the sand now.

The main advantage of an Option type is that Option is essentially a collection with a maximum size of 1, i.e. it is either empty or contains exactly one element. What this means is that Option implements IEnumerable and thus automagically works everywhere IEnumerable works. What this means is that I never need to care whether there is a value there or not, I can, for example, simply foreach over it, and the loop body will either be executed once or not at all, without me having to check.

Additionally, Option is a Monad and thus automagically works everywhere a monad works, e.g. in a LINQ Query Expression. Again, like above this means I never actually need to check whether there is a value or not. I simply do from value in valueThatMayOrMayNotExist select doSomethingWith(value) and this will always work no matter whether valueThatMayOrMayNotExist has a value or not.

With Nullable Reference Types, I generally need to do an explicit null check before dereferencing it, or the type checker needs to perform sophisticated dynamic flow analysis to prove that the reference is not null.

This is the first time I've heard of such a use of Option<T>.

To be fair to nullable reference types, it's pretty trivially solvable via something like:

public static IEnumerable<T> SingleOrEmpty<T>(T? element)
{
    if (element == null)
    {
        return Enumerable.Empty();
    }
    else
    {
        return Enumerable.Single(element);
    }
}
        public Option<Employee> GetById(string id)    => new DbContext().Find(id);

can be expressed in C# 8 as:

public Employee? GetById(string id)    => new DbContext().Find(id);

Is Option<T> still needed after the release of c#8 and Reference Types are not null by default?

The main advantage of an Option type is

The main advantage of an Option type is that you can build a "tower of Options.". Option<Option<T>> is a perfectly valid type, and makes it easy to compose type properties and write API libraries that use the type system to guarantee success ("fallback plans guaranteed to succeed"). The same is true for Either.

With a nullable, you cannot do that, because you can't do Nullable<Nullable<T>>. The engineer has to write imperative logic to achieve the same, and because there is less structure (less type guarantees), there is more chance the engineer can commit a modus ponens or similar "case fall through" common logic error.

Was this page helpful?
0 / 5 - 0 ratings