How to Constrain 'self' to a Generic Type

Is there a way to constrain `Self` to a generic type?

For the generic type Owl<T> you are allowed to have the constraint where Self: Owl<String>, but it will only work in contexts where the concrete type information is available.

To make it clear what is happening, consider this:

let nightOwl = Owl<String>(name: "Night Owl", power: "Who")
nightOwl.doSomething() // prints "Owl<String>: Night Owl"

As opposed to this:

let nightOwl: Bird = Owl<String>(name: "Night Owl", power: "Who")
nightOwl.doSomething() // prints "default Bird: Night Owl"

When Swift creates the protocol witness tables for the types Owl<T> and FlappyBird, it has to act differently for each one because Owl is generic. If it doesn't have the concrete type information, i.e. Owl<String> at the call site, it must use the default implementation for Owl<T>. This "loss" of type information is happening when you are inserting the owls into the array of type [Bird].

In the case of FlappyBird, since there is only one possible implementation (since it's not generic), the compiler produces a witness table with the "expected" method reference, which is print("FlappyBird: \(name)"). Since FlappyBird is not generic, its witness table doesn't need any reference to the unconstrained default implementation of doSomething() and can therefore correctly call the the constrained implementation even when the concrete type information is missing.

To make it clear that the compiler "needs" to have the fall back behavior for a generic type, you can remove the Bird conformance from Owl<T> and try to rely solely on the constrained default implementation. This will result in a compilation error with an error that is, as usual with Swift, highly misleading.

Value of type 'Owl' has no member 'doSomething'

Basically, it seems the witness table can't be built because it requires the existence of an implementation that will work for all types T on Owl.

References

  1. https://forums.swift.org/t/swifts-method-dispatch/7228
  2. https://developer.apple.com/videos/play/wwdc2016/416/

Is it possible to constrain `Self` to a generic parameter?

I managed to do it using:

func doSomething<T: Delegate>(with bar: Bar<T>) -> Foo where T.Foo == Foo

It seems using T: Self does not work here and you have to add the condition for equal associated types.

Using self as a Generic Type Constraint

public class Example : Singleton<Example> {}

The constraint on Singleton<T> is T : Singleton<T>. Substitute T := Example, and you get the constraint Example : Singleton<Example>, which holds because that's how we declared Example. No issues.


public class Fraud  : Singleton<Example> {}

The constraint on Singleton<T> is T : Singleton<T>. Substitute T := Example, and you get the constraint Example : Singleton<Example>, which holds because that's how we declared Example. No issues.


There's no reason for Fraud to not be valid, so it is valid. After all, the point of F-bounded polymorphism is method chaining, and you can chain methods on Fraud just as well as you can on Example. It might not do the same thing, but that's the same kind of problem you'd get if you created a new IDictionary that ignored every second call to Add: you can't use the type system to enforce everything (unless you're Coq); you can't always stop external entities from doing things they shouldn't do.

Generic class with self-referencing type constraint

You can cast 'this' to T:

Bar((T)this);

This however will fail if you have the following:

public class MyFoo : Foo<MyFoo> { }

public class MyOtherFoo : Foo<MyFoo> { }

Because 'MyOtherFoo' is not an instance of 'MyFoo'. Take a look at this post by Eric Lippert, one of the designers of C#.

Swift generic function constrained by class generic

Therefore, the error is incorrect. ...Right?

Well... the current constraints system is not yet powerful enough to allow this kind of constraints. The good news is that generalized supertype constraints is on the Swift road map as described in the Generics Manifesto.

The below won't compile either, for the same reasons:

func test<A, B>(_ a: A, _ b: B) where A: AnyObject, B: A {
// ^^^ Type 'B' constrained to non-protocol, non-class type 'A'
}

Neither the example from the manifesto:

protocol P {
associatedtype Base
associatedtype Derived: Base
}

Unfortulately, you'll have to wait until this feature is available to Swift, to make it work as you want.

Go generics: self-referring interface constraint

Combine your two attempted interfaces together:

type Shape[ST any] interface {
*Circle | *Square
Bigger() ST
Smaller() ST
}

And then instantiate the constraint of process with the type parameter itself:

func process[ST Shape[ST]](s ST) ST {
return s.Bigger().Smaller()
}
  • Adding the union element *Circle | *Square into Shape[ST any] means that only those two types will be able to implement the interface
  • Then using the type parameter in the method signature, like Bigger() ST, means that whichever type is passed has a method that returns itself.

If you want to keep ShapeType as a separated interface, you can write Shape as:

type Shape[ST any] interface {
ShapeType
Bigger() ST
Smaller() ST
}

You can also use process method with type inference, without any issue:

func main() {
c1 := NewCircle(3)
c2 := process(c1)
fmt.Println(c2.Radius()) // prints 3 as expected
fmt.Printf("%T\n", c2) // *main.Circle

s1 := NewSquare(6)
s2 := process(s1)
fmt.Println(s2.Side()) // prints 6 as expected
fmt.Printf("%T\n", s2) // *main.Square
}

Final playground: https://go.dev/play/p/_mR4wkxXupH



Related Topics



Leave a reply



Submit