问题
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.
回答1:
Yes, sort of, but what you're doing doesn't really work the way you likely intend it to, and other solutions will fail in similar ways that basically make it useless.
First, let's skip to the answer you're looking for (but won't do what you probably want). Your problem is just syntax. Swift doesn't support this syntax:
extension Generic1<P>
where T == ((P) -> Void) {
func foo() { print("T is a function with one parameter") }
}
Instead you write it this way:
extension Generic1
{
func foo<P>() where T == ((P) -> Void) { print("T is a function with one parameter") }
}
As I said, this is just syntax. It's nothing deep, and Swift may improve this later. But what you're trying to do is deep, and broken. Overloading this way does not make static things dynamic. Specializations like this must never change semantics, because you can't be certain which will be called. For example, using your top-level functions:
func g<T>(_ x: T) {
foo(type(of: x))
}
g(1) // T is unknown
g(f) // T is unknown
The problem is that g
resolves foo
in the context of "T can be any type at all." In that context, it selects your "unknown" case. That's determined at compile-time, based on the best information available. If the compiler can prove that T
is (P) -> Void
, then it will select the other overload, but it can't prove that here. Worst, if the compiler improves in the future, it might call the other function.
The point of ambiguous overloads like this is optimization, not a replacement for class-based inheritance. For example, some algorithms are possible on any Sequence, but more efficient on BidirectionalCollection, so it makes sense to have an overload for where Self: BidirectionalCollection
to make things faster when possible, but the result must be the same in either case.
So coming back to my original answer, it matches your code, but it won't do what you want:
let x = Generic1(type(of: f))
x.foo() // T is unknown
回答2:
You may want to use more than one generic parameter for your Generic class.
class Generic1<P, R> {
init(_ t: ((P) -> R).Type) {}
}
extension Generic1 where P == Void
{ func foo() { print("T is unknown") } }
extension Generic1{
func foo() { print("T is a function with one parameter") }
}
let f: (String) -> Void = { print($0) }
Generic1(type(of: f)).foo() // prints "T is a function with one parameter"
let v: (()) -> Void = { print($0) } // a bit ugly ;)
Generic1(type(of: v)).foo() // prints "T is unknown"
But it would be better with generic type aliases ;)
EDIT
So taking your comment into accout I tried to :
- get rid of the
()
s - find a way to scale up the number of supported params without asking too much to the client (that's up for debate though)
- find a way to use it with non function type
here's what I got:
// some generic type aliases
typealias Bar<P, R> = (P) -> R
typealias Foo<P> = Bar<P, Void>
typealias Quux<P, Q, R> = (P, Q) -> R
typealias Qux<P, Q> = Quux<P, Q, Void>
typealias Xyzyy<S, P, Q, R> = (S, P, Q) -> R
// some closures
let fooString: Foo<String> = { print($0) }
let barIntVoid: Bar<Int, Void> = { print($0) }
let quuxStringIntString: Quux<String, Int, String> = { "\($0)\($1)"}
let quuxStringIntVoid: Quux<String, Int, Void> = { print("\($0)\($1)") }
let xyzyyDateStringIntVoid: Xyzyy<Date, String, Int, Void> = { print("\($0): \($1)\($2)") }
// same class as before
class Generic2<G> {
init(_ t: G.Type) {}
}
// handling any type
extension Generic2 {
func foo<T>(_ f: T) {
print("\(T.self) is \(T.self == G.self ? "known" : "unknown")")
}
}
// these methods are put in an unspecialized extension in order to be "shared"
// I guess if your designing a module you probably won't be able to handle all the possibilities
// but I'm not sure you should anyway.
// it should be possible to extends Generic2 outside it's module to handle custom case though
extension Generic2 {
func foo<P,R>(p: P.Type, r: R.Type) {
print("f is a function with one parameter of type `\(P.self)` returning `\(R.self)`")
print("\(Bar<P,R>.self) is \(G.self == Bar<P,R>.self ? "known" : "unknown")")
}
func foo<P, Q,R>(p: P.Type, q: Q.Type, r: R.Type) {
print("f is a function with two parameter of type `\(P.self)` and `\(Q.self)` returning `\(R.self)`")
print("\(Quux<P, Q, R>.self) is \(G.self == Quux<P, Q, R>.self ? "known" : "unknown")")
}
func foo<S, P, Q,R>(s: S.Type, p: P.Type, q: Q.Type, r: R.Type) {
print("f is a function with two parameter of type `\(S.self)`, `\(P.self)` and `\(Q.self)` returning `\(R.self)`")
print("\(Xyzyy<S, P, Q, R>.self) is \(G.self == Xyzyy<S, P, Q, R>.self ? "known" : "unknown")")
}
}
// you have to create an extension an write an overload of `foo(_:)` for each type you want to support
extension Generic2 where G == Bar<String, Void> {
func foo(_ f: G) {
foo(p: String.self, r: Void.self)
}
}
extension Generic2 where G == Bar<Int, Void> {
func foo(_ f: G) {
foo(p: Int.self, r: Void.self)
}
}
extension Generic2 where G == Quux<String, Int, String> {
func foo(_ f: G) {
foo(p: String.self, q: Int.self, r: String.self)
}
func foo(p: String, q: Int, f: G) {
foo(f)
f(p,q)
}
}
extension Generic2 where G == Quux<String, Int, Void> {
func foo(_ f: G) {
foo(p: String.self, q: Int.self, r: Void.self)
}
func foo(p: String, q: Int, f: G) {
foo(f)
f(p,q)
}
}
I tested it like that :
print("fooString:")
Generic2(Foo<String>.self).foo(fooString)
print("\nbarIntVoid:")
Generic2(Bar<Int, Void>.self).foo(barIntVoid)
print("\nquuxStringIntString:")
Generic2(Quux<String, Int, String>.self).foo(quuxStringIntString)
print("\nquuxStringIntString:")
Generic2(Quux<String, Int, Void>.self).foo(quuxStringIntString)
print("\nquuxStringIntVoid:")
Generic2(Quux<String, Int, Void>.self).foo(p: "#", q:1, f: quuxStringIntVoid) // prints "#1"
print("\nxyzyyDateStringIntVoid:")
Generic2(Xyzyy<Date, String, Int, Void>.self).foo(xyzyyDateStringIntVoid)
print("\nnon function types:")
Generic2(Foo<String>.self).foo(Int.self)
Generic2(Foo<String>.self).foo(1)
Generic2(Int.self).foo(1)
and the output looks like that :
fooString:
f is a function with one parameter of type `String` returning `()`
(String) -> () is known
barIntVoid:
f is a function with one parameter of type `Int` returning `()`
(Int) -> () is known
quuxStringIntString:
f is a function with two parameter of type `String` and `Int` returning `String`
(String, Int) -> String is known
quuxStringIntString:
(String, Int) -> String is unknown
quuxStringIntVoid:
f is a function with two parameter of type `String` and `Int` returning `()`
(String, Int) -> () is known
#1
xyzyyDateStringIntVoid:
(Date, String, Int) -> () is known
non function types:
Int.Type is unknown
Int is unknown
Int is known
EDIT
At this point I'm not sure if I should keep previous edits, but this one is shorter.
I just changed you second overload to :
class Generic_<T> {
init(_ t: T.Type) {}
func member_foo() { print("T is unknown") }
func member_foo<P>(_ type: P.Type) { print("T is a function with one parameter") }
}
It behavior is unchanged for free_function :
free_foo(g_(type(of: f))) // T is function
free_foo(g_(String.self)) // T is unknown
BUT now it also works with Generic_
's members :
let generic = Generic_(Bar<String, Int>.self)
generic.member_foo() // T is unknown
generic.member_foo(String.self) // T is a function with one parameter
来源:https://stackoverflow.com/questions/62355933/swift-specialize-method-of-generic-class-for-function-types