[swift-evolution] Fixing the confusion between non-mutating algorithms and single-pass sequences

Dave Abrahams dabrahams at apple.com
Tue Jul 19 17:23:24 CDT 2016


on Mon Jul 18 2016, Jonathan Hull <swift-evolution at swift.org> wrote:

> Comments inline:
>
>> On Jul 15, 2016, at 11:47 AM, Dave Abrahams <dabrahams at apple.com> wrote:
>> 
>> 
>> on Thu Jul 14 2016, Jonathan Hull <jhull-AT-gbis.com <http://jhull-at-gbis.com/>> wrote:
>> 
>>> I have also been thinking about this problem for the last week or so
>>> (as well as the finite/infinite bit).  I don’t really have anything
>>> detailed that is ready to share (and it sounds like you are headed in
>>> a different direction now).  I still wanted to share the gist of my
>>> thoughts, in case they help spark ideas in others…
>> 
>> Hi Jonathan,
>> 
>> Thanks for your thoughts...
>> 
>>> My thought was to follow the first rejected approach: removing
>>> sequence and letting the Iterator protocol model single-pass.
>>> Iterators would be reference types.
>>> 
>>> I followed a similar path, and my version also has a pretty large
>>> duplication of API between Iterator and Collection… the difference
>>> though, is I think I have a way to avoid most external duplication of
>>> API.
>>> 
>>> Basically, I added back in a super-minimal protocol to fill the
>>> structural gap left by Sequence.  I call it “IteratorProvider” and it
>>> only has a single function which vends an iterator.  Collection
>>> adheres to this, and Iterator adheres to it by returning itself.  All
>>> of the other methods from Sequence remain on Iterator.  Thus anyone
>>> with API that only needs a single pass would take a IteratorProvider
>>> and then work on the iterator it provides.
>> 
>> That leaves us back where we are now: people will see that
>> IteratorProvider is a simple, universal protocol for both single-and
>> multi-pass sequences, write algorithm libraries that depend on
>> multi-pass-ness, and test them with the most prevalent examples, which
>> happen to be multi pass.
>
> Let me make a quick counter-argument, because I thought about it a
> bit, and I don’t think it does have the same problem (especially with
> careful/better naming).
>
> The difference is that the ONLY method on IteratorProvider is the one
> to get an iterator.  There is no map, filter, sort, first, count, etc…
> just a way to get a single-pass iterator.  This changes the mindset
> when using it.  You are aware that you are getting a single-pass
> iterator.

Maybe.  What's to stop people from extending IteratorProvider?

> True, people might try to get the iterator a second time, but we can
> make the iteratorProvider method optional (and trying to get an
> iterator from an iterator which is spent would return nil) 
> and then they are forced to deal with the case where it was
> single-pass.

Now you can't loop over the same array multiple times.

> It is a fairly subtle difference, but an important one IMHO.
>
>>> The big difference is that Collection and Iterator are still separate
>>> protocols, 
>> 
>> That's currently the case.
> Yes, you are correct.  I don’t know why I thought they were connected in the current version.
>
>>> iterator is a reference type, 
>> 
>> That's slightly different, but we have reasons for not wanting to make
>> that requirement.
>
> Would you mind expanding on those reasons?

All detailed below, before you write “ah.”

