<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On Fri, Jul 1, 2016 at 11:51 AM, Dave Abrahams via swift-evolution <span dir="ltr">&lt;<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>&gt;</span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div class="HOEnZb"><div class="h5"><br>
on Fri Jul 01 2016, Matthew Johnson &lt;matthew-AT-anandabits.com&gt; wrote:<br>
<br>
&gt;&gt; On Jun 30, 2016, at 12:26 PM, Dave Abrahams &lt;<a href="mailto:dabrahams@apple.com">dabrahams@apple.com</a>&gt;<br>
&gt; wrote:<br>
&gt;&gt;<br>
&gt;&gt;<br>
&gt;&gt; on Wed Jun 29 2016, Haravikk &lt;swift-evolution-AT-haravikk.me&gt; wrote:<br>
&gt;&gt;<br>
&gt;<br>
&gt;&gt;&gt;&gt; On 29 Jun 2016, at 00:10, Matthew Johnson via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>&gt; wrote:<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; Swift is a language that embraces value semantics.  Many common<br>
&gt;&gt;&gt;&gt; iterators *can* be implemented with value semantics.  Just because we<br>
&gt;&gt;&gt;&gt; can’t implement *all* iterators with value semantics doesn’t mean we<br>
&gt;&gt;&gt;&gt; should require them to have reference semantics.  It just means you<br>
&gt;&gt;&gt;&gt; can’t *assume* value semantics when working with iterators in generic<br>
&gt;&gt;&gt;&gt; code unless / until we have a way to specify a value semantics<br>
&gt;&gt;&gt;&gt; constraint.  That’s not necessarily a bad thing especially when it<br>
&gt;&gt;&gt;&gt; leaves the door open to interesting future possibilities.<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; -Matthew<br>
&gt;&gt;&gt;<br>
&gt;&gt;&gt; I&#39;m kind of undecided about this personally. I think one of the<br>
&gt;&gt;&gt; problems with Swift is that the only indication that you have a<br>
&gt;&gt;&gt; reference type is that you can declare it as a constant, yet still<br>
&gt;&gt;&gt; call mutating methods upon it, this isn&#39;t a very positive way of<br>
&gt;&gt;&gt; identifying it however. This may be more of a GUI/IDE issue though, in<br>
&gt;&gt;&gt; that something being a class isn&#39;t always that obvious at a glance.<br>
&gt;&gt;&gt;<br>
&gt;&gt;&gt; I wonder, could we somehow force iterators stored in variables to be<br>
&gt;&gt;&gt; passed via inout? This would make it pretty clear that you&#39;re using<br>
&gt;&gt;&gt; the same iterator and not a copy in all cases, encouraging you to<br>
&gt;&gt;&gt; obtain another if you really do need to perform multiple passes.<br>
&gt;&gt;<br>
&gt;&gt; I&#39;m going to push single-pass iteration on the stack briefly and talk<br>
&gt;&gt; about the topic that&#39;s been under discussion here: infinite multipass<br>
&gt;&gt; sequences.<br>
&gt;&gt;<br>
&gt;&gt; ## Fitting “Infinite Multipass” Into the Model<br>
&gt;&gt;<br>
&gt;&gt; It remains to be decided whether it&#39;s worth doing, but if it&#39;s to<br>
&gt;&gt; happen<br>
&gt;<br>
&gt; I definitely think it’s worth doing.<br>
<br>
</div></div>Opinions are nice, but rationales are better.  How will we understand<br>
*why* it&#39;s worth doing?<br>
<div><div class="h5"><br>
&gt; I really appreciate the attention that the library team has given to<br>
&gt; this.<br>
&gt;<br>
&gt;&gt; , the standard library team thinks the right design is roughly<br>
&gt;&gt; this:<br>
&gt;&gt;<br>
&gt;&gt;  /// A multipass sequence that may be infinite<br>
&gt;&gt;  protocol Collection {<br>
&gt;&gt;<br>
&gt;&gt;    // Only eager algorithms that can terminate available here<br>
&gt;&gt;    func index(where predicate: (Element)-&gt;Bool) -&gt; Index<br>
&gt;&gt;<br>
&gt;&gt;    // all lazy algorithms available here<br>
&gt;&gt;    var lazy: ...<br>
&gt;&gt;<br>
&gt;&gt;    var startIndex: Index<br>
&gt;&gt;    var endIndex: Index // possibly not reachable from startIndex<br>
&gt;&gt;<br>
&gt;&gt;    associatedtype SubSequence : Collection<br>
&gt;&gt;    // do we need an associated FiniteSubsequence, e.g. for prefixes?<br>
&gt;&gt;  }<br>
&gt;&gt;<br>
&gt;&gt;  protocol FiniteCollection : Collection {<br>
&gt;&gt;<br>
&gt;&gt;    // All eager algorithms available here<br>
&gt;&gt;    func map(...) -&gt;<br>
&gt;&gt;    var count: ...<br>
&gt;&gt;  }<br>
&gt;&gt;<br>
&gt;&gt;  protocol BidirectionalCollection : Collection { ... }<br>
&gt;&gt;<br>
&gt;&gt;  protocol RandomAccessCollection : BidirectionalCollection { … }<br>
&gt;<br>
&gt; Does this design entirely break with the relationship between<br>
&gt; collections and iterators (dropping `makeIterator` as a protocol<br>
&gt; requirement)?  If so, would for..in (over collections) be built on top<br>
&gt; of indices and use `formIndex(after:)`?  Would it require a finite<br>
&gt; collection (unless we add `until` to the language and then allow<br>
&gt; `for..in..until` to work with infinite collections)?<br>
<br>
</div></div>All of these points are up for discussion.  John McCall pointed out to<br>
me that an index-based for..in would make it possible to implement<br>
<br>
  for inout x in y { mutate(&amp;x) }<br>
<span class=""><br>
&gt; Would we still retain `IndexingIterator`even if we break the<br>
&gt; relationship in the protocol requirements?<br>
<br>
</span>Yes: it should be possible to implement Collection algorithms in terms<br>
of Iterator algorithms, and IndexingIterator provides the means.  That<br>
said, I think the makeIterator requirement does little harm, especially<br>
when it can be defaulted for Collections.<br>
<span class=""><br>
&gt; Would it still be possible to do things like zip a multi-pass sequence<br>
&gt; with a single-pass sequence (assuming we keep single-pass sequences or<br>
&gt; add them back eventually)?  This seems like a use case worth<br>
&gt; supporting in some way.<br>
<br>
</span>Yes.  If you can create an Iterator from a Collection, and you can zip<br>
Iterators, you can do this.<br>
<span class=""><br>
&gt; One subtle change I think this implies is that things like<br>
&gt; `LazyFilterSequence` can implement `makeIterator` with constant<br>
&gt; complexity, deferring the O(N) complexity to the first call to `next`.<br>
<br>
</span>I don&#39;t believe that&#39;s a difference, though I could be wrong.<br>
<span class=""><br>
&gt; `startIndex` for `LazyFilterCollection` currently has O(N) complexity.<br>
&gt; The complexity of a complete iteration doesn’t change and probably<br>
&gt; isn’t a big deal, but it’s worth noting.<br>
<br>
</span>Filtered collection views always require a bit of hand-waving around<br>
performance guarantees; I don&#39;t think that changes.<br>
<span class=""><br>
&gt; I’ve been looking at some code that wraps a sequence and considering<br>
&gt; how it would be impacted.  With iterators it looks like this:<br>
&gt;<br>
&gt; guard let element = base.next()<br>
&gt;   else { return nil }<br>
&gt;<br>
&gt; With collections and indices it would look something like this:<br>
&gt;<br>
&gt; base.formIndex(after: &amp;index)<br>
&gt; guard index != baseEndIndex<br>
&gt;    else { return endIndex }<br>
&gt;<br>
&gt; let element = base[index]<br>
&gt;<br>
&gt; That’s not too bad but it is more verbose.<br>
<br>
</span>Sequence today is a single-pass thing.  If you are wrapping Sequence<br>
today presumably you&#39;d wrap an Iterator tomorrow, and you wouldn&#39;t have<br>
to deal with indices.<br>
<span class=""><br>
&gt; If we’re going to push people towards collections and indices we<br>
&gt; should try to make common patterns like “update the iteration state<br>
&gt; and return the next element if there is one&quot; simpler.<br>
<br>
</span>That&#39;s IndexingIterator.<br>
<span class=""><br>
&gt; This could be accomplished with an extension method along these lines:<br>
&gt;<br>
&gt; guard let element = base.formIndex(after: &amp;index,<br>
&gt; .returningOptionalElement)<br>
&gt;     else { return endIndex }<br>
&gt;<br>
&gt; With an implementation something like:<br>
&gt;<br>
&gt; enum FormIndexResult {<br>
&gt;     .returningOptionalElement<br>
&gt; }<br>
&gt; extension Collection {<br>
&gt;     func formIndex(after i: inout Self.Index, _ result:<br>
&gt; FormIndexResult) -&gt; Self.Element?<br>
&gt; }<br>
&gt;<br>
&gt; This would provide similar functionality to `IndexingIterator` without<br>
&gt; coupling the storage of `elements` and `position` (which is important<br>
&gt; if you’re wrapping a collection and need to wrap the collection and<br>
&gt; its indices independently).<br>
<br>
</span>I&#39;m afraid I don&#39;t understand.  Could you be more explicit about what<br>
you have in mind?<br>
<span class=""><br>
&gt;&gt; Q: Why should there be indices on an infinite multipass sequence?<br>
&gt;&gt; A: Because the operations on indices apply equally well whether the<br>
&gt;&gt;   sequence is finite or not.  Find the index of a value in the<br>
&gt;&gt;   sequence, slice the sequence, find again, etc.<br>
&gt;&gt;<br>
&gt;&gt; Q: Why is there an endIndex on an infinite seque?<br>
&gt;&gt; A: So you can write algorithms such as index(where:) once.<br>
&gt;&gt;<br>
&gt;&gt; Q: Why not allow endIndex to have a different type from startIndex?<br>
&gt;&gt;<br>
&gt;&gt; A: It appears to offer insufficient benefit for the associated<br>
&gt;&gt;    complexity in typical usage.  A classic use case that argues for a<br>
&gt;&gt;    different endIndex type is the null-terminated C string.  But you<br>
&gt;&gt;    can&#39;t index one of those safely without actually counting the<br>
&gt;&gt;    length,<br>
&gt;&gt;    and once you&#39;ve done that you can make the endIndex an Int.<br>
&gt;<br>
&gt; It’s also worth nothing that we can use `Optional` with `nil` as the<br>
&gt; `endIndex` sentinel if necessary.<br>
<br>
</span>True, that&#39;s a useful technique when there&#39;s no underlying storage in<br>
the collection (e.g. a fibonacci sequence)<br>
<div><div class="h5"><br>
&gt;&gt;<br>
&gt;&gt; ## Single Pass Iteration<br>
&gt;&gt;<br>
&gt;&gt; The refinement relationship between Sequence and Collection is<br>
&gt;&gt; problematic, because it means either:<br>
&gt;&gt;<br>
&gt;&gt; a) algorithms such as map on single-pass sequences claim to be<br>
&gt;&gt;   nonmutating even though it&#39;s a lie (status quo)<br>
&gt;&gt;<br>
&gt;&gt; b) those algorithms can&#39;t be used on immutable (“let bound”)<br>
&gt;&gt;   multipass sequences. IMO that would be totally unacceptable.<br>
&gt;&gt;<br>
&gt;&gt; If we drop the refinement, we can have a saner world.  We also don&#39;t<br>
&gt;&gt; need to separate Sequence and Iterator anymore.  We can simply drop<br>
&gt;&gt; Sequence altogether, and the protocol for single-pass iteration<br>
&gt;&gt; becomes Iterator.<br>
&gt;<br>
&gt; Makes sense to me.<br>
&gt;<br>
&gt;&gt;<br>
&gt;&gt; ### Mutation and Reference Semantics<br>
&gt;&gt;<br>
&gt;&gt; Everything in Swift is copiable via `let copy = thing` (let&#39;s please<br>
&gt;&gt; not argue over the definition of copy for classes; this is the one<br>
&gt;&gt; built into the lowest level of the language—I refer to the other one,<br>
&gt;&gt; that requires allocation, as “clone”).<br>
&gt;&gt;<br>
&gt;&gt; Anything you do with a sequence that&#39;s truly single-pass mutates the<br>
&gt;&gt; sequence *and of its copies*.  Therefore, such a type *fundamentally*<br>
&gt;&gt; has reference semantics. One day we may be able to model single-pass<br>
&gt;&gt; sequences with “move-only” value types, which cannot be copied. You<br>
&gt;&gt; can find move-only types in languages like Rust and C++, but they are<br>
&gt;&gt; not supported by Swift today.  So it seems reasonable that all<br>
&gt;&gt; Iterators in Swift today should be modeled as classes.<br>
&gt;<br>
&gt; I think this makes a lot of sense in the model you are proposing.  All<br>
&gt; multipass structures are collections.  Any sequence that can only<br>
&gt; support a single pass is modeled as an iterator which inherently has<br>
&gt; identity.  Making this distinction strong will prevent any confusion.<br>
&gt;<br>
&gt;&gt; The fact that Swift doesn&#39;t have a mutation model for classes,<br>
&gt;&gt; though, means that mutating methods on a class constrained protocol<br>
&gt;&gt; can&#39;t be labeled as such.  So consuming operations on a<br>
&gt;&gt; class-constrained Iterator protocol would not be labeled as mutating.<br>
&gt;&gt;<br>
&gt;&gt; The standard library team is currently trying to evaluate the<br>
&gt;&gt; tradeoffs in this area.  One possibility under consideration is<br>
&gt;&gt; simply dropping support for single-pass sequences until Swift can<br>
&gt;&gt; support move-only value types and/or gets a mutation model for class<br>
&gt;&gt; instances.  It would be very interesting to know about any real-world<br>
&gt;&gt; models of single-pass sequences that people are using in Swift, since<br>
&gt;&gt; we don&#39;t supply any in the standard library.<br>
&gt;<br>
&gt; I’m happy to see you mention a mutation model for class instances!  (I<br>
&gt; don’t mean to sidetrack the discussion, but would love to see that<br>
&gt; someday)<br>
&gt;<br>
&gt; I don’t have any objection to dropping support for single-pass<br>
&gt; sequences temporarily.  It’s possible that I would feel differently if<br>
&gt; I was making use of them in my own code but I’m not.<br>
<br>
</div></div>On second thought, I believe it is important to have a way to support<br>
existing “partially formed” multipass sequences that don&#39;t expose<br>
copying or equality for their iteration states.  Iterator is the right<br>
way to do that.  So I think we need to keep Iterator around.<br>
<span class=""><br>
&gt; In the meantime, people would be able to implement their own protocol<br>
&gt; for single pass sequences.  What they would lose is for..in as well as<br>
&gt; the standard library algorithms.  I’m not sure how many people this<br>
&gt; would impact or how big the impact would be for them.  We have seen a<br>
&gt; couple of examples in this discussion, but probably not enough to<br>
&gt; asses the overall impact.<br>
&gt;<br>
&gt; One thing you don’t mention here is a distinction between finite and<br>
&gt; infinite single-pass sequences (iterators).  I don’t know if the<br>
&gt; finite / infinite distinction is as important here, but wanted to<br>
&gt; point it out.  Obviously if we remove support single-pass sequences<br>
&gt; now we could defer that discussion until we’re ready to bring back<br>
&gt; support for them.<br>
<br>
</span>There are a few possible answers I can think of:<br>
<br>
1. Do the “obvious” thing and create a separate protocol for finite<br>
   single-pass sequences<br>
