Open ElemarJR opened 7 years ago
@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<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<List
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);
}
}
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.
instead of
The proposed method signature reveals much better the real intent of this method/function. Right?
Option<T>
could provide aMatch
function....Here is a very basic implementation:
with some additional operators:
and some LINQ support:
This proposal does not incur any CLR changes.