>>> and most of the methods from sequence are now on iterator.
>>> 
>>> I think this makes more sense semantically than the current model (or
>>> renaming sequence).  I also really think it is important to have
>>> iterators be reference types (anything else is really a lie)
>> 
>> Once they're reference types, you might as well just make them conform
>> to the same protocol as collections for algorithms, because none of the
>> “mutating/nonmutating” distinctions will be honored anyway.  Furthermore
>> it implies efficiency costs we're not prepared to pay.  The main problem
>> is that the right way to model single-pass traversal is with move-only
>> types, which can't be copied... and that's not something we can express
>> in Swift today.
> ah.
>
>>> The rejected “consumedIn” idea also gave me an idea of how to reduce
>>> the internal API repetition, if desired.
>>> 
>>> Have a fileprivate method on Iterator (I will call it “consumedIn”
>>> here, but it is private, so call it whatever) that wraps the Iterator
>>> in a collection. The multi-pass-ness of that secret collection is a
>>> lie, but it is fileprivate so it should never get into the wild where
>>> someone can find that out. Then you would just define map() etc… on an
>>> extension of Iterator and have them forward to “self.consumedIn.map”,
>>> etc….  It does still have duplication of definitions, but the
>>> implementations would be in a single spot.
>> 
>> Yeah, we thought about that, too.  That doesn't really help the public
>> API, though. That's the problem we're trying to solve.  And if we don't
>> expose the thing publicly, any user wishing to write her own algorithm
>> that works on both Iterator and Collection has to write it herself.
>> 
>>> Another option, if the subterfuge of a secret collection is
>>> undesirable, would be to make “consumedIn” be public and have it
>>> create an array-like collection.  The default implementation would
>>> actually make an eager copy, but specialized cases could work with the
>>> created collection to avoid copying the iterator’s contents where
>>> possible.  Then you would remove all of the eager methods from
>>> Iterator and just use collection’s version.
>> 
>> Ultimately, there are lots of interesting ideas for addressing this
>> space, but none of them lead to an answer that we we're willing to bet
>> solves the design problems and is implementable in time for Swift
>> 3... except for the renaming.
>
> Yeah.  We are out of time.  Is there any hope of coming back to this
> in Swift 4, or would it be too much of a breaking change?

There's always hope :-)
>
>> 
>>> Food for thought…
>>> 
>>> Thanks,
>>> Jon
>>> 
>>>> Hi,
>>>> 
>>>> I'd like to continue the discussion of the issue raised by David Waite
>>>> inhttp://thread.gmane.org/gmane.comp.lang.swift.evolution/21295/:
>>>> <inhttp://thread.gmane.org/gmane.comp.lang.swift.evolution/21295/:>
>>>> <inhttp://thread.gmane.org/gmane.comp.lang.swift.evolution/21295/:
>>>> <inhttp://thread.gmane.org/gmane.comp.lang.swift.evolution/21295/:>>
>>>> 
>>>>> My main motivation for proposing this is the potential for
>>>> developer confusion. As stated during one of the previous threads on
>>>> the naming of map, flatMap, filter, etc. methods on Sequence,
>>>> Sequence has a naming requirement not typical of the rest of the
>>>> Swift standard library in that many methods on Sequence may or may
>>>> not be destructive. As such, naming methods for any extensions on
>>>> Sequence is challenging as the names need to not imply immutability.
>>>> 
>>>> I'd like to focus on a particular point: methods on Sequence can
>>>> consume elements, but the APIs are not markedmutating.
>>>> 
>>>> Dave Abrahams, Max Moiseev, and I have discussed this issue and we
>>>> agree this problem is severe and worth solving, we also think that the
>>>> likely solutions would be source-breaking, so it is important that we
>>>> discuss it now.
>>>> 
>>>> We have discussed a few options.
>>>> 
>>>> - Rejected option: remove Sequence, let IteratorProtocol model
>>>> single-pass data streams
>>>> 
>>>> - Rejected option: use a syntactic marker, like sequence.consumedIn.map {}
>>>> 
>>>> - Rejected option: mutating APIs on Sequence, non-mutating APIs on Collection
>>>> 
>>>> Proposed: rename Sequence to IterableOnce or TraversableOnce. We think
>>>> that Sequence does not convey the single-pass restriction clearly. The
>>>> term "sequence" has been used in math (as in "integer sequence"), and
>>>> since the math domain does not have mutation, "sequence" can be
>>>> understood to mean "multi-pass", since you can traverse a sequence of
>>>> integers an arbitrary number of times.
>>>> 
>>>> We think that only the last option is viable in the Swift language as
>>>> it exists now, without creating an undue burden for API vendors and
>>>> users.
>>>> 
>>>> For more details about rejection options, please see the full writeup:
>>>> https://gist.github.com/gribozavr/47f4717f3afc762549383e94da7f748b
>>>> <https://gist.github.com/gribozavr/47f4717f3afc762549383e94da7f748b>
>>>> <https://gist.github.com/gribozavr/47f4717f3afc762549383e94da7f748b
>>>> <https://gist.github.com/gribozavr/47f4717f3afc762549383e94da7f748b>>
>>>> 
>>>> Dmitri
>>> 
>> 
>> -- 
>> Dave
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>

-- 
Dave



More information about the swift-evolution mailing list