Suppose that I have a function generic function defined on a protocol, that has a specialized overload for a concrete type. When calling the the function the the type, the correct type is called. However, when trying to compose the functions, the generic version is called. Here's a simple example.
protocol Person {
var name: String { get }
}
struct GoodPerson: Person {
let name: String
}
func tellAbout(_ person: some Person) {
print("\(person.name) is evil (like most of people)")
}
func tellAbout(_ person: GoodPerson) {
print("\(person.name) is an angel")
}
func tellMoreAbout(_ person: some Person) {
tellAbout(person)
}
var mustafaIbrahim = GoodPerson(name: "Mustafa Ibrahim")
tellAbout(mustafaIbrahim)
tellMoreAbout(mustafaIbrahim)
prints
Mustafa Ibrahim is an angel
Mustafa Ibrahim is evil (like most of people)
If there is no way to resolve that at the moment - are there any planned features to allow more control in future?
A hacky workaround is to define another protocol something like protocol HasCustomImplementation
, to bridge between custom implementations and and concrete types, (or several protocols), but it is not very flexible or extendable.
The way to model this in the language is to add tellAbout as a requirement to protocol Person.
In general, overloading is a compile-time fiction and there is no possibility of runtime dispatch among different unrelated functions, even if they share the same name.
3 Likes
I agree about adding to protocol requirement is the best choice. The problem, is that it is not always possible, and not always anticipated. Also, what happens if we need a function or operator that acts on several types (Matrix/Vector multiplication for example for various types of matrices? ). I still can get some customization using type casting, for example if I define a function f
, add a protocol CustomImplementedF
and a wrapper within a function that performs a cast, something like
protocol Box {
var length: Float { get }
var width: Float { get }
var height: Float { get }
}
protocol HasCustomCapacity: Box {
var availableVolume: Float { get }
}
extension Box {
var availableVolume: Float {
if let hasVolume = self as? HasCustomCapacity {
hasVolume.availableVolume
} else {
length * width * height
}
}
}
struct EmptyBox: HasCustomCapacity {
let length: Float
let width: Float
let height: Float
let availableVolume: Float = 0
}
func printVolume(of box: some Box) {
print("Box has volume \(box.availableVolume)")
}
var empty = EmptyBox(length: 10, width: 20, height: 30)
print("Printing volume")
printVolume(of: empty)
But it is cumbersome and error prone (for example, if someone accidentally extends a Box
type to satisfy HasCustomCapacity
using the default implementation, the code will break.
What you’re describing is a multi-parameter type class: Multi-parameter type class - HaskellWiki. This has also been discussed in the forums before, sometimes referred to as “generic protocols”. The general consensus seemed to be that this is not a good direction for Swift, though.
I suspect you can get most of the way there with associated types, recursive conformances, and same type requirements.
1 Like