“opaque” generics (a.k.a. parameterized extensions)

i keep finding myself trying to extend a Sequence of some generic type, where i want to parameterize the extension over the generic parameter of the Sequence.Element. this is not possible in swift, so i end up writing a bunch of boilerplate like:

extension Sequence<Branch.Epoch<Module>>
{
    func find(_ module:Module.ID) -> Tree.Position<Module>? 
    {
    }
}
extension Sequence<Branch.Epoch<Symbol>>
{
    func find(_ symbol:Symbol.ID) -> Tree.Position<Symbol>? 
    {
    }
}
extension Sequence<Branch.Epoch<Article>>
{
    func find(_ article:Article.ID) -> Tree.Position<Article>? 
    {
    }
}

when what i really want to do is something like:

extension Sequence<some Branch.Epoch<Axis>>
{
    func find(_ axis:Axis.ID) -> Tree.Position<Axis>? 
    {
    }
}

or

extension <Axis> Sequence<Branch.Epoch<Axis>>
{
    func find(_ axis:Axis.ID) -> Tree.Position<Axis>? 
    {
    }
}

as a workaround it is possible to define a _AnyBranchEpoch protocol with an associated type that can become the Axis parameter, and extend that. but i really struggle to come up with a name for this protocol because _AnyBranchEpoch is just wrong: it is not an existential and this is abusing the word “any”.

_SomeBranchEpoch is a little bit better, but it leads to some _SomeBranchEpoch which also sounds wrong.

how do you handle this?

Drop the generic constraints down to the 'find' function, instead of writing it on the extension.

protocol AxisProtocol {
  associatedtype ID
}

enum Branch {
  struct Epoch<T> {}
}

enum Tree {
  struct Position<T> {}
}

extension Sequence {

  func find<Axis>(
    _ id: Axis.ID
  ) -> Tree.Position<Axis> where Element == Branch.Epoch<Axis>, Axis: AxisProtocol {
    //                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    fatalError("Not implemented")
  }
}
13 Likes

wow, that’s a handy technique! i completely forgot extension members can introduce their own parameterized constraints. thanks!

4 Likes