csharplang
csharplang copied to clipboard
zero- and one-element tuples
@gafter commented on Fri Jan 06 2017
To best align positional pattern-matching (which won't restrict the number of values being deconstructed) with tuples, please support zero-element and one-element tuple types and tuple expressions.
@AdamSpeight2008 commented on Fri Jan 06 2017
Doesn't the current tuple parser require a minimum of 2 values?
@alrz commented on Fri Jan 06 2017
@AdamSpeight2008 Yes.
@bbarry commented on Fri Jan 06 2017
Why not align the other way, don't recognize deconstruction syntax with fewer than 2 elements?
@HaloFour commented on Fri Jan 06 2017
@gafter
I assume that this will be a post-C# 7.0 consideration?
@bbarry
Without support for conditional deconstruction of zero/one values then patterns like None()
and Some(T)
would be impossible.
@orthoxerox commented on Sat Jan 07 2017
@bbarry Single-value tuples are necessary for primitive wrapper types.
@dsaf commented on Mon Jan 09 2017
Would leveraging it for naming primitive return values be considered an abuse?
(int id) SaveSomethingToDb(Something smth);
instead of
int SaveSomethingToDbAndReturnId(Something smth);
@HaloFour commented on Mon Jan 09 2017
I like how Swift handles "womples", they're effectively just the scalar value itself. So an Int32
is an (Int32)
is an ((Int32))
. This makes sense as 1
, (1)
and ((1))
are all the same value. Swift does not allow for naming the element of a "womple", though, which kind of makes sense.
@Richiban commented on Tue Jan 31 2017
Will the zero-tuple be explicitly referred to as unit anywhere?
I'd love to see some support for this in the future, such as being able to use Func<int, ()>
instead of Action<int>
, for example.
@alrz commented on Tue Jan 31 2017
I think the type (and literal) for unit could be ()
if it wasn't ambigious, but using it in place of void probably requires language support so you dont need to explicitly return ()
.
@jcouv commented on Tue Sep 05 2017
@gafter Is this tracking a compiler/API change or a language feature? If the latter, could you close or move to csharplang?
What's the purpose of a zero-element tuple?
Purpose.. probably to satisfy a desire for a unit type/value that some functional languages have (as opposed to 'void' that C#/.net has), and some language's common convention of using empty collections or zero-valued tuples to represent nothing, aka unit.
There might also be a need to support them conceptually to permit deconstruction of zero and one element patterns, e.g. Some(T)
and None()
.
@yaakov-h,
As @mattwar says, ()
is another way of saying unit
, ie no value.
However, I feel it would be a bad idea to introduce yet another type (unit
) to C# to represent that (no) value, as we already have two other ways of indicating no value: null
and void
. I'd far prefer to see the language support the idea of void expressions (eg, see #135) and to have the syntax, ()
, be a way of expressing void
in such expressions:
void Foo(bool b) => b ? Console.WriteLine("True!") : ();
Having said that. I'm not really convinced that ()
adds anything that couldn't be covered by just using void
:
void Foo(bool b) => b ? Console.WriteLine("True!") : void;
but I may be missing some use-case for it.
I'm thinking that they are, wanting to be able to treat Tuple
and ValueTuple
as a pseudo-base class.
tuple result = SomeFunction();
switch( result )
{
case Is (): /* roughly MayBe.None */
case Is (var A): /* roughly MayBase.Some<T> */
case Is (var A, var B):
}
with SomeFunction
roughly being
() xor (T) SomeFunction()
{
If( condition )
{
return ( Value );
}
else
{
return ();
}
}
If you have:
void Foo((int) x);
How would you call it? Would it implicitly convert Foo(1)
? You can't use any number of parens like Foo((1))
to disambiguate it.
@scalablecory
I like how Apple Swift does it. A single-element tuple (womple) is just an alternative syntax for a scalar value. An (Int)
is just an Int
, which kind of makes sense syntactically as (123)
is really just 123
. But one of the side-effects is that unlike tuples of two or more elements, you can't name that single element.
@HaloFour even if it does boil down to int
I think feature parity (naming deconstruction etc.) could all be accomplished with the help of attributes.
But one of the side-effects is that unlike tuples of two or more elements, you can't name that single element.
I consider this a feature though. 😆
but I may be missing some use-case for it.
A real unit type would be very useful for generic types. Take a look on tasks. We have there two types in C#, one is Task
the other Task<T>
. With a language featured unit type from the beginning, Task
would have been simply Task<()>
or Task<void>
.
For tasks this is already to late. But think of async enumerables. If IAsyncEnumerable<T>
is the sequential counterpart of Task<T>
, what is the counterpart for Task
? I doubt there will be a non-generic IAsyncEnumerable
, because it's not worth the effort. On the other hand we have to life then with the lack of symmetry.
@quinmars,
I agree that a real unit type would be useful. If it's feasible though, then I'd like to see void
become that unit type, so that I could do Task<void>
or IAsyncEnumerable<void>
.
Of course, it may not be practicable. In that case, I think the next best solution is a Unit
type (and unit
keyword alias), but that has similar semantics to void
, especially with regards to return
:
unit Foo()
{
Console.WriteLine("foo");
} // no return needed as unit has no value to return
@DavidArno
I think void should be the alias for the unit type ().
It must be an alias for a meaningful unit type, rather than a shorthand (longhand?) for Task so that List<void>
compiles.
I doubt there will be a non-generic IAsyncEnumerable, because it's not worth the effort.
But let's assume we're willing to put any amount of effort into it. What would it do, exactly? Lack a Current
property?
In that case, I think the next best solution is a
Unit
type (andunit
keyword alias), but that has similar semantics tovoid
, especially with regards toreturn
:
So basically, exactly what I described here.
@YairHalberstadt
I think void should be the alias for the unit type ().
void
already means something quite different in C#, and is valid syntax in some scenarios where it would collide with any unit type, e.g. typeof(void)
.
In that case, I think the next best solution is a Unit type (and unit keyword alias), but that has similar semantics to void, especially with regards to return:
So basically, exactly what I described here.
It appears to be the best what we can get. And I like it. The following feels very natrual:
async IAsyncEnumerable<unit> ToAsyncEnumerable(IEnumerable<Task> list)
{
foreach (var t in list)
{
await t;
yield return;
}
}
and:
// T can be unit!
async IAsyncEnumerable<T> ToAsyncEnumerable<T>(IEnumerable<Task<T>> list)
{
foreach (var t in list)
{
yield return await t;
}
}
I doubt there will be a non-generic IAsyncEnumerable, because it's not worth the effort.
But let's assume we're willing to put any amount of effort into it. What would it do, exactly? Lack a
Current
property?
Probably.
Non-generic IEnumerable
is an enumerable of result object
s. Making IAsyncEnumerable
an enumerable of non-results seems strange and asymmetric.
If there is no result being produced for each MoveNext, why would anyone want to call MoveNextAsync
in a loop until it returns false versus just awaiting a non-generic Task
?
@jm2, an IAsyncEnumeration<unit>
is not a just a collection of the same useless value, it has a second dimension: the timing.
Let's say you want to write an email to a list of recipients via Task SendEmailAsync(string r)
, but only for 5 seconds. You could write:
var lastOrder = DateTime.Now + TimeSpan.FromSeconds(5);
var orders = recipients
.Select(r => SendEmailAsync(r)) // due to lazy evaluation the mails are not sended now
.ToAsyncEnumerable() // The (extension) method in my last post
.TakeWhile(_ => DateTime.Now < lastOrder);
foreach await (var _ in orders)
{
// We can write this line after every sended email
// not just when all emails are sended.
Console.WriteLine($"Email sended at {DateTime.Now}");
}
@jm2 and @yaakov-h, don't get me wrong. I do not think that it is reasonable to add a non-generic IAsyncEnumerable
. I just wanted to show that there is a symmetry gap, which an unit type could fill.
I don't think one-element tuples should be in any way "equivalent" to the value itself. If you have any other tuple, it's just a container type and to get the actual value, you have to dot into it (and can even give it a custom name). The logical extension of this would be to do the same for a one-tuple. Why should it be any different?
@Neme12
Tuples are only somewhat containers in the C# language. You have to dot into them out of necessity, but in some cases the language does (or will) treat them just as separate values and ignore that container. For example, the compiler already completely skips the container if you immediately deconstruct a tuple literal. This is despite the fact that the ValueTuple<...>
implementation could technically do something custom in its constructor. There is also a championed proposal to have the compiler allow equality comparisons between tuples by emitting direct comparisons between the tuple elements, again skipping any potential equality operator implementation on ValueTuple<...>
:
(int, int) GetPoint(int x, int y) => (x, y);
(float, float) GetPointF(float x, float y) => (x, y);
var x = GetPoint(1, 2);
var y = GetPointF(1.0f, 2.0f);
if (x == y) { ... }
// converted to
if (x.Item1 == y.Item1 && x.Item2 == y.Item2) { ... }
IIRC, in math a "womple" is just the single value. In various programming languages (e.g. Swift) that's also the case. There are probably advantages and disadvantages to both approaches. I'm not particularly vested in either, but I'd like to hear scenarios where womples are actually useful/necessary.
@HaloFour My point is you still have to deconstruct it to get the value. I don't see why the pattern shouldn't continue:
var a = (item1: 1, item2: 2, item3: 3).item1; // OK
var b = (item1: 1, item2: 2).item1; // OK
var c = (item1: 1).item1; // error?
That would really be unexpected for me for the one-tuple to behave completely differently.
I can't think of any usage for a 1-tuple, I'm more interested in 0-tuples, and it would definitely be weird to have 0 and not 1. I think they should be allowed even if it's just for consistency.
Also, if you do go all the way down to zero:
var a = (item1: 1, item2: 2, item3: 3); // type == ValueTuple
var b = (item1: 1, item2: 2); // type == ValueTuple
var c = (item1: 1); // type == int ?
var d = (); // type == ValueTuple
In your example, a one dimensional point equality would just be translated to x.Item1 == y.Item1
IIRC, in math a "womple" is just the single value.
C# is not math (and I'm quite happy about that) 😄
I'd like var c = (item1: 1); // type == ValueTuple<int>
to be available.
I've found a way of serializing the tuple property names, and it's quite annoying not to be able to use "single value + name" tuples.
Func<(T1 x1, T2 x2)>
Func<(T1 x1)>
Func<()> // I really want this
...
Task<(T1 x1, T2 x2)>
Task<(T1 x1)>
Task<()> // I really want this
...
Does anyone know if this will be considered at any point? I keep having to use a dummy parameter in certain situations e.g. class SomeCommand : Command<(string someArgument, object _)> { ... } ...
@mcintyre321 Can you expand on your scenario? Why not just do class SomeCommand : Command<string> ...
?
I want the value to be named, not just for sugar, but for serialisation as well (I have some code that will read the name attributes).
Sounds like a bad case of Round Peg, Square Hole. If you want named values, especially for the purpose of serialization, it's much more appropriate to use a Dictionary<string, Command>
.
I probably haven't explained my use cases sufficiently. I find there are LOTS of situations where I would like to create a class where one would use a (potentially deeply nested) ValueTuple as a generic parameter, e.g. in a MessageHandler<TIn, TOut>
which works fine, except when I have single valued items.
or public class EngineTemperature : ValueOf<int> { }
vs public class EngineTemperature : ValueOf<(int Celsius)> { }
. I find the latter more helpful, but it won't compile. (NB I'm using my ValueOf library here, but this will be a problem with Record Types once they are part of the language).
Maybe these use cases aren't that common (yet - I think the Record Type use case is very desirable as you will be able to define structures for API messages very concisely), but the fact that single item ValueTuples have different rule (e.g. you have to use the class explicitly (as in public ValueTuple<int> GetSomething(){...}
, rather than being able to do public (int) GetSomething(){...)
complicates the language, and increases code churn when refactoring.