简体   繁体   English

Swift:function 类型的通用 class 的专用方法

[英]Swift: Specialize method of generic class for function types

For generic free functions I can use overloading, to essentially specialize the function for function types, like this:对于通用的自由函数,我可以使用重载,基本上将 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"

Note the second version of foo() is not protocol-constrained, mainly because as far as I know, we can't make function types conform to protocols (we can't extend non-nominal types).注意foo()的第二个版本不受协议约束,主要是因为据我所知,我们不能使 function 类型符合协议(我们不能扩展非标称类型)。 I could create a OneParamFunction protocol, and could use that in a constrained foo() , but I couldn't make all one-parameter function types conform to that protocol.我可以创建一个OneParamFunction协议,并可以在受约束的foo()中使用它,但我不能使所有单参数 function 类型都符合该协议。

But the above overload works without protocol constraints.但是上述重载在没有协议约束的情况下工作。

Is something like this possible for an instance method of a generic class?对于通用 class 的实例方法,这样的事情可能吗?

To me, this syntax would seem most natural, but it's not supported:对我来说,这种语法似乎是最自然的,但它不受支持:

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

The "normal" way of creating protocol-constrained extensions on the Generic class would look like this:在通用 class 上创建协议约束扩展的“正常”方式如下所示:

extension Generic1 where T: OneParamFunction { ... }

but as discussed above, I can't make function types conform to the OneParamFunction protocol.但如上所述,我不能使 function 类型符合 OneParamFunction 协议。

I also can't just create a single (no overloads / specializations) instance method and then forward to the free function, this doesn't work:我也不能只创建一个(无重载/特化)实例方法,然后转发到免费的 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"

Compiles, but always calls the unknown-T version, I think because of type erasure.编译,但总是调用 unknown-T 版本,我认为是因为类型擦除。 Inside Generic2, the compiler doesn't really know what T is.在 Generic2 中,编译器并不真正知道 T 是什么。 Generic2 doesn't define any protocol constraints on T that would help the compiler properly dispatch the myModule.foo() call (and it can't have such constraints, see above). Generic2 没有在 T 上定义任何有助于编译器正确调度myModule.foo()调用的协议约束(并且它不能有这样的约束,见上文)。

Using method overloading inside the generic class compiles and seems close, but still doesn't work, although in this case I'm not sure why.在通用 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"

Here at the site of calling foo() the type parameter of Generic3 is fully known, so it seems to me that the compiler would have all the necessary type information to correctly dispatch the call, but that's not what happens, it still prints "unknown T".在调用foo()的站点上,Generic3 的类型参数是完全已知的,所以在我看来,编译器将拥有正确调度调用所需的所有类型信息,但事实并非如此,它仍然打印“未知T”。

Not even repeating the type as a parameter to foo() helps (wouldn't be ideal anyway):甚至没有将类型重复作为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"

Do I have any further options?我还有其他选择吗?


Update , in response to Rob Napier's answer.更新,以回应 Rob Napier 的回答。

I think what I wish for here isn't really dynamic dispatch, I'd like to have static dispatch, but based on all the type information known at the call site , rather than based on the type-erased value for T previously inferred during Generic.init() .我认为我在这里希望的不是真正的动态调度,我希望有 static 调度,但基于调用站点已知的所有类型信息,而不是基于之前在期间推断的T的类型擦除值通用的Generic.init() And that does work with free functions, but not with member functions.这确实适用于自由函数,但不适用于成员函数。

Try this:尝试这个:

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"

This does call the "T is function" version of foo , even though T gets type-erased inside g() too.这确实调用了foo的“T 是函数”版本,即使Tg()中也被类型擦除。 And I think this is more similar to Generic(type(of: f)).foo() than Rob's example with g<T>() calling foo() (which is more analogous to calling Generic.foo() from some other member of Generic -- in this case I do understand why T is unknown).而且我认为这更类似于Generic(type(of: f)).foo()而不是 Rob 的示例,其中g<T>()调用foo() (这更类似于从其他一些调用Generic.foo() Generic的成员——在这种情况下,我明白为什么T是未知的)。

In both cases ( Generic(type(of: f)).foo() vs foo(g(type(of: f))) ) there are two types:在这两种情况下( Generic(type(of: f)).foo() vs foo(g(type(of: f))) )有两种类型:

  1. the original type of f , and f的原始类型,和
  2. the type returned from the first call ( Generic.init() / g() ).第一次调用返回的类型( Generic.init() / g() )。

But apparently the subsequent call to foo() is dispatched based on type #1 when calling the free function foo() , while type #2 is used for dispatching to member function Generic.foo() .但显然,在调用免费的 function foo()时,随后对foo()的调用是基于类型 #1 调度的,而类型 #2 用于调度成员 function Generic.foo Generic.foo()

First I thought that the difference has to do with how in the above example g() returns T.Type , while the result of Generic.init() is a Generic<T> , but no:首先,我认为差异与上述示例中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

In this case both Generic.init and g() return Generic<T> .在这种情况下, Generic.initg()都返回Generic<T> And yet, the free_foo() call seems to get dispatched based on the full original type of f , while the member_foo() call does not.然而, free_foo()调用似乎是根据f的完整原始类型进行调度的,而member_foo()调用则没有。 I still wonder why.我仍然想知道为什么。

You may want to use more than one generic parameter for your Generic class.您可能希望为您的通用 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"

But it would be better with generic type aliases ;)但是使用泛型类型别名会更好;)

