Roadmap for and state of single element tuple types?

This is expressly disallowed from the generics manifesto/Extensions of structural types. To quote the specific part:

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> T { // error: neither a structural nor a nominal type
}

I'm not sure how 2 would work with the extension you defined. Could you rephrase using the notation from the manifesto?

Unless the Generics Manifesto disallows a protocol to which all tuple types conform, you can imagine Tuple in my example being that protocol. (Does the manifesto disallow such a protocol?)

Here is an attempt to rephrase my code example (without any such protocol and) using the notation from the manifesto:

extension<...Elements> (Elements...) {   // extending the tuple type "(Elements...)"
    func foo() { ... }
}

I'm not sure what the manifesto says about that, but my guess is that it's considered to be equivalent to:

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

But is it really?

Anyhow, so far this points towards case 1 being the case.

Furthermore, the Generics Manifesto seems to be confident that the number of tuple elements is zero or more (not zero or two or more).

As can be clearly seen in this example from the manifesto.
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 I guess this means that the answer to my question is that:

  • Case 1 is the case, ie: All types T are tuple types, since T == (T). Except eg unconstrained generic type parameters, which are "neither structural nor nominal types". (Which makes me wonder if there are any other types that are not tuple types?) Perhaps a more correct formulation could be:
    "All nominal and structural types T are tuple types, since T == (T)"? (And this seems to imply that all nominal types are also structural, since tuple types are structural ... Could someone who knows this stuff please explain, @Douglas_Gregor ?)

So according to my interpretation of the manifesto, the language reference is wrong when it says:

A tuple type is a comma-separated list of types, enclosed in parentheses.

This is not true, because eg Int, which is the same as (Int), is also a tuple type, as is eg [Float?], none of which is a comma-separated list of types. And it's also wrong when it says:

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

This is not true, because a tuple type can contain zero or more elements.

Even the grammar is wrong:

GRAMMAR OF A TUPLE TYPE

tuple-type              → ( ) | ( tuple-type-element , tuple-type-element-list )
tuple-type-element-list → tuple-type-element | tuple-type-element , tuple-type-element-list
tuple-type-element      → element-name type-annotation | type
element-name            → identifier

How can The Language Reference and The Generics Manifesto be in such fundamental disagreement?

A week later.
I'm getting slightly worried that there is no common understanding (even among compiler devs) of Swift's tuple types. So I'll try again to get a definitive answer on at least this question:

Should these two programs compile or not?

Program 1:

let a = (label: 123)
print(a.label + a.label)

Program 2:

let a: (label: Int) = (label: 123)
print(a.label + a.label)

(Clearly, both programs should either compile or fail to compile. With Xcode 9.4, both fail to compile, but with the default toolchains of Xcode 10 beta 1 and 2, as well as with recent dev snapshots (eg 2018-06-24), Program 1 compiles.)

3 Likes

I asked about this on Twitter and it sounds like neither of those programs should compile. As I understand it, single-element tuples are currently only allowed due to implementation quirks and should be banned altogether.

When this is eventually (hopefully) sorted out at the implementation level, I'd be in favour of a proposal to introduce single-element tuples with some kind of special identifier syntax. At this stage, though, such a proposal sounds like it would be a recipe for bugs.

1 Like

Thanks! OK, though I'm still confused, I've filed:
SR-8109 - Swift 4.2 Regression: Single-element tuple not prevented

I agree, but I would prefer if the syntax for tuples (in general, not just single-element tuples) could be entirely separated from other concepts in the language that happens to be written using parentheses.

And I still welcome any clarifications to the questions raised upthread.

1 Like

Regarding this thread, as well as Jens' July 2016 posts, about parentheses, tuples, etc.: Jens' patience and enthusiasm for this topic are remarkable, and his writing on the subject is excellent. Thank you, Jens. Also, kudos to Letan and Thomas for their efforts and sharp counterpoints. Great discussion, all around.

These issues are a big deal. No matter their outcome, they have real implications for the long-run development of the Swift language.

I would be glad to see (or, if need be, help to create) a top-down decision tree for how parentheses are parsed and type checked by the compiler. I suspect that the parser and type checker are missing subtle opportunities to infer the intent of parentheses from context. I also suspect that there are edge cases where inference is impossible. Airing that sort of document would show us the specific problems, inform the discussion, and also might lead to creative solutions from the community.

