Making Swift Generics Play with Overloaded Functions

Making Swift generics play with overloaded functions

Unfortunately, there's no easy way to do this the way sin() is currently implemented in Swift. It would have to be treated as an operator (like how Equatable and == works) in order to allow you to add it as a protocol requirement.

@matt's solution is a nice quick fix, but if you want something more permanent, you may want to consider creating a protocol and then extending the floating point types in order to allow you to overload the sin() function with a generic version.

protocol FloatingPointMathType : FloatingPointType {
var _sinValue : Self { get }
}

extension Float : FloatingPointMathType {
var _sinValue : Float {return sin(self)}
}

extension Double : FloatingPointMathType {
var _sinValue : Double {return sin(self)}
}

extension CGFloat : FloatingPointMathType {
var _sinValue : CGFloat {return sin(self)}
}

func sin<T:FloatingPointMathType>(x:T) -> T {return x._sinValue}

(Feel free to add more math functions)

We're having to use a 'shadow' calculated property here to make up for the fact that we can't simply use sin() as a protocol requirement. It's not ideal – but probably about as good as you're going to get.

You can now go ahead and use sin() as a generic function:

struct MyStruct<T:FloatingPointMathType> {
var v : T

init(x:T) {
v = sin(x)
}
}

print(MyStruct(x: Float(3.0)).v) // 0.14112
print(MyStruct(x: Double(3.0)).v) // 0.141120008059867
print(MyStruct(x: CGFloat(3.0)).v) // 0.141120008059867

Overloading generic functions in iOS Swift

You haven't quite focussed on the actual issue. Let's eliminate everything irrelevant from the example. This compiles and works as expected:

struct TestFinder {

func doSomething<T,U>(_ function: (T,U) -> Void) -> Void {
print("two")
}

func doSomething<T,U,V>(_ function: (T,U,V) -> Void) -> Void {
print("three")
}

func doSomething<T,U,V,W>(_ function: (T,U,V,W) -> Void) -> Void {
print("four")
}

}

And here we'll test it:

    func f(_ s1: String, _ s2: String, _ s3: String, _ s4: String) -> Void {}
TestFinder().doSomething(f) // "four"

But if you add the version with one passed function parameter, everything breaks down:

struct TestFinder {

func doSomething<T>(_ function: (T) -> Void) -> Void {
print("one")
}

func doSomething<T,U>(_ function: (T,U) -> Void) -> Void {
print("two")
}

func doSomething<T,U,V>(_ function: (T,U,V) -> Void) -> Void {
print("three")
}

func doSomething<T,U,V,W>(_ function: (T,U,V,W) -> Void) -> Void {
print("four")
}
}

Now we can't compile, because the first version is seen as a candidate. And indeed, if we remove the other versions, we still compile!

struct TestFinder {

func doSomething<T>(_ function: (T) -> Void) -> Void {
print("one")
}

}

That's the weird part. We still compile, even though we are saying:

    func f(_ s1: String, _ s2: String, _ s3: String, _ s4: String) -> Void {}
TestFinder().doSomething(f)

Evidently, this function with four parameters is seen by the compiler as "fitting" the declaration with just one generic parameter.

I regard this as a bug. I think I can guess what might cause it; it could have to do with the legacy of function parameter list as tuples. This function f is "equivalent" to a function taking a single parameter consisting of a four-string tuple. Nevertheless, you cannot actually call the function inside doSomething with a four-string tuple; I cannot find a way to call it at all.

So, I would say, regard this as a bug, and work around it for now by removing the first version of your generic.


UPDATE: On the advice of the Swift team, I tested with the May 4, 2020 Swift 5.3 Development toolchain. With it, your code compiles and behaves as expected. This was indeed a bug, and it was fixed as part of

https://bugs.swift.org/browse/SR-8563

Returning for a moment to my version, my code, too, compiles and behaves as expected, with all four versions of doSomething present. However, note that if you delete all but the first version of doSomething, it still compiles and runs. Moreover, you can call function with four parameters by bundling them into a tuple and force casting, like this:

struct TestFinder2 {

func doSomething<T>(_ function: (T) -> Void) -> Void {
print("one")
function(("manny", "moe", "jack", "henry") as! T)
}

}

That seems to confirm my guess that what you're seeing is a consequence of the hidden tuple-nature of a function's parameter list. One can draw the same conclusion from the discussion of the bug, which refers to "tuple-splatting".

How to call an overloaded function when you only have a variable conforming to a lesser type?

