Skip to content
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

Nested NonEmpty #46

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft

Conversation

RemiBardon
Copy link

Following our discussion #45, here is a pull request introducing my proposed ideas.

It doesn't break any existing test, and adds new ones for introduced features.

Let me know what you think 🙂

@RemiBardon RemiBardon marked this pull request as draft February 14, 2022 23:19
@RemiBardon
Copy link
Author

I just realized I forgot to add tests for try AtLeast3([1,2]) for example, which, obviously, doesn't throw 🙈
I'll fix it tomorrow 🛠

@RemiBardon
Copy link
Author

Turns out it's quite easy to make XCTAssertThrowsError(try AtLeast10(Array(1...9))) pass, but I can't find a way to fix XCTAssertThrowsError(try AtLeast10(AtLeast10(Array(1...19)))).

@stephencelis
Copy link
Member

@RemiBardon Just wanted to chime in to let you know we saw this, and thanks for exploring! We won't have time to dig in quite yet but hopefully soon.

@RemiBardon
Copy link
Author

No problem, I'm busy too 😉

I really tried to work around my problem, but I couldn't find a clever way to do it without storing more than the initial collection in memory 😕

@RemiBardon RemiBardon force-pushed the nested-nonempty branch 2 times, most recently from 2618e9c to 675668b Compare March 3, 2022 17:30
@RemiBardon
Copy link
Author

I finally made it! It works like a charm 🙂

The biggest issue, which I tried hours to avoid, but couldn't get around, is that I have to store one Int along with the NonEmpty collections. This counts the minimum number of elements, and I could not avoid needing it.

I should have noted everything I tested, but my main problem was that because of how Swift's type system works, I couldn't create a static function which could calculate the minimum count from the type. With Swift 5.6, maybe it could work, but at least this solution doesn't break compatibility.

@RemiBardon RemiBardon marked this pull request as ready for review March 3, 2022 17:37
@RemiBardon
Copy link
Author

@stephencelis This PR is now ready 🙂

@RemiBardon RemiBardon marked this pull request as draft March 5, 2022 21:28
@RemiBardon
Copy link
Author

RemiBardon commented Mar 5, 2022

I found a bug, I'm fixing it.

For some reason, .drop10 can be called from an AtLeast2… which is super strange 🤔

fileprivate typealias AtLeast11<C: Swift.Collection> = NonEmpty<AtLeast10<C>>
extension AtLeast11 {
  public var drop10: NonEmpty<Collection.SubSequence> {
    try! NonEmpty<Collection.SubSequence>(from: self.rawValue.dropFirst(10))
  }
}

@RemiBardon
Copy link
Author

I found a bug, I'm fixing it.

For some reason, .drop10 can be called from an AtLeast2… which is super strange 🤔

fileprivate typealias AtLeast11<C: Swift.Collection> = NonEmpty<AtLeast10<C>>
extension AtLeast11 {
  public var drop10: NonEmpty<Collection.SubSequence> {
    try! NonEmpty<Collection.SubSequence>(from: self.rawValue.dropFirst(10))
  }
}

This also means .tenth is available for AtLeast2 😭
This makes no sense but I'll have to change my extensions with a lot of where statements 😥 Hopefully it'll help 🤞🏻

Also remove `.drop10`, as I could not make it type safe.
@RemiBardon
Copy link
Author

After hours of searching how to make drop10 type safe, I decided to remove it. It would probably never have been used anyway, as the use case is very specific.

@RemiBardon RemiBardon marked this pull request as ready for review March 6, 2022 11:18
@RemiBardon RemiBardon marked this pull request as draft March 6, 2022 12:26
@RemiBardon
Copy link
Author

Nested NonEmpty do not have type safe (non-throwing) initializers, how did I miss it?

I'm adding them.

@RemiBardon
Copy link
Author

b1a422a was a long commit… I had to change many things 😕

I see a few problems here:

  • I added a safe atLeast2(_:_:) initializer, but because of the AtLeast2 type alias, AtLeast2(_:) also compiles. This means a single letter with bad casing can raise a fatal error 😰 I'll have to get rid of the type aliases 😕
  • Because OneMore is a struct different from NonEmpty, I loose the extensions you created for NonEmpty, and if I copy them, they might not stay in sync.

