For generic free functions I can use overloading, to essentially specialize the function for function types, like this:
func foo<T>(_ t: T.Type) { print("T is unknown") }
func foo<P>(_ t: ((P) -> Void).Type) { print("T is a function with one parameter") }
let f: (String) -> Void = { print($0) }
foo(type(of: f)) // prints "T is a function with one parameter"
Note the second version of foo()
is not protocol-constrained,
mainly because as far as I know, we can't make function types conform to protocols
(we can't extend non-nominal types). I could create a OneParamFunction
protocol,
and could use that in a constrained foo()
, but I couldn't make all one-parameter
function types conform to that protocol.
But the above overload works without protocol constraints.
Is something like this possible for an instance method of a generic class?
To me, this syntax would seem most natural, but it's not supported:
class Generic1<T> { init(_ t: T.Type) {} }
extension Generic1 { func foo() { print("T is unknown") } }
extension Generic1<P>
where T == ((P) -> Void) {
func foo() { print("T is a function with one parameter") }
}
The "normal" way of creating protocol-constrained extensions on the Generic class would look like this:
extension Generic1 where T: OneParamFunction { ... }
but as discussed above, I can't make function types conform to the OneParamFunction protocol.
I also can't just create a single (no overloads / specializations) instance method and then forward to the free function, this doesn't work:
class Generic2<T> {
init(_ t: T.Type) {}
func foo() { myModule.foo(T.self) }
}
let f: (String) -> Void = { print($0) }
Generic2(type(of: f)).foo() // prints "unknown T"
Compiles, but always calls the unknown-T version, I think because of type erasure.
Inside Generic2, the compiler doesn't really know what T is.
Generic2 doesn't define any protocol constraints on T that would help the compiler
properly dispatch the myModule.foo()
call (and it can't have such constraints, see above).
Using method overloading inside the generic class compiles and seems close, but still doesn't work, although in this case I'm not sure why.
class Generic3<T> {
init(_ t: T.Type) {}
func foo() { print("T is unknown") }
func foo<P>() where T == ((P) -> Void) { print("T is a function with one parameter") }
}
let f: (String) -> Void = { print($0) }
Generic3(type(of: f)).foo() // prints "unknown T"
Here at the site of calling foo()
the type parameter of Generic3 is fully known,
so it seems to me that the compiler would have all the necessary type information
to correctly dispatch the call, but that's not what happens, it still prints "unknown T".
Not even repeating the type as a parameter to foo()
helps (wouldn't be ideal anyway):
class Generic4<T> {
init(_ t: T.Type) {}
func foo(_ t: T.Type) { print("T is unknown") }
func foo<P>(_ t: T.Type) where T == ((P) -> Void) { print("T is a function with one parameter") }
}
let f: (String) -> Void = { print($0) }
Generic4(type(of: f)).foo(type(of: f)) // still prints "unknown T"
Do I have any further options?
Update, in response to Rob Napier's answer.
I think what I wish for here isn't really dynamic dispatch, I'd like to have static dispatch, but based on all the type information known at the call site, rather than based on the type-erased value for T
previously inferred during Generic.init()
. And that does work with free functions, but not with member functions.
Try this:
func foo<T>(_ t: T.Type) { print("T is unknown") }
func foo<P>(_ t: ((P) -> Void).Type) { print("T is a function with one parameter") }
func g<T>(_ x: T.Type) -> T.Type { return x }
let f: (String) -> Void = { print($0) }
foo(g(type(of: f))) // prints "T is a function"
This does call the "T is function" version of foo
, even though T
gets type-erased inside g()
too. And I think this is more similar to Generic(type(of: f)).foo()
than Rob's example with g<T>()
calling foo()
(which is more analogous to calling Generic.foo()
from some other member of Generic
-- in this case I do understand why T
is unknown).
In both cases (Generic(type(of: f)).foo()
vs foo(g(type(of: f)))
) there are two types:
- the original type of
f
, and - the type returned from the first call (
Generic.init()
/g()
).
But apparently the subsequent call to foo()
is dispatched based on type #1 when calling the free function foo()
, while type #2 is used for dispatching to member function Generic.foo()
.
First I thought that the difference has to do with how in the above example g()
returns T.Type
, while the result of Generic.init()
is a Generic<T>
, but no:
class Generic_<T> {
init(_ t: T.Type) {}
func member_foo() { print("T is unknown") }
func member_foo<P>() where T == ((P) -> Void) { print("T is a function with one parameter") }
}
func free_foo<T>(_ g: Generic_<T>) { print("T is unknown") }
func free_foo<P>(_ t: Generic_<(P) -> Void>) { print("T is a function with one parameter") }
func g_<T>(_ t: T.Type) -> Generic_<T> { return Generic_(t) }
free_foo(g_(type(of: f))) // T is function
Generic_(type(of: f)).member_foo() // T is unknown
In this case both Generic.init
and g()
return Generic<T>
. And yet, the free_foo()
call seems to get dispatched based on the full original type of f
, while the member_foo()
call does not. I still wonder why.
()` function is that there is no parameter to help the compiler infer `P` since we've learned that it does not support "destructuring" function types. *But* I think I found a way to write it so that it prints what you want. I'll let you know ;)
– AnderCover Jun 21 '20 at 10:59