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

5

对于通用的自由函数,我可以使用重载来将函数针对函数类型进行专门化,例如:

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协议。

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

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的类型参数已经完全确定,所以我认为编译器应该具备所有必要的类型信息来正确地分派调用,但实际情况并非如此,它仍然打印出"unknown 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”版本,尽管Tg()中也被类型擦除了。我认为这更类似于Generic(type(of: f)).foo(),而不是Rob示例中使用g<T>()调用foo()(这更类似于从Generic的其他成员调用Generic.foo() -- 在这种情况下,我理解为什么T是未知的)。
在两种情况下(Generic(type(of: f)).foo() vs foo(g(type(of: f)))),有两种类型: 1. f的原始类型,以及 2. 第一次调用(Generic.init() / g())返回的类型。
但显然,对于调用自由函数foo(),后续的foo()调用是基于类型#1进行分派,而对于调用成员函数Generic.foo(),则使用类型#2进行分派。
起初我以为差异与上面的示例中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()调用则不是。我仍然在想为什么。

我现在看到的问题是你的 member_foo<P>() 函数没有参数来帮助编译器推断 P,因为我们已经知道它不支持“解构”函数类型。但是我想我找到了一种方法来编写它,以便它打印出你想要的内容。我会告诉你的 ;) - AnderCover
2个回答

1

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

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. 去掉 ()
  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


感谢您的回复。这种方法似乎存在几个问题。 1)需要客户端代码使用特殊语法(例如示例中的v)。 2)支持参数数量的扩展会导致客户端语法更加奇怪。 3)无法与非函数类型一起使用(foo(Int.self)--应该打印“unknown T”)。 使用重载的自由函数可以避免所有这些问题,自然语法适用于所有类型。 - imre
就像我说的那样,使用通用类型别名会更好。 - AnderCover
1
提醒一下,我还在试用编译器,如果我发现了什么新的东西,我会更新我的答案。不过我不太确定你想做什么,也许你已经有一个适合自己的解决方案了。我只是喜欢挑战 :) - AnderCover
我的解决方案是使用免费函数 :)。但这会导致客户端语法有些丑陋(foo(bar(create(x)))而不是create(x).foo().bar())。用例基本上是注册Swift方法,以便稍后可以根据从脚本或网络接收的字符串调用它们。类似于export(MyClass.foo, as: "foo")。在更复杂的情况下(例如,当导出方法的参数是回调函数时),我们需要一些后续操作:export(...).doStuff(),但由于问题中的问题,我需要将doStuff变成一个自由函数,这有点奇怪。 - imre
我完全同意,自由函数看起来很丑,而泛型则非常好看 ;) - AnderCover
@imre,我想知道你是否看到了我在帖子底部的最后一次编辑,它对你有帮助吗? - AnderCover

1
是的,有点像,但你所做的并不会按照你预期的方式工作,其他解决方案也会以类似的方式失败,基本上使其无用。
首先,让我们跳到你正在寻找的答案(但不会实现你可能想要的功能)。你的问题只是语法。Swift不支持这种语法:
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") }
}

如我所说,这只是语法问题,不涉及深层次的问题,Swift可能会在以后改进这个问题。但你试图做的事情确实很深奥,而且存在问题。像这样重载函数不会使静态内容变得动态。这种专业化必须永远不要改变语义,因为无法确定哪个会被调用。例如,使用你的顶级函数:
func g<T>(_ x: T) {
    foo(type(of: x))
}

g(1) // T is unknown
g(f) // T is unknown

问题在于gfoo解析为“T可以是任何类型”。在这种情况下,它选择了您的“未知”情况。这是基于可用的最佳信息在编译时确定的。如果编译器能够证明T(P) -> Void,则它将选择另一个重载,但它无法在这里证明。更糟糕的是,如果编译器在未来改进,它可能会调用其他函数。
这种歧义性重载的重点是优化,而不是替代基于类的继承。例如,一些算法对任何序列都是可能的,但在双向集合上更有效,因此有一个重载where Self: BidirectionalCollection可以使事情更快,但结果必须在任何情况下相同。
所以回到我的原始答案,它符合您的代码,但它不会做你想要的事情。
let x = Generic1(type(of: f))
x.foo() // T is unknown

谢谢。我认为你的g()示例有点不同(我确实知道它是如何工作的),而我所问的仍然是静态而非动态分派,问题更多地涉及到静态分派基于的确切类型以及为什么在调用自由函数和调用成员函数的情况下似乎存在差异。解释起来太长了,你能否请看一下问题更新?(顺便说一句,你说正确的语法已经在原始问题中作为“Generic3”变体出现了)。 - imre
2
我看到了更新;是的,那看起来像是当前编译器的限制。我相信这个问题就是:https://bugs.swift.org/browse/SR-10610。这不应该有什么影响,除了性能优化之外,但你说得对,这是一种有点意外的限制,而不是基本类型问题。对于性能优化来说,这确实令人沮丧。 - Rob Napier
另一个可能有所影响的领域是API语法。在类似但更复杂的情况下,我现在可以支持像foo(bar(create()))这样的语法,但不支持更自然的create().foo().bar()。这不是一个很大的问题,我只是对其背后的原因感到好奇。感谢澄清。 - imre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接