簡體   English   中英

Swift:function 類型的通用 class 的專用方法

[英]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 是函數”版本,即使Tg()中也被類型擦除。 而且我認為這更類似於Generic(type(of: f)).foo()而不是 Rob 的示例,其中g<T>()調用foo() (這更類似於從其他一些調用Generic.foo() Generic的成員——在這種情況下,我明白為什么T是未知的)。

在這兩種情況下( Generic(type(of: f)).foo() vs foo(g(type(of: f))) )有兩種類型:

  1. f的原始類型,和
  2. 第一次調用返回的類型( 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.initg()都返回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"

但是使用泛型類型別名會更好;)

編輯

因此,考慮到您的評論,我試圖:

  1. 擺脫() s
  2. 找到一種方法來擴大支持的參數數量而不向客戶提出太多要求(盡管這有待商榷)
  3. 找到一種與非 function 類型一起使用的方法

這是我得到的:

// 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM