The proposal doesn't mention them, because the opaque type feature doesn't exist yet so it isn't an actual alternative to consider (and waiting is not an option for ABI reasons). But I want to push back a bit on the assumption, both here and in the pitch, that opaque types would be better, even if we had them. The meme that opaque types are always preferable is a false one.
Keeping an air of mystery about what you are returning is not an end in itself. It is a means to two ends — of hiding complexity, and of preserving source compatibility under change.
We hide complexity to help the user understand what is important. If the exact implementation details of what type is returned are likely to distract and confuse the user, and that adding an extra type into the library would be noise, then an opaque type is better. But this comes at a cost. Opaque types are also less clear about exactly what is going on. The question is, is that lack of clarity worth it to reduce the noise.
In the recent example of LazyCompactMap
, it was worth it. The lazy sequences are a bit strange, and the laziness would still be spelled out explicitly in the opaque type (because it would be an opaque implementation of LazySequenceProtocol
). Lazy types are often wrapped around other lazy types in a chain and those types get unwieldy, so the opaqueness helps there too.
In this case, it's different, because this method is shadowed later in the hierarchy. That shadowing isn't great — shadowing also causes confusion and mishaps — but there's no good alternative. When shadowing like this, it's important to be as clear as possible at every point. You ought to know, when you call suffix, that you have called a version that returned an array not a slice. You ought to know, when you call DropFirstSequence
, that you've got a lazily evaluated sequence wrapper. You shouldn't have to wonder what the mystery thing you got back actually is. That would be unhelpful.
It would also be unhelpful, when the contents of that mystery package is an array, to not give the user an actual array. It's really nice to be given an array! Arrays have really important properties you can usefully take advantage of. Arrays are currency types and you should prefer to return them when they're the best option. Even if you think there might be a better implementation in the future, you should think really hard about whether that future potential outweighs the big user benefit of returning an array.
Opaque types also help preserve source stability when you want to change things in later releases. They will let you avoid brittle interfaces when you want to refactor your code. This will also be great for non-ABI-stable libraries; I wish we'd had this feature during the early development of the standard library. But once a library declares ABI stability, it's not so useful, because symbols are forever. So even if we want to change these methods to return something different in the future, we'd have to keep the old methods around until the next ABI break. Yes, recompiling means you'll pick up the new versions, but only when combined with minimum deployment targets. And there's also a real risk of confusion: if the behavior of the new version changes significantly (and presumably it does, to justify changing it), then you're in this odd situation where behavior changes in an opaque way, determined at a distance by things like which compiler you used and what availability settings kick in. That's a pretty bad unintended consequence.
tl;dr: opaque types are great for lots of use cases, but they're not a panacea.