The Generics Manifesto and single-element tuples

I've asked this question before, here and here, but since neither of those were answered, I'll ask again in this separate thread:

According to the language reference and (most of) the observable behavior of the current compiler, single-element tuples are not allowed.

But the Generics Manifesto seems to assume that single-element tuples are allowed, ie that tuples can have zero or more elements (as opposed to zero or two or more), as demonstrated by the following two code examples (quoted from the manifesto):

func apply<... Args, Result>(fn: (Args...) -> Result,    // function taking some number of arguments and producing Result
                           args: (Args...)) -> Result {  // tuple of arguments
  return fn(args...)                                     // expand the arguments in the tuple "args" into separate arguments
}

and

public struct ZipIterator<... Iterators : IteratorProtocol> : Iterator {  // zero or more type parameters, each of which conforms to IteratorProtocol
  public typealias Element = (Iterators.Element...)                       // a tuple containing the element types of each iterator in Iterators

  var (...iterators): (Iterators...)    // zero or more stored properties, one for each type in Iterators
  var reachedEnd = false

  public mutating func next() -> Element? {
    if reachedEnd { return nil }

    guard let values = (iterators.next()...) {   // call "next" on each of the iterators, put the results into a tuple named "values"
      reachedEnd = true
      return nil
    }

    return values
  }
}

So, as far as I can see, The Language Reference and The Generics Manifesto disagrees on whether single-element tuples are allowed or not.

Can someone please explain?

6 Likes

I believe the Generics Manifesto was written when both the core team and the compiler believed that we could treat everything as a tuple. I don't think it's been updated since. I wouldn't take anything said in that document as a definitive guide as to how tuples should be treated.

Single-element unlabeled tuples are equivalent to the element type in Swift's type system. Because the type system is parametric, we would know from context whether a scalar value is being used as a scalar value or as a single element of a variadic tuple, so the scalar type T (equivalent to (T)) is a substitution for the variadic tuple (TT...), as would be (T, U) or ().

3 Likes

Oh, that's still true? I thought that might have changed when function parameter lists ceased to be tuples. Good to know.

Thanks @Joe_Groff. I have a couple of follow up questions:


1

As far as I can understand, this must imply either:

  • That every type T is a (single element unlabeled) tuple type.

or

  • That there is no such thing as a single-element tuple type.

Which one is it? Or is there another possibility?


2

The following is an example from the generics manifesto, I've just added the foo() method and ==, so I guess this would be considered valid code once Swift has variadic generics:

extension<...Elements : Equatable> (Elements...) : Equatable {   // extending the tuple type "(Elements...)" to be Equatable
    func foo() { }
    static func == (lhs: (Elements...), rhs: (Elements...)) -> Bool { details … }
}

Wouldn't this have to special case (Elements...) to not include the substitution T (equivalent to (T))?

Perhaps this is what is meant by the following lines of the manifesto:

There are some natural bounds here: one would need to have actual structural types. One would not be able to extend every type:

extension T { // error: neither a structural nor a nominal type
}

?

If there is no special casing performed for this example, ie the substitution T is included, then I guess every type that conforms to Equatable (including eg Int) would get the foo() method (and a redundant conformance to Equatable)?

As far as I can understand, this must imply either:

That every type T is a (single element unlabeled) tuple type.
or

That there is no such thing as a single-element tuple type.
Which one is it? Or is there another possibility?

The first one is closer to the model Swift intends. If you form a single-element tuple in the Swift compiler with TupleType::get on a single unlabeled element, the resulting type is canonically equivalent to the element type. (In very early versions of Swift, x.0 would in fact work as the identity operation on every type, but we added an intentional suppression of the .0 element for types that are statically known to not be tuples because it was confusing, and also because .0 was effectively overloaded on N-tuples, since it could refer to either the entire value as a nested 1-tuple of itself or to the first element of the N-tuple.) If we had variadic generics, then substituting a single element into a tuple would likewise produce a scalar type:

func variadicId<T...>(_: T...) -> (T...)

variadicId(1) // returns Int
variadicId(1, 2) // returns (Int, Int)
extension<...Elements : Equatable> (Elements...) : Equatable { ... }

Wouldn't this have to special case (Elements...) to not include the substitution T (equivalent to (T))?

Most likely, yes.

4 Likes

Or, this could be spelled

// Arity 0
extension () : Equatable {
  public static func == (lhs: (), rhs: ()) -> Bool { return true }
}

// Arity >= 2
extension<
  First : Equatable, Second : Equatable, ...Rest : Equatable
> (First, Second, Rest...) : Equatable { ... }

could it not?

IMO it'd be a bit pedantic to force someone to write that.

4 Likes

Then I guess this statement is true: All nominal and structural types T are tuple types, since T == (T)?

But I'd say this doesn't make sense, because it implies that all nominal types are also structural, since tuple types are structural ... and because it naturally leads to undesirable situations like the variadic-generics-related one above and the ones you mentioned in your earlier post, ie

x.0.0.0.0…0 == x // for any x of any type T

and

typeof (123, true).0 == Int
&&
typeof (123, true).0 == (Int, Bool)

But instead of solving the underlying problem (which IMO is that the type of a 1-tuple is equivalent to the type of its element), Swift has only added band-aid fixes to cover up some of its symptoms, by introducing strange exceptions like suppressing .0 on statically knowable non-tuple types even though all types are 1-tuples, and preventing 1-tupels from being expressed and accessed even though they do exist, in a way, behind the scenes. Such strange rules and exceptions makes it impossible (for both users and compiler devs) to build a complete and working mental model of the system, resulting in a bug prone implementation and a constant need for further strange rules and exceptions.

