Protocol requirements specialization

Given a protocol with a generic requirement and a default implementation...

protocol Animal {
  func greet<A: Animal>(_ animal: A)
}
extension Animal {
  func greet<A: Animal>(_ animal: A) {
    print("\(Self.self) sniffs \(A.self) cautiously")
  }
}

Is it possible to provide a specialized version of the greet(_:) function for a specific type? For example:

struct Cat: Animal {}
struct Dog: Animal {
  func greet(_ animal: Dog) {
    print("Bark in excitement")
  }
}

On a test implementation, the compiler is not able to pick such specialization:

func walk<each A: Animal>(_ animals: repeat each A, towards animal: some Animal) {
  repeat (each animals).greet(animal)
}
walk(Cat(), Dog(), towards: Dog())

This returns:

Cat sniffs Dog cautiously
Dog sniffs Dog cautiously
2 Likes

Your Dog.greet here is not a valid implementation of the protocol requirement on Animal - you require that any type that implements Animal.greet be generic over any other Animal implementation, and then in Dog provide a totally separate function that is non-generic, but happens to share the same name. With the default implementation provided, the compiler will always select it in a generic context like in walk (since the other greet literally doesn't fulfill the protocol requirements).

I don't believe there's a way to accomplish what you're trying to do with a generic protocol requirement without inspecting the type at runtime - something like:

struct Dog: Animal { 
    func greet<A: Animal>(_ animal: A) { 
        if type(of: animal) == Dog.self {
            print("Bark in excitement") 
        } 
        else { 
            print("Dog sniffs \(A.self) cautiously") 
        } 
    } 
} 
4 Likes

Thank you for the reply @MPLewis.

Your Dog.greet here is not a valid implementation of the protocol requirement on Animal

Indeed it is not. However, since I provided a default implementation of Animal, I was hoping the compiler would have some heuristic to realize the Dog's function was just a specialization over the generic one. It does make sense that the compiler see those functions as completely different, though.

I don't believe there's a way to accomplish what you're trying to do with a generic protocol requirement without inspecting the type at runtime.

This is what I was fearing. The example I wrote is very basic for the purpose of discussing here in the forum, but in my code the Cat and Dog structures are simple generic or variadic types, making it impossible to test at runtime. At least I don't know to runtime test a variadic type to provide a concrete greet(_:) implementation.

1 Like

This would only be possible with a generic system that’s completely compiled away, like C++ templates. Swift, like many other modern languages, provides runtime support for generics, which means it would have to emit code like what @MPLewis wrote anyways for this to work.

4 Likes

You probably want to apply double dispatch pattern here: Double dispatch - Wikipedia. It’s a standard technique in all OO languages and not really specific to Swift or protocols.

You want two protocol requirements, where the first one calls the second requirement on its argument, receiving self as a parameter:

protocol Animal {
  func greet<A: Animal>(_: A)
  func greetedByDog(_: Dog)
}

extension Animal {
  func greet<A: Animal>(_: A) { ... }
  func greetedByDog(_ otherDog: Dog) { ... }
}

struct Dog: Animal {
  func greet<A: Animal>(_ animal: A) {
    animal.greetedByDog(self)
  }
}

struct Cat: Animal {
  func greetedByDog(_ otherDog: Dog) { ... }
}
6 Likes

Thank you Slava. I will keep that in mind.

Just wanted to comeback and thank you again @Slava_Pestov. I've been implementing a solution in your suggested direction and it is currently working fairly well (aside from all the indirection). Thanks :pray:

1 Like

I am trying to complete @Slava_Pestov's sketch. What does the default implementation for Animal look like now? Do we still need it?

The full sample code is as follows:

protocol Animal {
  func greet(_ animal: some Animal)
  func _greetedByDog(_ dog: Dog)
}

extension Animal {
  func greet<A: Animal>(_ animal: A) {
    print("\(Self.self) sniffs \(A.self) cautiously")
  }

  func _greetedByDog(_ dog: Dog) {
    print("\(Dog.self) sniffs \(Self.self) cautiously")
  }
}

struct Cat: Animal {}
struct Dog: Animal {
  func greet(_ animal: some Animal) {
    animal._greetedByDog(self)
  }

  func _greetedByDog(_ dog: Dog) {
    print("Bark in excitement")
  }
}

func walk<each A: Animal>(_ animals: repeat each A, towards animal: some Animal) {
  repeat (each animals).greet(animal)
}

If we do the same test as last time:

walk(Cat(), Dog(), towards: Dog())

We get:

Cat sniffs Dog cautiously
Bark in excitement
5 Likes