Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zero- and one-element tuples #883

Open
gafter opened this issue Sep 6, 2017 · 42 comments
Open

zero- and one-element tuples #883

gafter opened this issue Sep 6, 2017 · 42 comments

Comments

@gafter
Copy link
Member

gafter commented Sep 6, 2017

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

@yaakov-h
Copy link
Member

yaakov-h commented Sep 6, 2017

What's the purpose of a zero-element tuple?

@mattwar
Copy link
Contributor

mattwar commented Sep 6, 2017

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.

@HaloFour
Copy link
Contributor

HaloFour commented Sep 6, 2017

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().

@DavidArno
Copy link

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

@AdamSpeight2008
Copy link

AdamSpeight2008 commented Sep 7, 2017

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

@scalablecory
Copy link

scalablecory commented Sep 7, 2017

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.

@HaloFour
Copy link
Contributor

HaloFour commented Sep 8, 2017

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

@scalablecory
Copy link

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

@jnm2
Copy link
Contributor

jnm2 commented Sep 8, 2017

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

@quinmars
Copy link

quinmars commented Sep 8, 2017

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.

@DavidArno
Copy link

@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

@YairHalberstadt
Copy link
Contributor

YairHalberstadt commented Sep 15, 2017

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

@jnm2
Copy link
Contributor

jnm2 commented Sep 15, 2017

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?

@Joe4evr
Copy link
Contributor

Joe4evr commented Sep 15, 2017

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.

@HaloFour
Copy link
Contributor

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

@quinmars
Copy link

quinmars commented Sep 19, 2017

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.

@yaakov-h
Copy link
Member

Non-generic IEnumerable is an enumerable of result objects. Making IAsyncEnumerable an enumerable of non-results seems strange and asymmetric.

@jnm2
Copy link
Contributor

jnm2 commented Sep 19, 2017

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?

@quinmars
Copy link

quinmars commented Sep 20, 2017

@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}");
}

@quinmars
Copy link

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

@Neme12
Copy link

Neme12 commented Jan 11, 2018

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?

@HaloFour
Copy link
Contributor

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

@Neme12
Copy link

Neme12 commented Jan 11, 2018

@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) 😄

@mcintyre321
Copy link

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.

@ufcpp
Copy link

ufcpp commented Apr 13, 2018

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

@gafter gafter self-assigned this Jun 17, 2019
@lucasteles
Copy link

Will we suport deconstruct inline single value tuples?

Today in C#8 beta we suport it only in pattern matching

For exemple

        public abstract class Option<T> { }
        public class None<T> : Option<T> { }
        public class Some<T> : Option<T>
        {
            public T Value { get; }
            public Some(T value) => Value = value;

            public void Deconstruct(out T value) => value = Value;
        }

This works:

            if (maybeFoo is Some<Foo>(var value))
            {  
                 // value is a value of type Foo
                ...
            }

but this wont

            if (maybeFoo is Some<Foo> someFoo)
            { 
               var (value) = someFoo;
                ...
            }

I think this is something thats should work

@gafter gafter added this to the 8.X candidate milestone Aug 26, 2019
@gafter gafter modified the milestones: 8.X candidate, X.X candidate Sep 16, 2019
@gafter gafter removed their assignment Mar 13, 2020
@gafter gafter self-assigned this Apr 3, 2020
@zacharylayne
Copy link

I'd like to see this too. I'm putting together a scripting framework that wraps .NET's scripting library, and I'm considering letting scripts declare tuples for expected input (e.g. (string foo, char bar) input;. If 1-tuple syntax doesn't work, this is a less attractive option.

@myblindy
Copy link

This would be nice in general, it could allow for code such as this:

Dictionary<string, (int color)> dict;

// later
var color = dict["Table"].color;  

Compared to just dict["Table"] which doesn't provide a hint at a glance as to what it returns, even with a better dictionary name. Maybe I basically just want named dictionary value types, but this is by far easier to implement.

@CyrusNajmabadi
Copy link
Member

I think that code isn't clear because you called it dict. Just call it 'nameToColor' and it will be clear.

@jnm2
Copy link
Contributor

jnm2 commented Mar 24, 2021

I like colorsByName because then each entry is colorByName. colorByName.Key is obviously 'name' and colorByName.Value is obviously 'color.' It's harder to name lambda parameters/iteration variables in a way that .Key and .Value are just as clear if the dictionary variable itself has already taken the singular form of the name.

@dgioulakis
Copy link

This was very confusing when I attempted use a Deconstruct() with a single out param, n, but the compiler would not allow me to pattern match using this. The error just says An expression of type 'xyz' cannot be handled by a pattern of type 'n'.

@Shadowblitz16
Copy link

Shadowblitz16 commented Jun 10, 2022

single element tuples should be able to be named as it makes good quick objects in generics

Also regarding tuple syntax while wrapping a value inside braces is valid it doesn't actually do anything unless it is a logical expression.

Just check whether the whole expression is wrapped if it is then its a tuple if its only partially wrapped it's not.

there is no reason to do something like...

var value = (x * y + z == 0 ? 0 : 1)

However it is still useful to do things like..

var value  = (x * y + z) == 0) ? 0 : 1
var tuple = ((x * y + z) == 0) ? 0 : 1)
var value = x * (y + z)
var tuple  = (x * y + z)

@CyrusNajmabadi
Copy link
Member

@Shadowblitz16 That code already has meaning and exists today in many projects. We cannot break it by having its meaning change.

@binki
Copy link

binki commented Jun 10, 2022

I wonder if target typing could be used when passing a single-value tuple via a method call or doing an assignment. That approach was used recently to make the ternary operator more usable.

It would be unfortunate if I had to write this code:

(int Item1) GetInfo() {
  return (Item1: 2);
}

When the compiler could infer what I mean with this code:

(int Item1) GetInfo() {
  return (2);
}

But, even if I do have to write (Item1: 2), I still want this feature. I oftentimes am authoring a method and changing it between returning a single value and multiple values. Not being able to keep it a tuple for the one-value case makes that take a lot more work. Also, sometimes I want a name for clarity and end up making a garbage second member of the tuple just to get that:

(int Count, int _unused) GetInfo() {
  return (2, 0);
}

Using a struct record is a good alternative (now that it exists, yay!), but even that is a little bit heavyweight for some use cases.

I also admit that I think that Python’s way of solving this problem with a trailing comma is acceptable. I do not expect this to be acceptable to the C# community because it looks wrong:

>>> 1,
(1,)
>>> type((1,))
<class 'tuple'>

@jez9999
Copy link

jez9999 commented Nov 1, 2022

Another use-case for supporting shorthand single-element tuples:

public void DoThing(int id, Func<(int Id1, int Id2, string Message), bool> fnProcessCallback) {
    //...
    fnProcessCallback(1, 2, "hw");
}

//...

foo.DoThing(12345, (args) => {
    var (id1, id2, msg) = args;
}

Effectively allows named arguments for callbacks, as the arguments are all just one tuple with names. Works fine until you need a callback with just one argument:

Func<(int Id1), bool>
//...
var (id1) = args;

Would be nice for this to work.

As I mentioned in #6636 a possible way to avoid ambiguity would be to allow trailing commas in tuples, hence a single- and zero-element tuple could be:

(int,)
(int Thing,)

(,)

@binki And I've just noticed you said the same thing at the end of your post. 😄 I don't agree that it looks wrong - C# has trailing commas allowed in various places already such as array initializations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests