Swift:函数类型的泛型类的专用方法



对于通用的自由函数,我可以使用重载, 基本上将函数专用化为函数类型,如下所示:

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()的第二个版本不受协议约束, 主要是因为据我所知,我们无法使函数类型符合协议 (我们不能扩展非名义类型)。我可以创建一个OneParamFunction协议, 并且可以在受约束的foo()中使用它,但我无法使所有参数都成为单参数 函数类型符合该协议。

但是上述重载在没有协议约束的情况下工作。

泛型类的实例方法是否可能出现这样的事情?

对我来说,这种语法看起来最自然,但它不受支持:

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") }
}

在泛型类上创建协议约束扩展的"正常"方法 看起来像这样:

extension Generic1 where T: OneParamFunction { ... }

但如上所述,我无法使函数类型符合 OneParamFunction 协议。

我也不能只创建一个(无重载/专用化)实例方法 然后转发到 free 函数,这不起作用:

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"

编译,但总是调用未知的T版本,我认为是因为类型擦除。 在 Generic2 中,编译器并不真正知道 T 是什么。 Generic2 没有在 T 上定义任何有助于编译器的协议约束 正确调度myModule.foo()调用(它不能有这样的约束,见上文)。

在泛型类中使用方法重载编译并且看起来很接近, 但仍然不起作用,尽管在这种情况下我不确定为什么。

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的回答。

我认为我在这里希望的不是真正的动态调度,我希望有静态调度,而是基于调用站点上已知的所有类型信息,而不是基于先前在Generic.init()期间推断T的类型擦除值。这确实适用于自由函数,但不适用于成员函数。

试试这个:

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 is function"版本,即使T也在g()内部被类型擦除。我认为这更类似于Generic(type(of: f)).foo(),而不是 Robg<T>()调用foo()的例子(这更类似于从Generic的其他成员调用Generic.foo()——在这种情况下,我确实理解为什么T是未知的)。

在这两种情况下(Generic(type(of: f)).foo()foo(g(type(of: f)))),有两种类型:

  1. 原始f类型,以及
  2. 从第一次调用返回的类型 (Generic.init()/g())。

但显然,在调用自由函数foo()时,对foo()的后续调用是基于类型 #1 调度的,而类型 #2 用于调度到成员函数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()调用则没有。我仍然想知道为什么。

是的,有点,但是您正在做的事情并没有真正按照您可能想要的方式工作,并且其他解决方案将以类似的方式失败,基本上使其无用。

首先,让我们跳到您正在寻找的答案(但不会做您可能想要的)。你的问题只是语法。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 以后可能会改进这一点。但你想做的深刻的,破碎的。这种方式重载不会使静态事物动态化。像这样的专业化绝不能改变语义,因为你无法确定哪个会被调用。例如,使用顶级函数:

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,那么它将选择另一个重载,但在这里无法证明这一点。最糟糕的是,如果编译器将来改进,它可能会调用另一个函数。

像这样模棱两可的重载的要点是优化,而不是替代基于类的继承。例如,某些算法在任何序列上都是可能的,但在双向集合上更有效,因此在可能的情况下,对where Self: BidirectionalCollection进行重载以使事情更快是有意义的,但无论哪种情况,结果都必须相同。

所以回到我原来的答案,它匹配你的代码,但它不会做你想要的:

let x = Generic1(type(of: f))
x.foo() // T is unknown

您可能希望对泛型类使用多个泛型参数。

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. 找到一种将其与非函数类型一起使用的方法

这是我得到的:

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

输出看起来像这样:

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

最新更新