[英]Swift: Specialize method of generic class for function types
對於通用的自由函數,我可以使用重載,基本上將 function 專門用於 function 類型,如下所示:
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"
注意foo()
的第二個版本不受協議約束,主要是因為據我所知,我們不能使 function 類型符合協議(我們不能擴展非標稱類型)。 我可以創建一個OneParamFunction
協議,並可以在受約束的foo()
中使用它,但我不能使所有單參數 function 類型都符合該協議。
但是上述重載在沒有協議約束的情況下工作。
對於通用 class 的實例方法,這樣的事情可能嗎?
對我來說,這種語法似乎是最自然的,但它不受支持:
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") }
}
在通用 class 上創建協議約束擴展的“正常”方式如下所示:
extension Generic1 where T: OneParamFunction { ... }
但如上所述,我不能使 function 類型符合 OneParamFunction 協議。
我也不能只創建一個(無重載/特化)實例方法,然后轉發到免費的 function,這不起作用:
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"
編譯,但總是調用 unknown-T 版本,我認為是因為類型擦除。 在 Generic2 中,編譯器並不真正知道 T 是什么。 Generic2 沒有在 T 上定義任何有助於編譯器正確調度myModule.foo()
調用的協議約束(並且它不能有這樣的約束,見上文)。
在通用 class 中使用方法重載可以編譯並且看起來很接近,但仍然不起作用,盡管在這種情況下我不知道為什么。
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"
在調用foo()
的站點上,Generic3 的類型參數是完全已知的,所以在我看來,編譯器將擁有正確調度調用所需的所有類型信息,但事實並非如此,它仍然打印“未知T”。
甚至沒有將類型重復作為foo()
的參數有幫助(無論如何都不理想):
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"
我還有其他選擇嗎?
更新,以回應 Rob Napier 的回答。
我認為我在這里希望的不是真正的動態調度,我希望有 static 調度,但基於調用站點已知的所有類型信息,而不是基於之前在期間推斷的T
的類型擦除值通用的Generic.init()
。 這確實適用於自由函數,但不適用於成員函數。
嘗試這個:
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"
這確實調用了foo
的“T 是函數”版本,即使T
在g()
中也被類型擦除。 而且我認為這更類似於Generic(type(of: f)).foo()
而不是 Rob 的示例,其中g<T>()
調用foo()
(這更類似於從其他一些調用Generic.foo()
Generic
的成員——在這種情況下,我明白為什么T
是未知的)。
在這兩種情況下( Generic(type(of: f)).foo()
vs foo(g(type(of: f)))
)有兩種類型:
f
的原始類型,和Generic.init()
/ g()
)。 但顯然,在調用免費的 function foo()
時,隨后對foo()
的調用是基於類型 #1 調度的,而類型 #2 用於調度成員 function Generic.foo Generic.foo()
。
首先,我認為差異與上述示例中g()
返回T.Type
的方式有關,而Generic.init()
的結果是Generic<T>
,但不是:
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
在這種情況下, Generic.init
和g()
都返回Generic<T>
。 然而, free_foo()
調用似乎是根據f
的完整原始類型進行調度的,而member_foo()
調用則沒有。 我仍然想知道為什么。
您可能希望為您的通用 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"
但是使用泛型類型別名會更好;)
因此,考慮到您的評論,我試圖:
()
s這是我得到的:
// 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)
}
}
我是這樣測試的:
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)
output 看起來像這樣:
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
在這一點上,我不確定是否應該保留以前的編輯,但這個更短。
我剛剛將您的第二個重載更改為:
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") }
}
free_function 的行為沒有改變:
free_foo(g_(type(of: f))) // T is function
free_foo(g_(String.self)) // T is unknown
但現在它也適用於Generic_
的成員:
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
是的,有點,但是您正在做的事情並沒有真正按照您可能想要的方式工作,並且其他解決方案將以類似的方式失敗,基本上使其無用。
首先,讓我們跳到您正在尋找的答案(但不會做您可能想要的)。 你的問題只是語法。 Swift 不支持此語法:
extension Generic1<P>
where T == ((P) -> Void) {
func foo() { print("T is a function with one parameter") }
}
相反,你這樣寫:
extension Generic1
{
func foo<P>() where T == ((P) -> Void) { print("T is a function with one parameter") }
}
正如我所說,這只是語法。 沒什么深的,Swift 以后可能會改進。 但是你想要做的是深刻的,破碎的。 以這種方式重載不會使 static 事物動態化。 像這樣的特化永遠不能改變語義,因為你不能確定哪個會被調用。 例如,使用您的頂級函數:
func g<T>(_ x: T) {
foo(type(of: x))
}
g(1) // T is unknown
g(f) // T is unknown
問題是g
在“T 可以是任何類型”的上下文中解析foo
。 在這種情況下,它會選擇您的“未知”案例。 這是在編譯時根據可用的最佳信息確定的。 如果編譯器可以證明T
是(P) -> Void
,那么它將 select 另一個重載,但它不能在這里證明。 最糟糕的是,如果編譯器將來改進,它可能會調用另一個 function。
像這樣的模棱兩可重載的重點是優化,而不是替代基於類的 inheritance。 例如,某些算法在任何序列上都是可能的,但在 BidirectionalCollection 上更有效,因此對where Self: BidirectionalCollection
進行重載是有意義的,以盡可能加快處理速度,但在任何一種情況下結果都必須相同。
所以回到我原來的答案,它匹配你的代碼,但它不會做你想做的事:
let x = Generic1(type(of: f))
x.foo() // T is unknown
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.