What are single and zero element tuples good for?
Asked Answered
C

9

24

C# 7.0 introduced value tuples and also some language level support for them. They added the support of single and zero element tuples as well; however, I could not find out any scenario when they could be useful.

By ValueTuple.Create overloads I can create any kind of tuples but the C# 7.0 syntax allows only at least two elements:

Microsoft (R) Roslyn C# Compiler version 2.8.3.62923
Loading context from 'CSharpInteractive.rsp'.
Type "#help" for more information.
> ValueTuple.Create()
[()]
> ValueTuple.Create(1)
[(1)]
> ValueTuple.Create(1, "x")
[(1, x)]

By tuple syntax:

> var t2 = (1, 2);
> var t1 = (1); // t1 is now int (of course)
> ValueTuple<int> t1 = (1);
(1,23): error CS0029: Cannot implicitly convert type 'int' to 'ValueTuple<int>'
> ValueTuple<int> t1 = new ValueTuple<int>(1);
> t1
[(1)]

I think I found the thread where this feature was requested but none of the code samples are valid in C# now and could not find any references in the planned features of C# 8.0 either, not even at the recursive tuple patterns.

In the request thread functional programming languages are mentioned. Is there maybe any functional language, which uses them now? I'm not an F# expert but its tuple reference does not mention any use of single and zero element tuples.

So the TL;DR questions:

  • Are single and zero element tuples used in any (maybe functional) .NET language? I mean not by Tuple.Create or by the constructors but by native language support.
  • Are they planned to used in a future version of C#?
  • Or are they there for "just in case", for future compatibility?
Colmar answered 7/8, 2018 at 9:8 Comment(1)
I guess supporting (1) in C# is a breaking change due to the current parse rule. (That is why it evaluates to 1.)Interpleader
I
18

What good is a 0-tuple?

A 2-tuple or a 3-tuple represent a group of related items. (Points in 2D space, RGB values of a color, etc.) A 1-tuple is not very useful since it could easily be replaced with a single int.

A 0-tuple seems even more useless since it contains absolutely nothing. Yet it has properties that make it very useful in functional languages like F#. For example, the 0-tuple type has exactly one value, usually represented as (). All 0-tuples have this value so it's essentially a singleton type. In most functional programming languages, including F#, this is called the unit type.

Functions that return void in C# will return the unit type in F#:

let printResult = printfn "Hello"

Run that in the F# interactive interpreter, and you'll see:

val printResult : unit = ()

This means that the value printResult is of type unit, and has the value () (the empty tuple, the one and only value of the unit type).

Functions can take the unit type as a parameter, too. In F#, functions may look like they're taking no parameters. But in fact, they're taking a single parameter of type unit. This function:

let doMath() = 2 + 4

is actually equivalent to:

let doMath () = 2 + 4

That is, a function that takes one parameter of type unit and returns the int value 6. If you look at the type signature that the F# interactive interpreter prints when you define this function, you'll see:

val doMath : unit -> int

The fact that all functions will take at least one parameter and return a value, even if that value is sometimes a "useless" value like (), means that function composition is a lot easier in F# than in languages that don't have the unit type. But that's a more advanced subject which we'll get to later on. For now, just remember that when you see unit in a function signature, or () in a function's parameters, that's the 0-tuple type that serves as the way to say "This function takes, or returns, no meaningful values."

