Thinking a bit further on this, the syntax could be extended to allow for referencing the opaque type from other contexts (allowing constructions indicating “these two functions return the same opaque type”) by doing something like:
makeIntCollection() -> makeMeACollection<Int>(_:).Q { // or makeMeACollection<Int>(_:).Result if result type is anonymous?
return makeMeACollection(Int.self)
}
I’m also perfectly fine with the result type being unable to be referenced if it isn’t named explicitly.
Previously we’ve talked about enabling that using opaque type aliases. As we discuss syntax it’s worth keep that possible enhancement in mind. Does some work as well there as it does in the return type of a function signature? I’m not sure...
I did read some of that discussion. My thinking with this syntax is that it avoids the litany of typealiases that have names like ResultTypeOfSomeMethod. Since the opaque type is tied to the method itself, why not scope the name of that type around the method itself as well? The typealias approach could be an alternative as well if the user doesn’t want to have one method appear somehow “subordinate” to another since it inherits the result type.
I think the usage of some there works fine, in any case:
typealias some CollectionType<T>: MutableCollection & RangeReplaceableCollection = [T]
I share these concerns. It is true that "some" complements "any," but neither of these words are ultimately very descriptive of the concepts to which they pertain:
Not only is "some" reused as an Optional case, even in an alternative universe where that isn't the case, there is nothing a priori that tells a reader that a "some" type (not a "sum type"!) is an opaque type. This is also my criticism of "any" types, which do nothing to tell the user that they are used for type erasure based on the name alone. At least, however, the erasure is made manifest by the lack of generic parameters.
Now, suppose a reader encounters the keyword "some" and tries to look it up. How will they know which chapter to turn to in The Swift Programming Language? What can they expect to find if they google "Swift some"?
I do not see any reason to try to be clever in the choice of keyword. If this particular feature will be described as an "opaque type," then it is appropriate to use the keyword opaque or opaquetype, just as we use the keyword typealias for type aliases, associatedtype for associated types, and class for classes--without worrying that a user would be confused that class Room models a classroom.
I mused about words like abstract and interface, which are obviously problematic but not entirely unjustifiable.
As others alluded to, "some" has a usability problem in communication. Imagine two programmers talking about their code and a "some" type comes up, one would have to either work really hard to make the context clear or remember to use "opaque" to describe it, and hope the other party to make the connection...not being able to search on the internet would be a symptom of the same problem (although I think search algorithms can be trained to overcome it) .
What about some variation of ‘Any’? I think it reads very clearly. As far as the declaration goes, I don’t think the user of the function needs to care if the abstract, opaque result is always the same type or not; that’s information that the compiler cares about. You just get an Any<Shape> and treat it like a kind of less-restricted existential.
As far as my understanding of this core feature goes, the opaque type is some concrete type that is erased and fixed to the given constraints and type of the return type. In that sense, can we maybe call it erased which is a concept most Swift developer already are familiar with, it has no ambiguity like some or opaque!?
It seems like some people here think of this as a cute little convenience feature, but the impact it has on how types work is quite large and ultimately, hurts composability a lot.
It isn't even powerful in terms of decluttering code since the wrapper types still have to be written, and it would be particularly horrible if the standard library would make use of it instead of the existing wrapper-types for reversed collections etc – because of the already mentioned composability issues.
Here is one of the main problems that is inherent to the proposed concept:
protocol A {
associatedtype B
}
struct C { }
func d() -> opaque A where B: C {
return ...
}
//just some kind of convenience method that always ends up calling d:
func e() -> opaque A where B: C {
//other stuff...
return d()
}
let f = [d(), e()] //OH NO, ERROR, NOT THE SAME TYPES (even though they actually are, but we can't express that.)
I like some as much as anyone, but what it really got me thinking about is perspective. (This is going somewhere, I promise.)
some reads as a promise on the part of the author of the method: “I’m giving you something of this type”. The client doesn’t know concretely what; the author (and the compiler) do.
Consider the consumer’s side of that promise. In terms of what methods you can call on the returned type, how you can let it out, or basically what the compiler prevents you from doing with the opaque type, it’s more akin to “this method wants to give me anything of this type, I have to handle whichever one it actually is the same.”
But those two phrasings aren’t mutually exclusive, you know? They’re two sides of the same coin. (“Can I interest you in any beverages?” “Some wine, please”) I then recalled that this fancy glass rectangle I’m typing this on is also a thesaurus; indeed, “some” and “any” show up high in one another’s related words.
Which brings me to: If opaque return types and the syntax for existentials are such distinct design spaces that we’re hunting for a keyword for the former, why would we choose one that’s arguably interchangeable with ones already used for the latter? Won’t that be tremendously confusing to someone seeing both features for the first time?
Or, the inverse: if we are to come to consensus on some (yay! consensus!), doesn’t its closeness to “any” imply a superset/subset overlap than is (perhaps) more than we are intending intend for the “I don’t want to give you a concrete type for this right now” space?
Maybe it’s a crackpot theory, or maybe we should’ve stuck with opaque we were ahead. Either way, can’t wait to see a proposal moving forward.
I am not sure that we are allowed to call e() as return in d(), since the pitch states that the concrete type is needed as a return type in opaque functions.
Anyway, as I understand it, it's the selling point of opaque types: homogeneity vs heterogeneity of generalized existentials.
Any thoughts on more symbolic syntax? How about single-quoted 'T' instead of opaque T. This feature is meant to be used pretty commonly, right?, so terser syntax with semantic meaning might be preferable to a keyword. These could still be described as opaque types without the awkward clashes with English like the opaque Shape example mentioned above.
(I'm not wholly sold on this idea, but seems worth raising whilst we are spitballing terms...)
After sleeping on this I agree. Using some would hurt searchability and describes the feature no better than opaque does. If we go with opaquetype then IMO the type should have to be given a name explicitly, so -> opaquetype Collection where _.Element == Int would be disallowed.
There have been proposed syntaxes that would allow this sort of behavior, either via a typealias or some other annotation of the return type, so that something like this would be legal:
typealias opaque Q: A where B: C = MyConcreteType
func d() -> Q {
return ...
}
func e() -> Q {
return d()
}
func f() -> e().ResultType {
e()
}
let f = [d(), e(), f()] // OK!
Does this address your concerns?
For the same reason I've soured somewhat on some, I think this would be even worse. Single quotes don't suggest any meaning at all, and don't even really mirror any other syntax related to this feature. With Array and Dictionary shorthand, you can at least argue that the terse syntax mirrors the construction of a literal, and with Optional the shorthand mirrors Optional chaining. Furthermore, with the forward motion on character integer literals, we would be introducing two entirely unrelated uses of single quotes.
opaque typealias Q: (A where B: C) = MyConcreteType
In fact, I might go so far as to discourage the use of anonymous opaque types in function signatures. It seems far more desirable to have the typealias written out separately.
I like the parentheses but am iffy on having them be required, that seems more appropriate to offer as a choice if teams want to adopt it.
I would be in support of disallowing it outright as long as the following is acceptable:
func f() -> opaque Q: Collection where Q.Element == Int {
[Int]()
}
I think being able to specify the opaque type inline is important for avoiding a bunch of typealias opaque FReturnType: ... = [Int] style declarations. I believe that the solution for that is allow for a way to reference directly "the return type of function f()," which could be useful in contexts other than opaque types anyway.
I agree that opaque typealias reads much better than typealias opaque. I don’t think some typealias makes sense at all and typealias some doesn’t really work because the “some” applies to the constraints, not the name of the typealias. With that in mind, opaque might be the best choice after all.
opaquetype has also been mentioned, but that would be awkward when used with typealias: opaquetype typealias is not only redundant, but immediately repetitive.
I guess I group things differently in my head. I don’t think of typealias opaque... as a construction at all, really. Rather, opaque Q: Collection where Q.Element == Int reads as “an unknown concrete type conforming to Collection with Int as its Element. Thus, typealias opaque just says typealias <unknown type> = <concrete type> which seems very similar to how typealias is used already. Breaking up opaque from the type definition might read more grammatically, but it doesn’t really describe what’s going on. The type is opaque, not the typealias. Also, unless we disallow the “inline” definition of opaque types in the function declaration, having opaque attached to the type in one context but attached to typealias in another is inconsistent just for the sake of making the typealias more sentence-y.
My understanding is that the goal of the typealias would be to fix the concrete type so that when the alias is used in different methods, you can guarantee that they both return the same type. As written above, you would have non-equivalence between
typealias Q = A where B: C
and
typealias P = A where B: C
Which seems wrong given the implications of an alias. The other option would be that d() and e() do not have compatible return types, as if you had defined opaque inline.