There are a couple of ways to go about this...

Use a switch to reestablish the type

Swift needs to know at compile time which overloaded function it is calling. This can't happen if Swift doesn't know at compile time which type of the variable it has.

To get the type information back, you can use a switch to reestablish the type:

func performCommand(_ command:Command) {
guard let todoCommand = command as? ToDoCommand else {
return
}

// Perform some tasks that are common to all ToDoCommands...

switch todoCommand {
case let command as AddToDoCommand:
performCommand(command)
case let command as EditToDoCommand:
performCommand(command)
default: break
}
}

Use polymorphism

A way to let Swift decide which performToDoCommand() command to run at runtime is to use polymorphism.

Add the requirement to implement func performToDoCommand() to the ToDoCommand protocol, and then implement that for each struct that conforms to ToDoCommand. Calling the right one is then simple...

protocol Command {}
protocol ToDoCommand : Command {
func performToDoCommand()
}
protocol UserCommand : Command {}

struct AddToDoCommand : ToDoCommand {
func performToDoCommand() {
print("Add ToDo")
}
}

struct EditToDoCommand : ToDoCommand {
func performToDoCommand() {
print("Edit ToDo")
}
}

struct AddUserCommand : UserCommand {}
struct EditUserCommand : UserCommand {}

class ToDoManager {
func performCommand(_ command:Command) {
guard let todoCommand = command as? ToDoCommand else {
return
}

todoCommand.performToDoCommand()
}
}

Overload Functions For Different Structs Implementing The Same Protocol?

This is backwards of how you should approach the problem. In Swift you should generally avoid free functions (like handle(mutation:state:)), and instead attach methods to types.

Your loop wants a specific method, so you want a new protocol (you could of course attach this function to Mutation, but making it separate means that it could have different access controls or the like, so is as flexible as the free functions).

protocol MutationHandling {
func handleMutation(forState state: Int) -> Int
}

So we've just declared that this function you want is a thing. Now we can attach it to things that we care about. This is exactly the same as your writing free-functions above. It's the same pattern. The syntax is just different, and provides some extra documentation about why this function exists and allows the IDE to provide auto-completion and gather documentation together more effectively.

extension CountIncrement: MutationHandling {
func handleMutation(forState state: Int) -> Int {
print("WILL INCREMENT")
return mutate(state: state)
}
}

Now, you use this special protocol for your list:

var queue = [CountIncrement(), CountDecrement()] as [MutationHandling]

And call it:

for mutation in queue {
mutation.handleMutation(forState: state)
}

This isn't just some random limitation in Swift. This is a deep part of how Swift decomposes problems. Free-functions are explicitly discouraged (see the API Design Guidelines). Methods and extensions are the Swift way.

Swift: generic overloads, definition of more specialized

I'd say your problem is that you don't explicitely tell the compiler that P == ()

try the following code in a playground :

Void.self == (Void).self // true
Void() == () // true
(Void)() == () // true
(Void) == () // Cannot convert value of type '(Void).Type' to expected argument type '()'

Foo<Int>.self == (() -> Int).self // false
(() -> Int).self == ((Void) -> Int).self // false
Foo<Int>.self == ((Void) -> Int).self // true

Since (Void) cannot be converted to (), I guess the compiler can't understand that foo<R>(_ f: () -> R) is actually a specialization of foo<P, R>(_ f: (P) -> R).

I suggest you create generic type aliases for your function types to help the compiler understand what you're doing eg. :

typealias Bar<P, R> = (P) -> R
typealias Foo<R> = Bar<Void, R>

Now you can can define your function like that :

func foo<R>(_ f: Foo<R>) { print("r") } // Note that this does not trigger a warning.
func foo<P, R>(_ f: Bar<P, R>) { print("pr") }

and then use them with any closure you want :

let f: () -> Int = { 42 }
foo(f) // prints "r"
let b: (Int) -> Int = { $0 }
foo(b) // prints "pr"
let s: (String) -> Double = { _ in 0.0 }
foo(s) // prints "pr"

But you can actually just write :

func foo<R>(_ f: (()) -> R) { print("r") }
func foo<P, R>(_ f: (P) -> R) { print("pr") }

or even :

func foo<R>(_ f: (Void) -> R) { print("r") } // triggers warning :
// When calling this function in Swift 4 or later, you must pass a '()' tuple; did you mean for the input type to be '()'?
func foo<P, R>(_ f: (P) -> R) { print("pr") }

and you get the same results.



Related Topics



Leave a reply



Submit