Incretion answered 7/8, 2018 at 12:18 Comment(5)
P.S. The "more advanced subject which we'll get to later on" that I mentioned was how you can compose all functions in F#. In C#, you could write a Compose method that takes a Func<A, B> and a Func<B, C> and returns a Func<A, C>. But an Action<T> would mess up your Compose method, and you'd have to write four variants: Compose(Func, Func), Compose(Func, Action), Compose(Action, Func) and Compose(Action, Action). In F#, though, you can just write a single Compose function and it will work for all functions: unit -> unit can be composed with unit -> int, and so on.Incretion
Thank you for your answer (upvoted). The underlying .NET type of F# unit is Microsoft.FSharp.Core.Unit in FSharp.Core.dll. Is it just that because ValueTuple did not exist back then? Or are they somehow different semantically?Recombination
I'm pretty sure it's because ValueTuple did not exist back then. As long as all 0-tuples are the same type, then Microsoft.FSharp.Core.Unit could be replaced with a ValueTuple of 0 elements. However, it's very important to the F# type system that all instances of () be the same type. I don't yet know the internals of ValueTuple, so I don't know if it can make that promise or not. If it can't, then that's a semantic difference between F#'s unit type and ValueTuple: all instances of () in F# are the same type.Incretion
And for more on why composition is so important (and why currying is so important in F#), you can watch Scott Wlaschin's recent presentation, "The Power of Composition": youtube.com/watch?v=WhEkBCWpDasIncretion
I've never encountered any docs saying () is a zero element tuple, or that values are one-tuples. Nevertheless I've more or less seen the logic in this all the time, so what you write makes good sense.Annelleannemarie
C
3

I can't think of a use case for one element tuple. EDIT - As pointed out the referenced page mentions one element tuples as a way to name return parameters, which sounds actually useful, although this is probably C# specific.

Zero element tuple is in functional languages also known as unit. It's sort of equivalent of void in C#, which some (most?) functional languages don't have.

The difference is that () is an actual value, which you can do stuff with like hold a list of, pattern match etc. Functional languages need this because a function must return something and if you want to return "nothing" you have to do it explicitly by returning (). You can also write a function that accepts unit as a parameter, which basically means it's a value with delayed execution, most likely with side effects:

let x = getFromDb() // will read DB
stream.Flush() // will change state
Chaim answered 7/8, 2018 at 9:32 Comment(3)
I can't think of a use case for one element tuple. The referenced Github page mentions named return variables through tuples.Interpleader
Yes, unit was referred in the thread. It could be handy even if behind the scenes it can be compiled to System.Void but I'm just curious whether it is really used (or is planned to do so) anywhere.Recombination
@taffer - The unit type is used all over the place in F#. I wrote something about it for the now-defunct Stack Overflow documentation, which is too long to put in a comment so I'll scrounge it up and turn it into another answer here. (Edit: done, see the "What good is a 0-tuple?" answer).Incretion
L
3

There's no support for o-tuple (nople) an 1-tuple (oneple) in C#.

On the base class library (BCL), however, for coherence and just in case someone in the future finds a use for it, there is ValueTuple and ValueTuple<T>. For example, a language that has constructs for 0-tuple and 1-tuple.

ValueTuple has some added value. From the documentation:

The ValueTuple structure represents a tuple that has no elements. It is useful primarily for its static methods that let you create and compare instances of value tuple types. Its helper methods let you instantiate value tuples without having to explicitly specify the type of each value tuple component. By calling its static Create methods, you can create value tuples that have from zero to eight components. For value tuples with more than eight components, you must call the ValueTuple constructor.

Lan answered 7/8, 2018 at 15:45 Comment(0)
H
1

A 1-tuple can be used as a non-null wrapper around a nullable value. This useful when trying to add to a dictionary, for example:

void Process<TKey>(TKey[] keys)
{
    var dict = new Dictionary<TKey, Something>();
    var firstKey = keys[0];
    dict[firstKey] = new Something();
    ...
}

will show a warning if nullable is enabled (when creating dict), because TKey could be a nullable type (which the API may be willing to accept) and Dictionary<TKey, TValue> does not accept null ; while:

void Process<TKey>(TKey[] keys)
{
    var dict = new Dictionary<ValueTuple<TKey>, Something>();
    var firstKey = ValueTuple.Create(keys[0]);
    dict[firstKey] = new Something();
    ...
}

will not show the warning.

Handbarrow answered 2/3, 2021 at 3:46 Comment(0)
I
1

A 1-Tuple could be useful as a single parameter list of a delegate. Tuples are nice to use as delegate parameter lists, because you can change their structure and not break implementations of the delegate. Also you can name the parameters of the delegate this way. But since you can't use a 1-Tuple, you have to revert back to a regular parameter if the number of arguments changes to 1.

Eg:

int DoSomething(
   Func<(int First, int Second, int Third), int> func
) =>
   func((1, 2, 3));

// notice that you could add more parameters or change the order
// of the values and not break the lambda
DoSomething(args => 
   args.First + args.Second - args.Third
);

// DOESN'T WORK
int DoSomethingElse(
   Func<(int First), int> func
) =>
   func((1));

// does work but not as nice
int DoSomethingElse2(
   Func<int, int> func
) =>
   func(1);

// can do it this way; no naming
int DoSomethingElse3(
   Func<ValueTuple<int>, int> func
) =>
   func(new(1));

DoSomethingElse3(args =>
   args.Item1
);

Another use for 1-Tuples is to allow nested nullability. You can actually do this if you use the ValueTuple struct directly.

ValueTuple<string?>? maybeTupleOfMaybeString = new(null);
Assert.IsTrue(maybeTupleOfMaybeString.HasValue);
ValueTuple<string?> tupleOfMaybeString = maybeTupleOfMaybeString.Value;
string? maybeString = tupleOfMaybeString.Item1;
Assert.IsNull(maybeString);

Another use is if you want to specify a collection of values generically, and then in some cases you might specify 1 (or 0) such values.

abstract class GenericBase<TTuple>
where TTuple : struct, ITuple {
   public void DoSomething(
      TTuple tuple
   ) {
   }
}

...

class OneTupleImpl : GenericBase<ValueTuple<int>> {
   ...
   DoSomething(new(5));
   ...
}
Ibnrushd answered 19/7, 2021 at 2:30 Comment(0)
A
0

At the moment, ValueTuple'1 is only used in representations of long tuples, even though there is no C# tuple syntax for 1-tuples: ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>> is the underlying type for an 8-tuple.

As for 0-tuples and ValueTuple'0, we speculate they may appear in future scenarios (maybe some recursive patterns, if I recall correctly).

Ambert answered 8/8, 2018 at 0:3 Comment(0)
W
0

Single value tuples can be used to describe the return value of a method. Sometimes it is useful to provide additional information on what the return value is, e.g.:

  (int ScreenNumber) AddingTarget(string stockItemCode); 
Wunder answered 2/1, 2022 at 3:5 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Predicate
A
0

I often encounter 1-tuple types when dealing with HTTP GET query strings.

A generic query parser would typically translate a query string to some kind of struct with array/tuple members. For example, the query string ?format=pill&color=red&color=blue might be translated to the following intermediate structure.

{
  format: ["pill"],
  color: ["red", "blue"],
}

On type level we could use the 1-tuple type Tuple<Format> to indicate that there should be only one value of type Format, and we could use the array type Array<Color> to indicate that several values of type Color are acceptable.

{
  format: Tuple<Format>,
  color: Array<Color>,
}

The 1-tuple only makes sense when we are dealing with the generic intermediate representation. We would usually wish to remove the 1-tuple entirely and use the following isomorphic, and perhaps less confusing, structure.

{
  format: Format,
  color: Array<Color>,
}

However, using the above type signature is only possible after we have transformed the generic structure to the following application specific structure.

{
  format: "pill",
  color: ["red", "blue"],
}

Going through the extra step of removing the 1-tuple is in my experience usually worth it. However, the 1-tuple based alternative can be useful as a sanity check while doing the transformation.

Note that the same steps could be done in reverse to construct a query string from the parsed structure. The same data structures could be used in both directions.

Ariew answered 6/9, 2022 at 23:28 Comment(0)
S
-1

Empty tuples are constructed by an empty pair of parentheses; a tuple with one item is constructed by following a value with a comma (it is not sufficient to enclose a single value in parentheses)

Sabba answered 6/9, 2023 at 20:57 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Predicate

© 2022 - 2024 — McMap. All rights reserved.