Now that it works, I will try to refactor it in two steps:

  1. Remove the type aliases
  2. Try to remove the OneMore type, and use NonEmpty instead (with verbose type constraints, it might be possible, I'll see)

@RemiBardon
Copy link
Author

RemiBardon commented Mar 11, 2022

  1. Try to remove the OneMore type, and use NonEmpty instead (with verbose type constraints, it might be possible, I'll see)

The main problem with this is that I still have to separate two cases:

  • When NonEmpty wraps any collection → Collection == RawValue
  • When NonEmpty wraps a NonEmptyCollection == NonEmpty<_>, RawValue == Collection.RawValue

The only way I can think of to separate these cases, and keep a single definition of protocol conformances, is to go back to the enum definition of NonEmpty. However, this time "head" and "tail" would mean "root NonEmpty" and "nested NonEmpty".

I will try to refactor this way, and I'll see where it takes me.

@RemiBardon
Copy link
Author

I'll probably do one more step, which is to make NonEmpty a struct which stores a internal enum _NonEmpty. This way, people won't be able to access and alter the raw value.

@RemiBardon
Copy link
Author

I feel stupid… I realize I can't have type safe nesting if I'm using enums 🤦🏻‍♂️

@RemiBardon
Copy link
Author

I finally got it!

Separating WithMinimumCount from NonEmptyProtocol allowed me to avoid the "Self or associated type requirement" compiler error 😪 This was what blocked me in so many tries 😥

/// - Note: We need to separate `WithMinimumCount` from `NonEmptyProtocol`
///   to avoid the "Self or associated type requirement".
public protocol WithMinimumCount {
  static var minimumCount: Int { get }
}

@RemiBardon RemiBardon marked this pull request as ready for review March 16, 2022 22:24
@RemiBardon
Copy link
Author

@stephencelis I marked this pull request as ready to review, as I think it has enough tests, which all pass, covering every requirement I had in mind.

I will start using it myself on my branch, to see if problems arise.

@RemiBardon
Copy link
Author

One thing I would like to add, but I'm not sure if it's a good idea, is an extension saying AtLeast2(1, 2) is of type AtLeast2<[Int]> by default, to avoid needing <[Int]> everywhere. It's just syntactic sugar, with not much value (if at all), and potential issues, so I didn't include it here.

@RemiBardon
Copy link
Author

I just force pushed twice to cleanup some things I had changed and shouldn't have.

I can also squash my commits if you want to.

@RemiBardon
Copy link
Author

The biggest issue, which I tried hours to avoid, but couldn't get around, is that I have to store one Int along with the NonEmpty collections. This counts the minimum number of elements, and I could not avoid needing it.

Separating WithMinimumCount from NonEmptyProtocol allowed me to avoid the "Self or associated type requirement" compiler error 😪 This was what blocked me in so many tries 😥

/// - Note: We need to separate `WithMinimumCount` from `NonEmptyProtocol`
///   to avoid the "Self or associated type requirement".
public protocol WithMinimumCount {
  static var minimumCount: Int { get }
}

Oh, and by doing so, I was able to avoid storing minimumCount as a value, which is what I ultimately wanted 🤩

@RemiBardon
Copy link
Author

RemiBardon commented Mar 18, 2022

I updated Xcode and the toolchain, and I get error: Abort trap: 6 (in target 'NonEmpty' from project 'swift-nonempty') 😭

It happens with:

  • Xcode 13.3 (13E113)
  • Apple Swift version 5.6 (swiftlang-5.6.0.323.62 clang-1316.0.20.8)

Let me fix it then…

Edit: Until then, I reported https://bugs.swift.org/browse/SR-16024.

@RemiBardon RemiBardon marked this pull request as draft March 18, 2022 18:04
It's too complex for the compiler, we have to remove it.
It's probably useless anyway.
@RemiBardon
Copy link
Author

I removed AtLeast9 and AtLeast10, it fixes the issue and it was probably useless.

I propose we'll try to find a solution only if someone needs it.

@RemiBardon RemiBardon marked this pull request as ready for review March 18, 2022 21:31
@RemiBardon
Copy link
Author

The biggest issue, which I tried hours to avoid, but couldn't get around, is that I have to store one Int along with the NonEmpty collections. This counts the minimum number of elements, and I could not avoid needing it.

Separating WithMinimumCount from NonEmptyProtocol allowed me to avoid the "Self or associated type requirement" compiler error 😪 This was what blocked me in so many tries 😥

/// - Note: We need to separate `WithMinimumCount` from `NonEmptyProtocol`
///   to avoid the "Self or associated type requirement".
public protocol WithMinimumCount {
  static var minimumCount: Int { get }
}

Oh, and by doing so, I was able to avoid storing minimumCount as a value, which is what I ultimately wanted 🤩

Following this discovery, I realised I could revert some changes I made in the code, to make the Pull Request as clean as possible.

There are a lot of things I changed which I could have avoided.
To make Pull Request more readable, I preferred to revert them.
@RemiBardon
Copy link
Author

Converting this Pull Request to draft once again, because by using it I realized it introduces a huge increase in compile time (see https://forums.swift.org/t/wrong-redundant-conformance-constraint-warning/56207/15).

I will mark this PR as "ready" once I fix this issue.

@RemiBardon
Copy link
Author

@RemiBardon
Copy link
Author

A new trunk development snapshot was released, and now everything (including AtLeast9 and AtLeast10) compiles in "normal" time.

I don't mark the PR as "ready", as you might want to wait for the compiler have a stable release containing the fix. I will use my branch personally, and wait for your approval on this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants