Runtime: RFC: Add a Unit type to the System namespace

42

Abstract

Units 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 called Unit provided by different packages.

Proposal

Implement a System.Unit struct, taking on board the needs of the major libraries providing Units at the moment.

Benefits

  • Provide a Type that can be used _interchangeably_ between the major libraries
  • Provide a Unit for future libraries, preventing yet more proliferation
  • Make the lives of everyone who has to work these so much better

_Example_

This file is an example of integrating System.Reactive.Unit (paging @ghuntley @onovotny ) with Mediatr.Unit (credit @jbogard) and language-ext.Unit (credit @louthy ).

This covers 90% of the Use Cases for Unit. If this were implemented in the System namespace with other Types, it would solve a lot of issues and prevent a lot of future issues .

richbryant picture richbryant  ·  13 Jan 2020

Most helpful comment

14

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:

  • This already exists and is used heavily in System.Reactive and other libraries
  • The BCL has had to implement two different delegate types: Action<> and Func<> in order to work around this. If there was a unit type Action<int> would be Func<int, Unit>
  • Other languages that have a unit type (from https://en.wikipedia.org/wiki/Unit_type):

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.

grokys picture grokys  ·  13 Jan 2020

All comments

-19

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.

masonwheeler picture masonwheeler  ·  13 Jan 2020
5

Often not used with methods though. Often used as part of lambda based constructs. Essentially tends to be used with more c# functional libraries.

glennawatson picture glennawatson  ·  13 Jan 2020
0

I like this, but would prefer to be able to pass void as a type, because Unit is a silly name :)

mrpmorris picture mrpmorris  ·  13 Jan 2020
-10

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

masonwheeler picture masonwheeler  ·  13 Jan 2020
4

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.

richbryant picture richbryant  ·  13 Jan 2020
-11

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

masonwheeler picture masonwheeler  ·  13 Jan 2020
5

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 picture glennawatson  ·  13 Jan 2020
-6

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

masonwheeler picture masonwheeler  ·  13 Jan 2020
1

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)

glennawatson picture glennawatson  ·  13 Jan 2020
-5

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.

masonwheeler picture masonwheeler  ·  13 Jan 2020
1

I think this will be cool if it can come on board in System namespace

chrisgate picture chrisgate  ·  13 Jan 2020
3

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.

glennawatson picture glennawatson  ·  13 Jan 2020
3

@masonwheeler by your reasoning we should he passing the MouseEventArgs everywhere and never stop using it.

glennawatson picture glennawatson  ·  13 Jan 2020
14

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:

  • This already exists and is used heavily in System.Reactive and other libraries
  • The BCL has had to implement two different delegate types: Action<> and Func<> in order to work around this. If there was a unit type Action<int> would be Func<int, Unit>
  • Other languages that have a unit type (from https://en.wikipedia.org/wiki/Unit_type):

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.

grokys picture grokys  ·  13 Jan 2020
0

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

richbryant picture richbryant  ·  13 Jan 2020
7

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

david-driscoll picture david-driscoll  ·  13 Jan 2020
1

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.

RLittlesII picture RLittlesII  ·  13 Jan 2020
-1

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.

masonwheeler picture masonwheeler  ·  13 Jan 2020
0

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 picture jspuij  ·  13 Jan 2020
-2

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

masonwheeler picture masonwheeler  ·  13 Jan 2020
0

Yes, and the one on List takes an Action.

but since an Action can have just as many side-effects as a Func, I do not see the relevance to this issue of the _existing proliferation of Units_.

richbryant picture richbryant  ·  13 Jan 2020
10

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`). Before having unit if you wanted to add a behavior (a kind of middleware) you had to implement the behavior twice. once for void returning and once for value returning cases.

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.

david-driscoll picture david-driscoll  ·  13 Jan 2020
2

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

jspuij picture jspuij  ·  13 Jan 2020
3

@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

mrpmorris picture mrpmorris  ·  13 Jan 2020
0

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

glennawatson picture glennawatson  ·  13 Jan 2020
0

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

jspuij picture jspuij  ·  13 Jan 2020
6

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.

jbogard picture jbogard  ·  13 Jan 2020
0

Oh and this one, which is a much longer discussion of first-class support for an actual unit type:

https://github.com/dotnet/csharplang/issues/1604

jbogard picture jbogard  ·  13 Jan 2020
0

That seems as a pretty well thought out proposal. Upvoting it too.

jspuij picture jspuij  ·  13 Jan 2020
0

Please make this happen. A built-in Unit will really make life easier when using and writing generic code.

StefanBertels picture StefanBertels  ·  15 Jan 2020
1

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 picture bartonjs  ·  15 Jan 2020
0

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

jbogard picture jbogard  ·  15 Jan 2020
0

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.

bartonjs picture bartonjs  ·  15 Jan 2020
4

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

tannergooding picture tannergooding  ·  15 Jan 2020
1

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.

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.

DavidArno picture DavidArno  ·  16 Jan 2020
2

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.

mrpmorris picture mrpmorris  ·  16 Jan 2020
5

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

StefanBertels picture StefanBertels  ·  16 Jan 2020
-1

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

mrpmorris picture mrpmorris  ·  16 Jan 2020
1

@StefanBertels, that is a fair point. I'll say no more on DUs here. 👍

DavidArno picture DavidArno  ·  16 Jan 2020
0

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.

danmosemsft picture danmosemsft  ·  3 Mar 2020