[Pitch] Conditionally conform Optional: Sequence where Wrapped: Sequence

I don't think so; it's more about the fact that Array<S: Sequence>.init(_ s: S) now silently accepts any Optional<S> where S: Sequence. The name map is already duplicated today, yet Optional.map still returns an Optional, which doesn't fit into a non-optional Sequence parameter (you'd have to unwrap it first). If Optional started conforming to Sequence, in cases like this it'd become easier to forget you have an Optional.

Isn’t this one of those “obvious” conditional conformances, though?

I am hard-pressed to think of any reasonable interpretation in any context of Optional<SequenceType>.none as a Sequence in which it should be something other than the empty sequence. At least not any example that isn’t hideously contrived. (I’m certainly open to a compelling example if you have one.)

This feels very much like Arrays being equatable when their elements are; there’s only one sensible meaning for that to have.

Not at all. The usual interpretation of optional types as a collection I am familiar with is as a collection of zero or one elements whose element type is Wrapped. The proposed interpretation seems strange to me as it artificially equates an empty sequence with .none.

1 Like

This interpretation may be usual in ML-family languages, but I'm not convinced it makes useful sense. It feels more like a clever functional hack than something that meets expectations or aids understanding. I can view Int as a collection of empty tuples, but that doesn't mean it’s useful to do so.

Can you think of a practical situation where, given thing: [Thing]?, a reasonable person would expect for x in things to loop once, instead of looping over each thing? Because I’ve certainly wanted the latter plenty of times.

1 Like

The only use I can imagine for treating it as a collection of 0/1 elements, is to share the same interface for features like map, flatMap, forEach (which would be better off as something like ifSome(then:)), isEmpty, etc.

But those things can exist without a formal conformance to Collection, and more importantly, it leads to some really dumb available API:

let x: Int? = 5

// If optional was a colleciton of 0 or 1 elements:
x.first
x.last
x.removeFirst()
x.removeLast() // two names for something that'll always be the same ... interesting
x[12] // wut
x.index(after: x.startIndex) // ???
x.count // why would anyone want this?

On the contrary, I think that's a rather unreasonable interpretation:

Recall that, in Swift, an array of arrays (or any sequence of sequences) is not automatically flattened for iteration. In other words, iterating over [[[T]]] doesn't give you elements of type T but of type [[T]].

This proposal would flatten (iteratively) iteration for a type [T]??? but not for a type [[[T]]]? in the same breath as claiming that Optional<S : Sequence> is a Sequence. That is, not only would it cause Optional not to behave like other Sequences, it would do so ironically in the conformance of Optional to Sequence.

Recall also that, in Swift, let z: T?? = nil is not equivalent to let a: T?? = .some(nil), and that this is a deliberate design decision.

Now, if let x: [T]? = nil is an empty Sequence, then what is let y: [T]? = [] in terms of what elements you'd get on iteration? Based on the principle above, y (an Optional value wrapping an empty Sequence) should be a different sequence from x: in other words, a non-empty sequence. If not empty, then the only alternative would be to conclude that y ought to consist of one element of type T? that is nil. Maybe not too bad. But the conformance Optional : Sequence where Wrapped : Sequence applies recursively. Therefore, if y has one element, then let w: [T]??? = [] must consist of three elements of type T???: .some(.some(nil)), .some(.some(nil)), .some(.some(nil)).

This proposal doesn't do that at all because it would be quite insane, and instead w, x, and y would all have equivalent sequences. However, this goes against a fundamental expectation of Optional (that U?? is not the same as U?).

No, this is not an "obvious" conformance; in fact, I believe it would be entirely inconsistent with existing Sequence and Optional behavior in Swift for the reasons above.

Yes, map and flatMap can and do exist without a formal conformance to Collection. Nor do I think Optional should conform to Collection.

But if Optional would conform to Sequence, it certainly ought not to conform in another way that's incompatible with treating it as a collection of 0/1 elements [at least, not vended by the standard library]. Not least because there would then be two incompatible methods named map (and the same for flatMap, etc.). But also because it is not merely a silly compiler limit that Optional should not conform to Sequence in two different ways: it's also not good design for Optional to have semantics that conform to Sequence in two different ways--even if one such conformance is unstated outright.

This is not "really dumb." It's not particularly useful for the concrete type, but the point is that it may enable useful generics. For the same reason, we already have a CollectionOfOne<T> type that conforms to Collection. In some ways, Optional : Collection would result in essentially a mutable version of CollectionOfOne<T>.

There's nothing wrong with x.first == x.last, as shown by CollectionOfOne. In the case of Optional, x.first == x && x.last == x, which is actually a rather elegant result. Of course, x.removeFirst() does the same thing as x.removeLast(), as you would expect for a "mutable CollectionOfOne." No one would attempt to write x[12], obviously. x.index(after: x.startIndex) == x.endIndex, as expected. And x.count would provide you with another way of telling if your Optional is nil; you won't need it for concrete work, but it's not like you can write == nil if you're working with a generic Collection.

6 Likes

@Paul_Cantrell I don't have anything to add to the excellent answer @xwu has provided.

Just +1 his post, then

Your point about the inconsistency of what would be flattened and what isn't is a good one. I concede.

But I disagree with your points about Optional as a collection. "No one would attempt to write x[12]" Sure, but the compiler lets them. You shouldn't crowbar Optional into Collection, even if you can make it work. first, last, count, etc. could all be made to work, but it's so far removed from what an Optional represents. Adding indices into the picture only makes it worse.

We don't disagree here. I don't think Optional should conform to Collection. As pointed out in another thread, promotion from T to T? would make that feature downright dangerous. My point was that, semantically, Optional can be regarded as a Collection type with either zero or one element. That's all.

Unsurprisingly, Xiaodi absolutely nailed it. I think the desired goal would be better achieved with (say) a for? keyword that iterates over a non-nil sequence and does nothing for a nil sequence. Given that for __ in __ is already syntax built to handle a Sequence, it feels like the correct approach to handle a Sequence? is parallel syntax such as for? __ in __. The same idea could be applied throughout the language; switch? for an optional enum, while? for an optional Bool, etc. Handling this issue at the language level seems better than handling it at the protocol level.

I think Optional should be array like, with either 0 or 1 element. It is useful in scientific programming were you often write func sin<S>(xs: S) -> [Double] where S: Sequence, S.Element == Double { var result: [Double]; for x in xs { result.append(sin(x)) }; return result }.

Uh, no. That's what Array.map is for. And you forgot to reserveCapacity (yet another reason to use map)

xs.map(sin)?

Strong -1. Implementing this for Sequence means that we'd have to implement it for every other protocol on the standard library, for this sort of a thing to make intuitive sense. It isn't scalable, and it's inconsistent with how Optional implements certain protocols (Encodable, for example. Would you encode an empty value, as it does now, or throw an error if the Optional is nil?).

Yes better implementation. The main point is that you want optional to be a Sequence of 0 or 1 element so that you can use your