SE-0244: Opaque Result Types (reopened)

Hi Swift Community,

The review of SE-0244: Opaque Result Types has reopened and runs through April 17, 2019.

The previous review was returned for revision, with the goal of setting the proposal in a wider context of future possible directions for the generics system. Since then, @Joe_Groff has written a post on the discussion forum, Improving the UI of Generics.

He has updated the proposal to reference that document. If you participated in the previous review, you may find this diff helpful.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me as the review manager via email or direct message on the forums. If you send me email, please put "SE-0244" somewhere in the subject line.

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • What is your evaluation of the proposal?

  • Is the problem being addressed significant enough to warrant a change to Swift?

  • Does this proposal fit well with the feel and direction of Swift?

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thank you for contributing to Swift!

Ben Cohen
Review Manager

8 Likes
  • What is your evaluation of the proposal?

As in the earlier review I am an enthusiastic +1. I am very excited to see improvements coming to the generics system and believe the improving UI roadmap is a very good one.

I do find it somewhat odd that the proposal focuses many examples on Collection while not providing the ability to constrain the Element type. I think some people reading the text may misunderstand this and assume they will be able to work with a specific Element type. These readers may be disappointed if the proposal is accepted and they find they are not able to do this. I recommend following up with support for associated type constraints (possibly using the some Collection<.Element == Int> syntax which avoids the need to name the opaque return type).

  • Is the problem being addressed significant enough to warrant a change to Swift?

Absolutely. There are still quite a few limitations in the generics model. The improving UI roadmap lays out a path to lifting some of the most important limitations while also making generics syntax much more convenient in most use cases.

  • Does this proposal fit well with the feel and direction of Swift?

Very much so.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

N/A

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I have closely followed and participated in all discussions.

2 Likes

Here's the diff for those interested.


My opinion is mostly unchanged from before, except I can now appreciate the "some" name better now that we have "any" as a counterpart in sight for the future.

The most worrisome aspect for me remains the inability to refer to the opaque type by name, which makes composability difficult. I proposed earlier that #returnType(of: someFunction) could be used for that, but the UI of Generics document made me think of a more ergonomic syntax:

struct DrawingBoard {
    var shape: some Shape from someFunction
    init() { shape = someFunction() }
}

Perhaps it's too much to expect this to be part of this proposal, but I believe this is quite important to have when working with opaque types.

Overall I'm positive about this proposal.

2 Likes

This would be easy to add. In fact, the implementation includes an @_opaqueReturnTypeOf attribute for compiler-internal use by printed interfaces, since it's necessary in order to represent associated types that were inferred to opaque return types (although it uses mangled names for full specficity so isn't ergonomic for end user use at all). From what I've seen of Rust adoption of impl T in Rust code and from asking Rust community members, it doesn't seem like a huge problem not to be able to refer to the type names in practice.

3 Likes

I'll just add that "some Shape" by itself not working as a type is a bit strange at first glance, and that if the compiler was to helpfully suggest that you write "some Shape from <function>" to disambiguate it would make the concept of an opaque type easier to grasp to the uninitiated. Perhaps you won't need it often, but it'd be a good teaching tool.

4 Likes

How can we specify the first and the second types in the returned tuple of the function below using #returnType(of:)?

func makeAnimals() -> (some Animal, some Animal) {
    return (Cat(), Dog())
}

Syntax like #returnType(of: ...).0 can be.

let animals = makeAnimals()
let cat: #returnType(of: makeAnimals).0 = animals.0
let dog: #returnType(of: makeAnimals).1 = animals.1

We can also think about similar cases.

func makeOptionalAnimal() -> (some Animal)? {
    return Bool.random() ? Cat() : nil
}

let cat: #returnType(of: makeOptionalAnimal).Wrapped = makeOptionalAnimal()!
1 Like

The @_opaqueReturnTypeOf attribute, and the mangled representation of opaque types, currently uses an ordinal like this, yeah. If we adopt the dual-generic-signature notation, like:

func makeAnimals() -> <T: Animal, U: Animal> (T, U) { ... }

then maybe you could use the names too, like #returnType(of: makeAnimals).T.

4 Likes

Good point @koher. I guess with my syntax you could use this to extract the type of the first animal:

let cat: ((some Animal, some Animal) from makeAnimals).0

And maybe we should allow _ as a shortcut:

let cat: (_ from makeAnimals).0

