Collection of enums from arrays of enum cases

let’s say i have an enum with associated values like:

enum Letter
{
    case a(A)
    case b(B)
    case c(C)
    case d(D)
    case e(E)
    case f(F)
    ...
    case z(Z)
}

i also have a struct that contains arrays of the enum payloads:

struct Letters
{
    var a:[Letter.A]
    var b:[Letter.B]
    var c:[Letter.C]
    var d:[Letter.D]
    var e:[Letter.E]
    var f:[Letter.F]
    ...
    var z:[Letter.Z]
}

now, the task is to conform Letters to Collection<Letter>.

extension Letters:Collection<Letter>
{
    ???
}

i’ve been able to do this before with enums of three or four cases by writing increasingly awkward chaining iterators. but that approach doesn’t really scale to enums with many payloads.

I decided to give it a try. This is how far I got before I ran into the fact that my Index type, being a protocol existential, can't conform to Comparable. At any rate I hope this is a helpful starting point.

Maybe I have to do this:

class Index: Comparable {
  fileprivate init() {}
}

private final class ConcreteIndex<Element>: Index {
  // ... actual implementation ...
}
1 Like

yeah, i ran into similar issues when pursuing a struct EncodingView<Element> -based idea.

extension Letters
{
    struct EncodingView<Element> where Element:BSONDocumentEncodable
    {
        let elements:[Element]

        init(_ elements:[Element])
        {
            self.elements = elements
        }
    }
}
extension Letters.EncodingView:RandomAccessCollection
{
    var startIndex:Int { self.elements.startIndex }
    var endIndex:Int { self.elements.endIndex }

    subscript(index:Int) -> any BSONDocumentEncodable { self.elements[index] }
}

but one cannot actually construct any such views, because the individual Letter case payloads don’t conform to BSONDocumentEncodable (and they shouldn’t!), only Letter itself does.

with enough effort, you could work around that by introducing a LetterProtocol abstraction, but then you’ve got a

FlattenCollection<[any RandomAccessCollection<any BSONDocumentEncodable>]>

which isn’t a valid type because any RandomAccessCollection doesn’t conform to Collection.

now, if i haven’t hinted at it enough yet, i don’t actually need to do a lot of Collection things with this structure, it just needs to conform to Collection in order to serialize itself to BSON. the procedure is basically this:

//  Requires Collection
let count:Int = self.letters.count

//  Requires Sequence<some BSONDocumentEncodable>
let insert:Mongo.Insert = .init(documents: self.letters)
guard count == try await mongodb.run(command: insert)
else
{
    ...
}

i’m trying to avoid intermediate buffers as much as possible because in reality, the “letters” arrays are quite large and encoding them is a very memory-intensive operation.

Could you not just adopt Sequence and then provide a separate count property? If indexing isn't necessary, what I've already provided above should suffice. (Unless of course this is within a generic function <T where T: Collection, T.Element: BSONDocumentEncodable>)

in this part:

    [
      self.a.map(Letter.a),
      self.b.map(Letter.b),
    ].joined()

you’re copying the typed buffers to wider [Letter] buffers, which uses a lot of memory (imagine self.a through self.c contain many many documents, and self.d through self.z are generally empty or only contain a handful of documents).

one possible workaround could involve

public
protocol LetterProtocol
{
    var wrapped:Letter { get }
}

extension Letter.A:LetterProtocol
{
    @inlinable public
    var wrapped:Letter { .a(self) }
}
...
extension Letter.Z:LetterProtocol
{
    @inlinable public
    var wrapped:Letter { .z(self) }
}

extension Letters
{
    struct EncodingView<Element> where Element:LetterProtocol
    {
         ...
    }
}
extension Letters
{
    struct AnyEncodingView:RandomAccessCollection
    {
        // how to constrain Index == Int?
        private
        let base:any RandomAccessCollection<Letter>
    }
}

but this felt like an absurd amount of generic machinery to accomplish a proportionally minor goal: encode some heterogenous data to BSON.

There’s definitely some way to make that lazy, I just didn’t feel like fighting with the type system this morning. I can investigate it further later this week, but my free time is going to be limited with exams coming up.