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.
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
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:
PriorityQueue
- dotnet/runtime#14032), or 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
@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, SelectMany
looks 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 Option
s (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?
null
, exceptions and side-effects. I am inclined to follow the "functional style" - I like to have functions with clearly defined inputs and outputs. 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 ofPerson
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
):
Value
directly, removing the core purpose of trying to get C# programmers to take the 'happy path'.GetValueOrDefault
that could return null
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).Option
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 ofT
, yet it's a lie. It's a massive hole in the type-system, and that's whynull
is considered the 'billion dollar mistake'. You can't dereferenceNone
, and therefore it's safer. You have to pattern-match to either extract a concrete value ofT
that will do what its contract says, orNone
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.T?
? Check.null
-check to extract the value? Check.@svick
Are you happy with NullReferenceException
s? 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 NullReferenceException
s, 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 NullPointerException
s?
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.
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
@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:
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
.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.
- 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.
- or example,
'T list
in F# does not have a function for appending'T option
, even though there are some functions in theList
module that useoption
.
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 notnull
?
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
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.
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 removenull
, which is what non-nullable reference types (https://github.com/dotnet/csharplang/issues/36) are about. I don't see how renamingnull
toNone
helps.To expand upon this some more:
For a reference type
T
, the possible values are:null
T
For
Option<T>
, the possible values are:*None
Some
holding an instance ofT
The two are effectively identical, so adding
Option<T>
does not bring much benefit. It makes the possibility of not having an instance ofT
more explicit, but since it does not removenull
, it also adds confusion: if you seeT
, does it mean it's a "new-styleT
", so it shouldn't benull
(but nobody is checking that) or is it an "old-styleT
", in which casenull
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
holdingnull
. I'm going to assume that's not intended.