I fear we're going a bit off-topic with all these details, as getting the return type in one way or another is not part of the current proposal.

I strongly agree with adding opaque result types to Swift. However, supporting "reverse generics" first seems better for me because

  1. Swift has already supported generics. I think having generics and "reverse generics", in the transition period to add features step by step, makes sense better than having only generics and opaque result types.
  2. I think opaque result types can be understood easily when they are considered as sugar of "reverse generics". It seems obvious that this proposal should have referred to "reverse generics".
  3. "reverse generics" is more expressive even if some features described in "Future Directions" are implemented, e.g. a function below can't be implemented with the some syntax in my understanding, and tackling "reverse generics" first can prevent lack of consideration about internal implementation.
func makeAnimals() -> <A: Animal> (A, A) {
    return (Cat(), Cat())
}

About the choice of the keyword, I love the keyword some as a dual to any. I also support the keyword some for the reason shown below.

func foo1() -> some Foo { ... }
func foo2() -> some Foo { ... }

var a = foo1()
let b = foo2()
a = b // Error: `some Foo` and `some Foo` can be read naturally as *different* types linguistically.

It is clearer when compared with existential types.

func foo1() -> any Foo { ... }
func foo2() -> any Foo { ... }

var a = foo1()
let b = foo2()
a = b // OK: `any Foo` and `any Foo` can be read naturally as *same* types linguistically.

Yes. I think it is a missing piece of Swift.

Yes. Especially I think adding the keyword some is an important step considering making existential types explicit with the keyword any.

N/A

I have been briefly checking discussions since the first round of the review. I also checked "Improving the UI of generics" and the diff of the proposal from last round.

6 Likes
  • What is your evaluation of the proposal?

Overall +1, but deferring handling Optionals will impede my ability to fully make use of this feature.

For better or for worse, Optionals have a special place in the type system, and there's this weird boundary with them:

  1. There's no IsOptional type of protocol (in the language or standard library) unless I write my own to add it to the opaque type signature
  2. There's no way of returning an optional in a reverse generic function - when I might want to. This would be an "arbitrary" restriction from my standpoint.
  • Is the problem being addressed significant enough to warrant a change to Swift?

Yes, definitely. Part of my development time is spent understanding the huge landscape of names and trying to comprehend how they piece together and what I should or should not be using directly in documentation (which may not be the greatest)

  • Does this proposal fit well with the feel and direction of Swift?

Yes, it matches the general use and reliance on an inferred type system that gives us static-type safety without verbosity outside of conveying the most important information - the interface.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

This is my first real exposure to this type of feature, outside of the mentioned C++14 auto keyword.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I read the whole document, the Generics UI document, and kept-up with both the pitch and first review threads.

1 Like

Big +1 for this proposal. The post from @Joe_Groff was highly elucidating and made a fantastic case for this feature. I, like @michelf, am very much interested in the ability to name generic result types and appreciate the possible future directions that this proposal allows. I’m also far more supportive of the some keyword with an any counterpart, as others have mentioned.

+1!

  • What is your evaluation of the proposal?

Great!

  • Is the problem being addressed significant enough to warrant a change to Swift?

Definitely, long-standing problem that is now being addressed without penalising performance. In SwiftNIO we don’t expose the concrete Channel (ServerSocketChannel vs SocketChannel etc) types to you and they’re existentials right now which is not optimal (but we get away with it).

  • Does this proposal fit well with the feel and direction of Swift?

Absolutely

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

No

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Read the first version in depth, this some slightly less so but seems to be mostly the same :slight_smile:

Before I go further, a minor nit:

The second paragraph in the "Proposed Solution" section contains an error. Specifically, it contains the following (grammatically invalid) sentence: "Unlike an existential, though, clients still have access to the type identity, so values This allows the library to provide a potentially-more-efficient design that leverages Swift's type system, without expanding the surface area of the library or making implementors of the library's protocols rely on exposing verbose implementation types.".

It seems that this was intended to be two sentences, and the first one was truncated which caused the period to be missed as well. For the benefit of future us looking backwards it'd be nice if @Joe_Groff could add the missing sentence back!

Along with @johannesweiss I am an enthusiastic +1. I have been thinking of attempting to add more expressiveness to SwiftNIO's current Bootstrap abstractions, but this expressiveness has been limited by the fact that we extensively use Channel as an existential container to hide the concrete type, which prevents us providing associatedtypes on the Channel protocol. This change would lift that restriction and make it easier for us to allow users to program generically over both Channels and Bootstraps.

