Generic placeholder with Self—any workarounds other than writing multiple concrete types?

The simplest example,

public enum Outer<Inner> { }
public extension Outer where Inner == Self { }

yields

Cannot build rewrite system for generic signature; concrete nesting limit exceeded

I think that means it's impossible to combine the following into one type. Correct?

enum ReferenceArrayNest<Element> {
  case element(Element)
  case array([Reference<Self>] = [])
}
enum ValueArrayNest<Element> {
  case element(Element)
  case array([Self] = [])
}
/// Adds reference semantics to a value type.
@propertyWrapper public final class Reference<Value> {
  public var wrappedValue: Value
  public var projectedValue: Reference { self }

  public init(wrappedValue: Value) {
    self.wrappedValue = wrappedValue
  }
}

…and it's also impossible to write either of those types to use Sequence<Self>-deriving protocols, rather than copying and pasting again for any type other type but Array?

1 Like

That type would be the infinitely nested Outer<Outer<Outer<Outer<Outer<...>>>>>: why would one need that such that a workaround is needed?

The nesting in the arrays isn't infinite.

I’m confused about your question.

The “simplest example” doesn’t compile and it’s trying to extend a type with an infinitely nested generic parameter. It doesn’t have any arrays.

All the other code which uses arrays for associated types compile fine.

What is the thing that you’re trying to accomplish and what do you need to work around?

This post can't be empty but the questions are just the rest of the post:

What do you mean by “combining” into one type? With a wrapper type? If so, do you need to express additional generic parameters on that type? What errors are you encountering when you try?

And what to you mean about “using” Sequence-derived protocols? Do you want to conform your enums to Collection and other protocols in the hierarchy? If so, what is the difficulty you need to work around when trying to do so?

Given the above, by first having ValueArrayNest adopt ArrayNestProtocol, rewrite the following using

  1. One enumeration instead of two.
  2. No protocol.
public protocol ArrayNestProtocol<Element, ArrayElement> {
  associatedtype Element
  associatedtype ArrayElement

  static func element(_: Element) -> Self
  static func array(_: [ArrayElement]) -> Self
}

public extension ArrayNestProtocol where ArrayElement == Self {
  init(_ referenceArrayNest: ReferenceArrayNest<Element>) {
    switch referenceArrayNest {
    case .element(let element):
      self = .element(element)
    case .array(let array):
      self = .array(
        array.map { .init($0.wrappedValue) }
      )
    }
  }
}

First adopt a protocol but then delete the protocol? I’m sorry, I’m genuinely not following—someone else maybe can chime in here.

What’s the relationship between @propertyWrapper class Reference<Value> and enum ReferenceArrayNest and enum ValueArrayNest? Without understanding that, I find it difficult to grasp your ultimate goal.

I feel like this thread has filled up with noise about an "ultimate goal". I tried to create two simple examples and apparently I needed to go simpler to get an answer to the question in the title.

Either someone can show me the following code working, using

  1. One enumeration instead of two.
  2. No protocol.

…or I'll just take it as a limitation of the language which warrants code generation.

@Reference var referenceArrayNest = ReferenceArrayNest.element("🐈‍⬛")
ValueArrayNest(referenceArrayNest)
previous code, consolidated
public enum ReferenceArrayNest<Element> {
  case element(Element)
  case array([Reference<Self>] = [])
}
public enum ValueArrayNest<Element>: ArrayNestProtocol {
  case element(Element)
  case array([Self] = [])
}

/// Adds reference semantics to a value type.
@propertyWrapper public final class Reference<Value> {
  public var wrappedValue: Value
  public var projectedValue: Reference { self }

  public init(wrappedValue: Value) {
    self.wrappedValue = wrappedValue
  }
}

public protocol ArrayNestProtocol<Element, ArrayElement> {
  associatedtype Element
  associatedtype ArrayElement

  static func element(_: Element) -> Self
  static func array(_: [ArrayElement]) -> Self
}

public extension ArrayNestProtocol where ArrayElement == Self {
  init(_ referenceArrayNest: ReferenceArrayNest<Element>) {
    switch referenceArrayNest {
    case .element(let element):
      self = .element(element)
    case .array(let array):
      self = .array(
        array.map { .init($0.wrappedValue) }
      )
    }
  }
}

I don’t understand “the question in the title.” What is a “generic placeholder?” What type-system relationship does “with Self” mean? What is the problem that requires a “workaround”?

As @xwu said, your simplest example fails because Outer<Inner> where Outer == Self is an infinite type. In your second example, do you want to combine ReferenceArrayNest, ValueArrayNest, and Reference all into one class? Or do you just want to combine ValueArrayNest and ReferenceArrayNest?

Would this result in a type that can wrap a Sequence of anything, including other Sequences?

I too truly do not understand what your goal is with the ArrayNest types. This works in Swift 5.7 (though printing it requires a 5.7 stdlib at run time):

public enum ArrayNest<Element> {
  case element(Element)
  case array(any Sequence<Self> = [])
}

let x: ArrayNest<Int> = .array([.element(1), .array([.element(2)])])
print(x)

This does not quite work…

// error: generic enum 'ArrayNest' has self-referential generic requirements
public enum ArrayNest<Element, Collection: Sequence>
where Collection.Element == Self {
  case element(Element)
  case array(Collection)
}

let x: ArrayNest<Int, _> = .array([])
print(x)

…and to my knowledge making it work requires higher-kinded types, so you can pass Array and not Array<Something> as the Collection parameter. (Or having a language that supports self-referential requirements, which I think is what your "simplest example" is getting at, but higher-kinded types would be sufficient even without self-referential requirements.)

Thank you for addressing the later question. :+1:

But there's no reason to move on to it before addressing the first. I should have made two posts.

You don't need to understand the motivation to provide an answer, but the motivation is that it's too hard to mutate the nested arrays (e.g. in last week's advent of code) if they're value types. It's easiest to build them up with references and then remove the references afterwards.

This is a general usability with Swift, but without associated types and nesting involved, it's manageable addressing everything with indices instead.

1 Like