It seems to me like Swift has to make up its mind about whether 1-tuples should exist or not, picking one of the two non-problematic alternatives, ie either:

  • 1-tuples don't exist, at all, in any way, because a tuple type is a direct product of two or more types (not one, not zero), in which case tuple types would be more suitably written using an infix operator like T * U * V, which would reflect the semantic non-existance of both 0- and 1-tuples in syntax.

or

  • 1-tuples exist and their type is separate from their element's type, which would make .0 unproblematic, and let us express and access single element tuples in the language (assuming there is syntax that solves the conflation-of-parentheses issue).

I would prefer the latter, since it seems most useful.

4 Likes

I haven't seen any evidence that the lack of unlabeled 1-tuples is causing major problems, either in users' understanding of the language or in implementation technical debt. ".0 doesn't exist on scalars" is really the extent of it. Making 1-tuples distinct from their element would have its own set of tradeoffs and potential for confusion—especially with variadics, the need to wrap and unwrap 1-tuples to use variadic functions in their unary form would be annoying.

IMO, the existing syntax has that same property. The natural extension of (T,U,V) syntax to one element, (T), is naturally the same as the parens-as-grouping syntax.

I suppose I should ask, what use cases do you have in mind exactly for unlabeled 1-tuples?

1 Like

are we ruling out python (T,) syntax?

If it turns out the distinction must be made, that'd be a reasonable proposal. Another possibility might be to reuse the labeled tuple syntax, (_: T).

6 Likes

I can’t give a single example that really depends on 1-tuples - but I don‘t have use cases for tuples of size 453621 either, and no one seems to think those should be disallowed ;-).

Imho we should rather ask why we should‘t have single element tuples, because it feels like an artificial restriction:
„Tuples of size three? Fine! Two elements are great as well - but not one, that number is forbidden. Zero, on the other hand, is so nice that we grant it an alias“.

Removing exceptions is a value by itself, even if that does not add power to the language.

4 Likes

Here, there'd be no artificial restriction really, the single-element case folds into the completely equivalent non-tuple case. (The prohibition of labeled single-element tuples is a real special case that'd be nice to eliminate, I agree.)

5 Likes

tino is right though it is weird to have 0 tuples and 2 tuples but not 1 tuples

2 Likes

A bit off-topic. If we had variadic generics why coudldn‘t we fold tuples into a new generic Tuple struct which is extensible and also create a new literal protocol like ExpressibleByTupleLiteral? I don‘t have any insights into the technical aspect of the question but I really would like to learn more about it.

1 Like

The 1-tuple exists, but it isn't usefully different from the single element type.

it’d be more useful if we had tuple splatting for variadic arguments tho

I didn't say that the lack of unlabeled 1-tuples is the cause, I said that the underlying problem is

I've been bumping into and reporting tuple- and parentheses related bugs since the first versions of Swift, and its not just me. Here is a recent bug that @mattrips reported and just started working on, it has some interesting comments.

Also, the following is another example of the confusion caused by 1-tuples having the same type as their element:

But the language reference says that 1-tuple types don't exist:

All tuple types contain two or more types, except for Void which is a type alias for the empty tuple type, ().

It also says:

In Swift, there are two kinds of types: named types and compound types. A named type is a type that can be given a particular name when it’s defined. Named types include classes, structures, enumerations, and protocols.
/.../
A compound type is a type without a name, defined in the Swift language itself. There are two compound types: function types and tuple types.
/.../
You can put parentheses around a named type or a compound type. However, adding parentheses around a type doesn’t have any effect. For example, (Int) is equivalent to Int.

I would interpret this as 1-tuples not being a thing, they don't exist, in any way. And (Int) == Int is a "named type", not a tuple type.

But if, as you say, 1-tuples do exist, and are equivalent to their element's type, because (T) == T, then all "named and compound types" are "compound types", which sounds strange.

I'm afraid I'm still no wiser than when I wrote:


I'd like to see a code example illustrating the following:

Because as far as I can see, the opposite is true: Special casing 1-tuples spreads through all things dealing with / affected by 1-tuples, as eg variadic functions in their unary form. Here is an example of a user who is annoyed by the special casing of 1-tuples.

I wish Swift could have more consistent (ie fewer and simpler) rules in this area. And the only reason I can see for why tuples can't simply have zero or more elements and why the same rules can't apply to all tuple types, is the conflation-of-parentheses-syntax-issue, without which I doubt anyone would find it natural to consider the type of a single element tuple equivalent to the type of its element.

I'd still say that the current rules are complex and confusing (for both users and compiler devs):

  • Tuple types can have 0, not 1, or 2 or more elements, but it seems like 1-tuples also exist, in some way.

  • It seems like all types are tuple types (because (T) is equivalent to T) but I'm not sure.

  • To me, saying that there exist single element tuples and that their type is equivalent to the type of their (single) element, and that the language prevents us from naming or accessing their single element or otherwise treating them like all other tuple types, sounds almost as strange as if for example enum MyEnum { case singleCase(String) } would not be allowed because it has just a single case, although single-case enums would still exist, in a way, and their type would always be equivalent to the type of the associated value of their single case (provided it has an associated value of course). Or, if Swift had fixed size array types, with a type level count, if single element fixed size arrays would be considered unnecessary and therefore although they would kind of exist, their type would be equivalent to their single element's type, and we would not be allowed to access its single element in the same way as we access all other fixed size array types (of sizes 0 or 2 or more).

  • There has always been and still are lots of tuple- and parentheses- related compiler bugs. My feeling is that I'm no longer sure whether reporting and "fixing" these bugs simplifies or complicates the situation.

2 Likes

These kinds of issues are with the function type representation, primarily. If function calls represented their arguments as a list of expressions instead of sometimes as a tuple, which better reflects the language model, then 1-tuples would not be an issue in situations like this.

1 Like