EDIT编辑

So taking your comment into accout I tried to:因此,考虑到您的评论,我试图:

  1. get rid of the () s摆脱() s
  2. find a way to scale up the number of supported params without asking too much to the client (that's up for debate though)找到一种方法来扩大支持的参数数量而不向客户提出太多要求(尽管这有待商榷)
  3. find a way to use it with non function type找到一种与非 function 类型一起使用的方法

here's what I got:这是我得到的:

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

I tested it like that:我是这样测试的:

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)

and the output looks like that: 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

EDIT编辑

At this point I'm not sure if I should keep previous edits, but this one is shorter.在这一点上,我不确定是否应该保留以前的编辑,但这个更短。

I just changed you second overload to:我刚刚将您的第二个重载更改为:

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

}

It behavior is unchanged for free_function: free_function 的行为没有改变:

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

BUT now it also works with Generic_ 's members:现在它也适用于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

Yes, sort of, but what you're doing doesn't really work the way you likely intend it to, and other solutions will fail in similar ways that basically make it useless.是的,有点,但是您正在做的事情并没有真正按照您可能想要的方式工作,并且其他解决方案将以类似的方式失败,基本上使其无用。

First, let's skip to the answer you're looking for (but won't do what you probably want).首先,让我们跳到您正在寻找的答案(但不会做您可能想要的)。 Your problem is just syntax.你的问题只是语法。 Swift doesn't support this syntax: 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") }
}

As I said, this is just syntax.正如我所说,这只是语法。 It's nothing deep, and Swift may improve this later.没什么深的,Swift 以后可能会改进。 But what you're trying to do is deep, and broken.但是你想要做的是深刻的,破碎的。 Overloading this way does not make static things dynamic.以这种方式重载不会使 static 事物动态化。 Specializations like this must never change semantics, because you can't be certain which will be called.像这样的特化永远不能改变语义,因为你不能确定哪个会被调用。 For example, using your top-level functions:例如,使用您的顶级函数:

func g<T>(_ x: T) {
    foo(type(of: x))
}

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

The problem is that g resolves foo in the context of "T can be any type at all."问题是g在“T 可以是任何类型”的上下文中解析foo In that context, it selects your "unknown" case.在这种情况下,它会选择您的“未知”案例。 That's determined at compile-time, based on the best information available.这是在编译时根据可用的最佳信息确定的。 If the compiler can prove that T is (P) -> Void , then it will select the other overload, but it can't prove that here.如果编译器可以证明T(P) -> Void ,那么它将 select 另一个重载,但它不能在这里证明。 Worst, if the compiler improves in the future, it might call the other function.最糟糕的是,如果编译器将来改进,它可能会调用另一个 function。

The point of ambiguous overloads like this is optimization, not a replacement for class-based inheritance.像这样的模棱两可重载的重点是优化,而不是替代基于类的 inheritance。 For example, some algorithms are possible on any Sequence, but more efficient on BidirectionalCollection, so it makes sense to have an overload for where Self: BidirectionalCollection to make things faster when possible, but the result must be the same in either case.例如,某些算法在任何序列上都是可能的,但在 BidirectionalCollection 上更有效,因此对where Self: BidirectionalCollection进行重载是有意义的,以尽可能加快处理速度,但在任何一种情况下结果都必须相同。

So coming back to my original answer, it matches your code, but it won't do what you want:所以回到我原来的答案,它匹配你的代码,但它不会做你想做的事:

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