Yes. The proposal makes it clear that in addition to providing a helpful tool for libraries like SwiftNIO, it is reasonable to think of Opaque Result Types as a currently-absent dual of caller-selected generics. Adding this missing symmetry to the language is a good thing.

I believe so. The specific spellings proposed in this pitch are novel, but there is a clear roadmap for how they will evolve, and the place we'd end up seems reasonably in keeping with Swift.

N/A

In-depth reading of both the original proposal and this one, as well as the follow-up threads.

1 Like

Thanks for reporting the typo! I'll fix that.

1 Like

Giant +1. This would be major step towards alleviating my largest battle with the Swift type system.

Yes. As stated above, the restriction on not being able to use protocols with associated types and Self requirements in regular type locations is the biggest pain point for me when implementing complex systems. While this proposal does not completely solve that issue, it goes a long way for the most common instances.

Yes. "some" reads like English and expresses the intent clearly. Seems like a natural extension to Swift.

N/A

I read the Generics UI document, the proposal, and reviewed the existing reviews. I am also familiar with the Generics Manifesto.

I'm in favour of this change. I think that mitigating the complexity of the return types any time you write some code around Collections is a needed improvement to the language.

Yes. I've read the previous thread and found the commentary about opaque typealias worth considering, but honestly, I think that the major impact that this proposal would have on existing "application" will be to reduce the noise around Collection types. And for that scenario typealiases don't improve anything. I'm happy to get this as is and improve the support with typelias if needed in the future.

I think so, it fills an important gap on the language. On the original thread I was more in favour of opaque but seeing the future of the generics system this makes sense.

Hiding the concrete implementation of something is quite common in some libraries and other languages, things like "tokens".

Followed the topic since the original discussion, read those threads and the new post about generics.

In the previous review thread, I said:

I'm in favor of the feature and I think the proposal covers enough ground to be a useful addition to the language.

However, the vague syntax suggestions in the "Future Directions" section concern me. I'm worried that the -> some P syntax may be adequate for this proposal's purposes, but may not extend cleanly to uses we envision in the future. Maybe there is some larger scheme we could fit the some syntax into; if so, I'd like to see a sketch of that scheme before we commit to it here. (More thoughts on that in the Protocol<.AssocType == T> thread.)

Otherwise, I support it.

The "generics UI manifesto" has addressed my future-directions-related concerns and I support its overall vision. I also still support the feature itself.

I'm a little hesitant about the decision to introduce the some syntax with this proposal and defer the full "reverse generics" syntax for later, but I ultimately think it's the right decision. Once we support (...) -> <T: P> T syntax, people will immediately try to use it in ways the current implementation doesn't support. It would be a poor experience for users to have so many different things be straightforward to express but unsupported, and I don't think it makes sense to delay opaque result types until they can support every bell and whistle the generics system has to offer. Therefore, shipping opaque result types with only the some syntax is our best option.

I look forward to seeing some of the other results of the generics UI manifesto. some in parameter position seems like especially juicy and low-hanging fruit—I've had to explain to several different people why inout P parameters don't do what they want, and inout some P would be a very nice solution to offer them.

1 Like

Strong +1. I don't have any strong feelings on the specific syntax being proposed, so I'll leave arguing over that to other folks, but I'm very much in favor of the spirit of this pitch.

Yes absolutely.

I believe so. I'm a heavy user of Swift's generics and this feature would resolve a good portion of the issues I face. The other features listed in the Generics UI post would solve most of the rest.

I haven't

I read the generics manifesto, generics UI post, proposal, diff, most of the associated threads.

What is your evaluation of the proposal?
I would give this it a YES

Is the problem being addressed significant enough to warrant a change to Swift?
As a developer who develops and maintains a few libraries, I would say YES
Does this proposal fit well with the feel and direction of Swift?
I believe it does

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
[N/A]

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
I would like to try a try since it looks promising. However I couldn't load the toolchain linked in the Swift Evolution. Where can I get a toolchain to try this? I have a few questions but did found answers to those question in both the a reading in the SE and read through the old proposal thread.

The new manifesto makes it much clearer where this fits and and why it’s not an edge case with way too much conceptual overhead, which is how it looked before. I do think there will be a pedagogical problem, but it no longer looks insurmountable.

I’m a little bit worried that the lack of composability, especially the inability to use Optional<some T>, will be a sharp edge.

3 Likes