Open richbryant opened 4 years ago
Nobody can deny their utility (since you can't return a void)
I deny their utility. Having a data type that contains no data does nothing useful; all it does is cause confusion. We have void
methods for a reason.
Often not used with methods though. Often used as part of lambda based constructs. Essentially tends to be used with more c# functional libraries.
I like this, but would prefer to be able to pass void
as a type, because Unit is a silly name :)
This is a bad idea that comes up over and over again every few months, generally by people who appear to be unaware of the concept of minus 100 points, because they never provide any explanation as to why Unit
is useful; always simply taking it as an article of faith, with no proof offered or required.
The distinction between methods that have a return type and methods that don't is a useful one. Blurring that line would be harmful. For example, LINQ methods are written, by design, in a pure-functional style, taking Func
s as lambda parameters that look at data but don't touch it. A Unit
type would make it easy for people who don't know what they're doing to pass a lambda with side effects to Select
and end up breaking a bunch of implicit assumptions that everyone who uses LINQ relies on.
Unit
types are already out there and are proliferating. That's a headache for everyone. If you don't like System.Unit
you can always not use it. I'd recommend it was abstract
if it weren't a struct but it is - for obvious reasons - so here we are.
That's a headache for everyone.
Yes, it really is! Especially those of us who are aware of its harmful nature!
If you don't like
System.Unit
you can always not use it.
No, not really. Because if it exists in a standardized form in the BCL -- rather than its current status as some niche thing that only a few FP crazies on the fringe of the ecosystem care about and they can't even settle on a single implementation -- then it's going to get used, and sooner or later I'll end up in a situation where I have to deal with it because some code I need that was written by someone else has Unit
s everywhere.
I recently had exactly this happen to me with dynamic
, and I'd really prefer not to have to go through it again with yet another bad idea.
It's a struct to avoid allocations.
Frameworks often use it when they don't care about the value anymore in a chain of methods.
For example in reactive it means you may have processed the data in the chain and now you just care about something has happened. Without having a common struct for providing this you wouldn't be able to combine together chains of reactive events together.
@glennawatson That seems really short-sighted. Even if you don't care about the data you're processing anymore, how do you know that whoever's next in the chain won't?
Well. It's not necessarily data in the reactive world you care about.
It's about something has happened.
Eg a mouse click. You don't want your view model knowing about a mouse event Arg. You only care that something is invoking my logic and my command should run. You can then change it so you respond to another thing happen eg a key down and you don't have to change your code base because all the events are telling you something has happened (eg unit)
Then why does all this data on MouseEventArgs exist in the first place? This sounds like an argument made by someone unfamiliar with Chesterton's Fence. Just because you don't think it's useful right this moment doesn't mean it should be thrown away.
I think this will be cool if it can come on board in System namespace
MouseEventArgs is useful in the context of the view in my example. Maybe you want to have it only respond when there is a mouse event between certain mouse coordinates. You can do a Where() statement restricting that.
In the view model how that happens is not a concern for it.
Now you can have your view model running on xamarin forms and wpf. A lot of users do.
@masonwheeler by your reasoning we should he passing the MouseEventArgs everywhere and never stop using it.
its current status as some niche thing that only a few FP crazies on the fringe of the ecosystem care about
I don't think this is true and calling us "fringe crazies" is unnecessarily insulting.
As for it being niche:
Action<>
and Func<>
in order to work around this. If there was a unit type Action<int>
would be Func<int, Unit>
In Haskell and Rust, the unit type is called () and its only value is also (), reflecting the 0-tuple interpretation. In ML descendants (including OCaml, Standard ML, and F#), the type is called unit but the value is written as (). In Scala, the unit type is called Unit and its only value is written as (). In Common Lisp the type named NULL is a unit type which has one value, namely the symbol NIL. This should not be confused with the NIL type, which is the bottom type. In Python, there is a type called NoneType which allows the single value of None. In Swift, the unit type is called Void or () and its only value is also (), reflecting the 0-tuple interpretation. In Go, the unit type is written struct{} and its value is struct{}{}. In PHP, the unit type is called null, which only value is NULL itself. In JavaScript, both null and undefined are built-in unit types. in Kotlin, Unit is a singleton with only one value: the Unit object. In Ruby, nil is the only instance of the NilClass class. In C++, the std::monostate unit type was added in C++17.
Basically every language in common use except C# and Java (F# also has one).
A Unit type would make it easy for people who don't know what they're doing to pass a lambda with side effects to Select and end up breaking a bunch of implicit assumptions that everyone who uses LINQ relies on.
How does not having a Unit
type prevent passing a lambda with side-effects?
@masonwheeler I'd suggest using System.Reactive for a while and seeing whether you have a need for a Unit
class. Spoiler: you will.
How does not having a Unit type prevent passing a lambda with side-effects?
It doesn't.
var newList = list.Select(x => { item = x; return x.ToUpper(); });
:trollface: Try to keep it civil, he is just trying to stir the pot by trolling the thread. :trollface:
I like and support the idea as a user of both System.Reactive and MediatR it is frustrating having to interop between the two. I kinda wish it was added with netstandard2.1
because this just means more change.
This feels very similar to the ML.NET types (Microsoft.ML.*
) and a possible solution that might work would be a single "blessed" nupkg that contains System.Unit
that anyone can reference. Then perhaps it can make it into netstandard at a later date like System.ValueTuple
did a few years ago.
That seems really short-sighted. Even if you don't care about the data you're processing anymore, how do you know that whoever's next in the chain won't?
You can compose your observable pipeline and split it before the call to transform to Unit
. That way anyone who wants to process the value can, and whoever just needs the notification gets the notification. Problem solved.
How does not having a Unit type prevent passing a lambda with side-effects?
It doesn't, but it does discourage it. Taking that discouragement away would not be an improvement.
A Unit type would make it easy for people who don't know what they're doing to pass a lambda with side effects to Select and end up breaking a bunch of implicit assumptions that everyone who uses LINQ relies on.
It is easy already, just start an anonymous method that has side effects. The return type does not change anything. People write ForEach extensions methods themselves and for some reason the List<T>
type has exactly such a method.
@jspuij Yes, and the one on List<T>
takes an Action
. There's a nice, clear semantic distinction there as part of the type system so you can tell at a glance exactly how it's expected to work. No Unit
needed.
Yes, and the one on List
takes an Action.
but since an Action
The key advantage that Unit
brings is that you don't have to code around the edge case of void
returning methods. It aids in composition of methods and even compositions for the internals of your application.
MediatR
added unit to get around having to have implement two different pipelines IRequest
(Task
) and IRequest<T> (
Task
System.Reactive
uses the unit type for composition. You want to get a notification for mouse drag, and only mouse drags?
Rx makes this trivial, you get a unit notification for Mousedown
and Mouseup
and then compute the x/y for each Mousemove
in between. To compose Mousedown/Mouseup you have to be able to "transform" them into something, Unit simplifies this because all you care about is the notification, not the value.
Unit
also allows decoupling here. You want your business logic to be as environment agnostic as possible. Therefore your BL lib cannot take a dependency on MouseEventArgs
. This is what @glennawatson is referring to, your ViewModel
is agnostic to the display technology so it shouldn't ever see MouseEventArgs
and there is no reason to capture the data if it is not required.
Then perhaps it can make it into netstandard at a later date like
System.ValueTuple
did a few years ago.
Come to think of it, in most functional languages Unit is equivalent to the 0-element tuple. MAybe we are just after the System.ValueTuple struct after all.
Edit: I was not the only one who realized this: https://github.com/dotnet/csharplang/issues/1279
@masonwheeler here is an example of when Unit is useful - and it's not a functional programming fringe https://github.com/jbogard/MediatR/blob/c1ad66ef52434a22c10a0de5e060d13b185ef80b/src/MediatR/IRequestHandler.cs#L28
Could you provide an example of how having Unit is harmful, and can allow people to accidentally "pass a lambda with side effects"?
Thanks
@jspuij interesting point. Wonder if that could use like a ()
like syntax in that case also like a value tuple without arguments or if that has the potential to confuse users.
@jspuij interesting point. Wonder if that could use like a
()
like syntax in that case also like a value tuple without arguments or if that has the potential to confuse users.
It is already used to signal an anonymous method without arguments... Would not be very difficult to get used to.
Just an FYI, this has come up a lot in various C# language repos. Here's a recent one, to support zero-index value tuples:
https://github.com/dotnet/csharplang/issues/883
And unit-as-valuetuple:
https://github.com/dotnet/csharplang/issues/1279
etc.
I looked for something "official" but my options were pretty limited, and still are. I don't really want to use System.Reactive.Unit
or the F# version, but here we are.
Oh and this one, which is a much longer discussion of first-class support for an actual unit
type:
That seems as a pretty well thought out proposal. Upvoting it too.
Please make this happen. A built-in Unit
will really make life easier when using and writing generic code.
For what it's worth, absent following links to Wikipedia, my assumption was that a type named Unit is either a strongly-typed string ("Kilometer", "Mile", "Gram", etc) or a combination of a value and a string/strongly-typed-string (return new Unit(5280, "Foot")
).
It may be the term of art in functional programming, but it's probably something that would need a different name to fit in with our mostly-procedural libraries.
@bartonjs it's not functional-language-specific, but type-theory-specific. It just so happens that C# picked a different name to match C-based languages, with different semantics and now we have this "fun" Func
/Action
dichotomy.
Okay, so maybe I have my domain names wrong. But I just wanted to point out that I thought of unit in the sense of a unit of measure, and was surprised to find out that it meant something else. So I'm suggesting that the name is likely going to be a hindrance.
Aside from the semi-obvious void
(which has language restrictions), I'd expect None
or NoValue
to be a better name for this concept in the System namespace than Unit
.
Unit Type
is a fairly common name, across many languages (and even outside computer programming): https://en.wikipedia.org/wiki/Unit_type
I'd so it is no more hindering than Rune
Aside from the semi-obvious
void
(which has language restrictions), I'd expectNone
orNoValue
to be a better name for this concept in the System namespace thanUnit
.
None
also has a common pre-existing use with option/maybe types. They either have Some(value)
or None
(no value). With discriminated unions (DUs) potentially being added to C# 9, Unit
won't be the only type people will be asking to be added to the System
namespace. If Option
/Maybe
, Either
and other common DUs aren't added, we'll end up with a proliferation of incompatible versions of those types too across a number of libraries.
If
Option
/Maybe
,Either
and other common DUs aren't added, we'll end up with a proliferation of incompatible versions of those types too across a number of libraries.
That's no reason to not consider Unit
on its own. Option/Either/etc are specifically used in functional programming, Unit
is a concept used across various libraries.
@DavidArno While I really like types like Option
, Either
and other discriminated unions. I think we shouldn't extend this RFC to those. It will be a much longer discussion how to get them right. IMO LanguageExt is a good start for them, too -- Paul Louth did great work to get a very good solution there.
Unit
alone is already very useful because of the problems with void
not being a Type
(Generics, Action
/Func
).
Regarding naming I favor Unit
. It's already used in libraries, and it's a proper name for the concept as @tannergooding mentioned. I don't see a big conflict with "units of measurement".
Okay, so maybe I have my domain names wrong. But I just wanted to point out that I thought of unit in the sense of a unit of measure, and was surprised to find out that it meant something else. So I'm suggesting that the name is likely going to be a hindrance.
Aside from the semi-obvious
void
(which has language restrictions), I'd expectNone
orNoValue
to be a better name for this concept in the System namespace thanUnit
.
Unit isn't a value, it's a type, so NoValue
wouldn't make sense. NoType
would make more sense, but that looks a bit Visual Basic
:(
@StefanBertels, that is a fair point. I'll say no more on DUs here. 👍
The right place for such a proposal is dotnet/runtime. The dotnet/standard repo does not create API - it defines a group of API and infrastructure around it.
FWIW we have versions of this in the runtime itself
@DavidArno While I really like types like
Option
,Either
and other discriminated unions. I think we shouldn't extend this RFC to those. It will be a much longer discussion how to get them right. IMO LanguageExt is a good start for them, too -- Paul Louth did great work to get a very good solution there.
Unit
alone is already very useful because of the problems withvoid
not being aType
(Generics,Action
/Func
).Regarding naming I favor
Unit
. It's already used in libraries, and it's a proper name for the concept as @tannergooding mentioned. I don't see a big conflict with "units of measurement".
void is System.Void struct in dotnet
Another point of contention I've ran into and see others run into is void
-returning switch expressions. There are in fact proposals seeking to address this very thing: https://github.com/dotnet/csharplang/issues/3038.
It seems to me void
already exists, is an empty struct, and is highly proliferated throughout the ecosystem. If the C# language allows void
as a generic type argument, expressions like default(void)
sizeof(void)
etc, probably some interface implementations, and undoubtedly a few other things, this feature could be widely adopted rather than yet another thing devs have to think about when the latest .NET version comes out.
The posts on this thread from years ago suggest adding this to .NET standard. That ship has probably already sailed. That said, using void
as the Unit
type would prevent the NuGet package approach, and could only be consumed by the latest .NET version (whichever this would be released in). I think the tradeoff is worth it, but it's worth taking into consideration.
Abstract
Unit
s are proliferating in the NetCore ecosystem. Nobody can deny their utility (since you can't return a void) but this does introduce the issue of incompatible structs all calledUnit
provided by different packages.Proposal
Implement a
System.Unit
struct, taking on board the needs of the major libraries providingUnit
s at the moment.Benefits
Type
that can be used interchangeably between the major librariesUnit
for future libraries, preventing yet more proliferationExample
This file is an example of integrating
System.Reactive.Unit
(paging @ghuntley @onovotny ) withMediatr.Unit
(credit @jbogard) andlanguage-ext.Unit
(credit @louthy ).This covers 90% of the Use Cases for
Unit
. If this were implemented in theSystem
namespace with other Types, it would solve a lot of issues and prevent a lot of future issues .