apple / swift-algorithms

Commonly used sequence and collection algorithms for Swift
Apache License 2.0
5.97k stars 443 forks source link

Add new `FiniteCycle` type as the return type of `Collection.cycled(times:)` #106

Closed LemonSpike closed 3 years ago

LemonSpike commented 3 years ago

This PR aims to fix #99. I have also split up some relevant tests into separate methods for empty collections and changed some tests for FiniteCycle. I have also added tuple labels to Product2 when using the Iterator.next() method and also in the subscript method. This is a cosmetic improvement, but I felt it may read better.

Checklist

timvermeulen commented 3 years ago

Thanks for taking this on @LemonSpike, this is a great start! Your FiniteCycle correctly delegates all of the work to an underlying Product2<Range<Int>, Base>. However, the real benefit will come from being able to efficiently implement all of the Collection methods that Product2 already implements. By forwarding all public Collection and BidirectionalCollection methods to an underlying Product2 collection, FiniteCycle will have all the same performance characteristics as Product2. Want to have a go at that?

LemonSpike commented 3 years ago

Thanks for the feedback @timvermeulen! I have tried to add the Collection protocol conformances this time by forwarding to an underlying Product2 collection. Are these what you had in mind? Thanks again. 😊

timvermeulen commented 3 years ago

This is indeed what I had in mind, great work! The Collection conformance looks good, it implements all the relevant methods. There are just a few things left to do:

LemonSpike commented 3 years ago

Hi @timvermeulen, thanks for the feedback. Re: your points, I tried to implement your suggestions as follows:

  1. I removed the Sequence conformance entirely. To conform to LazyCollectionProtocol I had to conform to LazySequenceProtocol and LazyCollectionProtocol together where Base: LazyCollectionProtocol, because I couldn't inherit LazySequenceProtocol conformance automatically.
    1. Managed to add these as suggested, but because the FiniteCycle.Index is a typealias for Product2.Index, the Index Hashable conformance was already synthesized.
    2. Added @inlinable to all public methods, by making the let product public.
    3. Markdown file edited but reworded without despite its name since the name no longer includes Sequence.
    4. I changed some @usableFromInline internal methods to @inlinable because of #109.

Let me know your thoughts, thanks.

timvermeulen commented 3 years ago

Apologies for the delay, @LemonSpike!

  1. I removed the Sequence conformance entirely. To conform to LazyCollectionProtocol I had to conform to LazySequenceProtocol and LazyCollectionProtocol together where Base: LazyCollectionProtocol, because I couldn't inherit LazySequenceProtocol conformance automatically.

We can get rid of the Iterator type and makeIterator method entirely. The typechecker did get confused about what the Element type is after I tried doing this, so you might need to add typealias Element = Base.Element to the Collection conformance.

  1. Managed to add these as suggested, but because the FiniteCycle.Index is a typealias for Product2.Index, the Index Hashable conformance was already synthesized.

Great point! Let's for now give FiniteCycle its own Index type that wraps a Product2.Index, just so in the future we have the freedom to change the internals if the need ever arises.

  1. Added @inlinable to all public methods, by making the let product public.

While we make all public and internal methods @inlinable, for stored properties @usableFromInline suffices, so there's no need to make the property public.

  1. Markdown file edited but reworded without despite its name since the name no longer includes Sequence.

That looks good, could you also have it mention the conditional LazyCollectionProtocol conformance, similar to the Cycle description? And there's a note a bit higher up about cycled(times:) being a combination of two existing standard library functions which we can probably get rid of altogether.

  1. I changed some @usableFromInline internal methods to @inlinable because of #109.

💯

natecook1000 commented 3 years ago

@swift-ci Please test

LemonSpike commented 3 years ago

@swift-ci Please test

natecook1000 commented 3 years ago

@swift-ci Please test

natecook1000 commented 3 years ago

Looks great, @LemonSpike — thanks for this addition! 🎉