[swift-evolution] Proposal: Add SequenceType.first
kevin at sb.org
Sun Jan 3 02:04:23 CST 2016
On Sat, Jan 2, 2016, at 11:53 PM, Dave Abrahams wrote:
> > On Jan 2, 2016, at 11:26 PM, Kevin Ballard via swift-evolution <swift-evolution at swift.org> wrote:
> > On Sat, Jan 2, 2016, at 11:17 PM, Brent Royal-Gordon wrote:
> >>> `buffered` is no more problematic than `lazy` is. In fact, calling `buffered` actually doesn't have any side-effects at all (it can avoid fetching the first element until you call `first` on the result of `buffered`).
> >> If `seq` is a single-pass sequence, then `seq.buffered.first` will consume an element from `seq`, even though you only accessed two properties. That's why I call it problematic.
> >> (If calling `buffered` somehow rendered the original sequence unusable—for instance, if we had some way to express that the `BufferedSequence` takes unique ownership of its base—this wouldn't bother me as much.)
> > If `sequence` is a single-pass sequence, wrapping it in any other sequence type and then doing anything with that other sequence type makes the original sequence unusable (or rather, you can still use it but the elements yielded from any further access to the original sequence can be completely arbitrary).
> > And for the record we already have precedent for the specific case of `seq.prop1.prop2` destructively consuming the original sequence: `seq.lazy.array`.
> Yes, and there are arguments for dropping “.array” as a property. The convention is that “conversions” (ill-defined, I know) use constructor syntax, and we are currently heading towards the elimination of "convenience” interfaces that duplicate functionality, so we might end up with Array(seq).
> All that said, single-pass Sequences are just weird in that they get mutated without calling any mutating methods on them; you mutate them by calling a mutating method on a separate generator instance. In other words, they fundamentally have reference semantics. There may be some better way to address this whole area, but we’ll have to go much deeper than merely poking at the question of a `.first` property.
FWIW move-only structs (and safe immutable references) are a great solution to this. Rust is a good example. Rust's equivalent to SequenceType is IntoIterator, which has a method into_iter() that consumes the receiver and returns an Iterator. If a sequence is multi-pass, then instead of just implementing it on the type, the IntoIterator trait is also implemented on references to that type (e.g. both `Vec<T>` and `&'a Vec<T>` implement it) and sometimes on mutable references too (`&'a mut Vec<T>`). The multi-pass iterators actually yield references to the elements involved (e.g. the Iterator for `Vec<T>` yields `T`, but the Iterator for `&'a Vec<T>` yields `&'a T`). Similarly Iterator itself can be move-only if it's a single-pass iterator, but for multi-pass iterators the Iterator is either copyable or Clone-able. This scheme means that the type system documents whether a sequence/iterator is multi-pass and statically prevents you from violating that guarantee.
Of course, Swift doesn't have move-only structs and it doesn't have immutable references, but thinking long-term this does seem like a great model to be aware of.
More information about the swift-evolution