<br>
2. Decide that the combination of infinite and single-pass is rare<br>
   enough (/dev/urandom, temperature sensor) that it&#39;s better to just<br>
   ask people handling them to be careful and not, e.g., try to “count”<br>
   them.<br></blockquote><div><br></div><div>Not really feeling sufficiently in my element (excuse the pun) to comment on most of this discussion, but here I thought I&#39;d chime in. What&#39;s interesting about your two examples (/dev/urandom, temperature sensor) is that, though single-pass, they should be insensitive to destructive consumption, no? By which I mean, if some function returns 5 elements from the &quot;sequence&quot;, in both scenarios it would be undetectable whether it consumes 5, 10 or 15 elements in the process, IIUC. Are there other examples of infinite, single-pass sequences where destructive consumption would make a difference?</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
3. Decide that everything on a single-pass sequence is lazy. Since you<br>
   can only take a single pass anyway, people won&#39;t observe their<br>
   closures being called more often than necessary, which was the main<br>
   motivator for making map, filter, et. al eager on collections without<br>
   an explicit .lazy.<br>
<br>
Implications of #3:<br>
<br>
* Any “partially-formed” multipass sequences (modeling only Iterator)<br>
  would be free to expose an accurate underestimatedCount, thereby<br>
  optimizing the process of copying into an array. The lazy filter<br>
  Iterator adaptor would have an underestimatedCount of 0.<br>
<br>
* All algorithms that require multiple passes, such as sorted(), would<br>
  be unavailable on Iterator.  You&#39;d have to construct an Array (or<br>
  other MutableCollection) and sort that in-place.  Of course,<br>
  constructing an Array from an Iterator could still go on forever if<br>
  the Iterator turned out to be infinite, which means, at some level #3<br>
  is just a refinement of #2 that makes it less error-prone.<br>
<div class="HOEnZb"><div class="h5"><br>
--<br>
Dave<br>
_______________________________________________<br>
swift-evolution mailing list<br>
<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a><br>
<a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank">https://lists.swift.org/mailman/listinfo/swift-evolution</a><br>
</div></div></blockquote></div><br></div></div>