Swift:为函数类型专门化泛型类的方法

imr*_*mre 5 generics overload-resolution 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"
Run Code Online (Sandbox Code Playgroud)

注意第二个版本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") }
}
Run Code Online (Sandbox Code Playgroud)

在 Generic 类上创建协议约束扩展的“正常”方式如下所示:

extension Generic1 where T: OneParamFunction { ... }
Run Code Online (Sandbox Code Playgroud)

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

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

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"
Run Code Online (Sandbox Code Playgroud)

编译,但总是调用未知-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"
Run Code Online (Sandbox Code Playgroud)

这里在调用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"
Run Code Online (Sandbox Code Playgroud)

我还有其他选择吗?


更新,以回应 Rob Napier 的回答。

我想我在这里想要的不是真正的动态调度,我想要静态调度,而是基于调用站点上已知的所有类型信息,而不是基于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"
Run Code Online (Sandbox Code Playgroud)

这确实调用了 的“T 是函数”版本foo,即使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))))都有两种类型:

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

但显然,foo()在调用 free 函数时foo(),随后的调用是根据类型 #1 分派的,而类型 #2 用于分派到成员函数Generic.foo()

首先,我认为差异与上面示例中的g()返回方式有关T.Type,而结果Generic.init()是 a 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
Run Code Online (Sandbox Code Playgroud)

在这种情况下,Generic.init和 都g()返回Generic<T>。然而,free_foo()呼叫似乎是根据 的完整原始类型调度的f,而member_foo()呼叫则没有。我仍然想知道为什么。

And*_*ver 1

您可能希望为您的 Generic 类使用多个泛型参数。

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"

Run Code Online (Sandbox Code Playgroud)

但使用泛型类型别名会更好;)

编辑

因此,考虑到您的评论,我尝试:

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

Run Code Online (Sandbox Code Playgroud)

我这样测试过:

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)

Run Code Online (Sandbox Code Playgroud)

输出如下所示:

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

Run Code Online (Sandbox Code Playgroud)

编辑

此时我不确定是否应该保留以前的编辑,但这个更短。

我刚刚将第二个重载更改为:

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

}
Run Code Online (Sandbox Code Playgroud)

free_function 的行为没有改变:

free_foo(g_(type(of: f)))   //  T is function
free_foo(g_(String.self))   // T is unknown

Run Code Online (Sandbox Code Playgroud)

现在它也适用于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

Run Code Online (Sandbox Code Playgroud)