The compiler has been built and modified over a long period of time by a lot of people. My impression is that, out of practical necessity, many modifications have been made as local patches rather than systemic integrations. At this stage, any real change to how parentheses are handled probably involves a frightening amount of retooling within the compiler. The work and the attendant risks of error are daunting, but waiting has its own costs. Whatever happens, great respect is due to the core team; they do amazing work.

Please see the commentary on SR-8089. In particular, there may be some significant under-the-hood work on tuples in the near future. My supposition is that that work may clear the way for better discussions about above-the-hood aspects of the issues addressed by this thread.

Let's find a way to keep this thread near the top of the heap.

5 Likes

To circle back on this: single-element labeled tuples are now banned: Ban one element tuples harder by slavapestov · Pull Request #21343 · apple/swift · GitHub

Note that in SR-8089, you're observing "parenthesis types", which are just sugar. (T) is equivalent T and does not designate a tuple type.

5 Likes

Ah, I see that it fixed https://bugs.swift.org/browse/SR-8109 now, nice.

I'd like to echo a question I wrote in a comment to SR-8172 here:

Are there any situations where a user would benefit from being able to observe a "parenthesis type"? Isn't it always just a pointless and confusing exposure/leakage of compiler-implementation details?

Also, why should Swift even allow us to write:
let x: (Int) = 123
in the absence of single element tuples?

To me, the above makes as little sense as:
(let) x: Int = 123

I use parentheses when the return type is a function because it helps me to read the thing: (T) -> ((T) -> U). Swift should definitely allow me to write that.

If arbitrary many other keywords could be used before and after let, and if surrounding them with parentheses in certain situations were required, then yes I would also expect Swift to allow me to write non-required parentheses around let as well.

4 Likes

(() -> ()) -> () and () -> (() -> ()) are different types. Similarly () -> ().Type and (() -> ()).Type are different.

I disagree that it’s an implementation detail - quite the opposite, keeping track of parentheses actually complicated things somewhat and if it weren’t for being able to print them back out we would have no reason to track them.

1 Like

Swift (and Clang and now GCC) have a separation between "types" and "canonical types" [1]. Types retain all their "sugar" including typedefs/typealiases/etc, but canonical types represent the semantic type which have all the sugar removed. Types are propagated and meticulously maintained by the compiler in an effort to improve diagnostics and make source translation tools work better, whereas canonical types are uniqued and pointer comparable / hashable.

We decided quite a long time ago that parenthesized types should be the same as the underlying type, which makes parens "sugar" just like typealiases are. I can't imagine changing this, as it would cause significant ripples throughout the type system, including source and ABI breakage.

-Chris

[1] The idea of canonical types originated (AFAIK) from a post by @Douglas_Gregor on the GCC mailing list in the ~2006 timeframe when he was grappling with variadic templates and other C++ features. I can't find the original post, but it really is a great idea, which is why Clang got built around it and Swift followed in those footsteps.

7 Likes

Is there anyway to demonstrate this in (Swift) code?

func foo() -> ().Type {
  return Void.self
}

func bar() {}

let baz = type(of: bar)

type(of: foo)   // (() -> ().Type).Type
type(of: baz)   // (() -> ()).Type.Type
2 Likes
let foo: () -> Void.Type = { return Void.self }
let bar: () -> Void = {}

// (() -> Void.Type).Type != (() -> Void).Type
type(of: foo) != type(of: bar) // true

I realized now that I misread it as in () -> () being a different type from (() -> ())

1 Like

These are definitely the same in current Swift.

Yes, I guess they are equivalent even though the latter is a "parenthesis type", As in:

And my above question:

Could probably have been better formulated something like:

  • Is it possible to write Swift code that can tell a "parenthesis type" from its canonical type, eg (Int) from Int or (() -> ()) from () -> ()? If not, how is it meaningful for the user to be able to observe a "parenthesis type" (say in the IDE) like (Int) rather than just Int?

But I guess the answer to this is that it can be meaningful, in the same way that it is still meaningful to be able to observe eg the name of a typealias and not just the type that it aliases, even though they are equivalent and no code can be written that tells them apart.

2 Likes

Right, that's the idea.

Thanks for the writeup -- I didn't know canonical types were a @Douglas_Gregor invention :slight_smile:

Minor nitpick though:

I can't imagine that adding or removing a sugared type should have ABI impact -- mangling uses canonical types only. Or do you mean if we changed (T) to be a different type than T? That would be major breaking and quite odd from a language standpoint, I agree.