Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Apr 7, 2021

Conversation

LemonSpike
Copy link
Contributor

@LemonSpike LemonSpike commented Mar 14, 2021

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

  • I've added at least one test that validates that my change is working, if appropriate
  • I've followed the code style of the rest of the project
  • I've read the Contribution Guidelines
  • I've updated the documentation if necessary

@LemonSpike LemonSpike force-pushed the finite_cycle_cycled branch from 63438b9 to 70a2c56 Compare March 14, 2021 13:27
Copy link
Contributor

@xwu xwu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -56,6 +56,47 @@ extension Cycle: Sequence {

extension Cycle: LazySequenceProtocol where Base: LazySequenceProtocol {}


/// A collection wrapper that repeats the elements of a base collection for a finite number of times.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Please wrap to 80 columns, here and elsewhere.

/// A collection wrapper that repeats the elements of a base collection for a finite number of times.
public struct FiniteCycle<Base: Collection> {
/// The collection to repeat.
public let base: Base
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this internal, as well as times.

This is because equality (we could conform this type to Equatable, but it doesn't have to be here in this PR) should be based on elementwise comparisons, and [1, 2, 3].cycled(times: 6) should be equivalent to [1, 2, 3, 1, 2, 3].cycled(times: 3). Having base and times publicly accessible would raise the question of whether these properties are "salient" ones for the purposes of considering equivalence.

@LemonSpike LemonSpike marked this pull request as ready for review March 16, 2021 10:35
@timvermeulen
Copy link
Contributor

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
Copy link
Contributor Author

LemonSpike commented Mar 20, 2021

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
Copy link
Contributor

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:

  • We can get rid of the Sequence conformance entirely — normally it's needed in case the base type is only a Sequence and Collection cannot be implemented, but in the case of FiniteCycle we already require that Base: Collection, so it's no longer needed. In the same vein, the type needs LazyCollectionProtocol conformance rather than LazySequenceProtocol.
  • FiniteCycle is still missing some conformances that Product2 does have, implemented here.
  • Most public methods are missing @inlinable annotations.
  • The Cycle.md guide needs a slight tweak to reflect that cycled(times:) now has a new return type.

@timvermeulen timvermeulen added the source breaking This change affects existing source code label Mar 23, 2021
@LemonSpike
Copy link
Contributor Author

LemonSpike commented Mar 24, 2021

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.
  2. Managed to add these as suggested, but because the FiniteCycle.Index is a typealias for Product2.Index, the Index Hashable conformance was already synthesized.
  3. Added @inlinable to all public methods, by making the let product public.
  4. Markdown file edited but reworded without despite its name since the name no longer includes Sequence.
  5. I changed some @usableFromInline internal methods to @inlinable because of Inline all the things! 🎨 #109.

Let me know your thoughts, thanks.

@timvermeulen
Copy link
Contributor

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 Inline all the things! 🎨 #109.

💯

Comment on lines 47 to 48
public mutating func next() -> (element1: Base1.Element,
element2: Base2.Element)? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep this tuple unlabeled — Zip2Sequence's element tuple doesn't have labels either, and generic labels such as element1 and element2 don't really add any clarity over .0 and .1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am happy to do this, but I still think that the labels improve readability for users who don't know about tuple indexing. I saw we use more descriptive names for public APIs and type names, so I think it could help users understand the API. Let me know your thoughts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These labels don't improve the users' understanding, because they don't convey any useful information. It's impossible for us to give the tuple elements descriptive names because at this level of abstraction we have nothing to go on. To quote the API Design Guidelines:

Omit needless words. Every word in a name should convey salient information at the use site.

Feel free to start a discussion about this topic on the forums, as this is equally relevant to the Zip2Sequence element type. For the time being we'd like to stick with the precedent set by the standard library.

@natecook1000
Copy link
Member

@swift-ci Please test

@LemonSpike LemonSpike force-pushed the finite_cycle_cycled branch from cb3aba8 to e583de9 Compare April 7, 2021 10:48
@LemonSpike
Copy link
Contributor Author

@swift-ci Please test

1 similar comment
@natecook1000
Copy link
Member

@swift-ci Please test

@natecook1000
Copy link
Member

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

@natecook1000 natecook1000 merged commit bcdbac8 into apple:main Apr 7, 2021
@LemonSpike LemonSpike deleted the finite_cycle_cycled branch April 16, 2021 18:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
source breaking This change affects existing source code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add new FiniteCycle type as the return type of Collection.cycled(times:)
4 participants