【问题标题】:Is there any way to determine sub-protocol conformance for instance conforming to generic protocol in Swift 2 (at runtime or during compilation)?有没有办法确定子协议的一致性,例如符合 Swift 2 中的通用协议(在运行时或编译期间)?
【发布时间】:2015-11-21 13:24:48
【问题描述】:

我正在将 Java 库移植到 Swift 2.0,但在泛型方面遇到了一些问题。

我有以下协议层次结构:

public protocol Graph {
    typealias V: Hashable
    typealias E: Hashable

    func getAllEdges(sourceVertex: V, targetVertex: V) -> Set<E>?
    func getEdge(sourceVertex: V, targetVertex: V) -> E?
    func getEdgeFactory() -> EdgeFactory<V, E>?
    func addEdge(sourceVertex: V, targetVertex: V) -> E?
    func addEdge(sourceVertex: V, targetVertex: V, e: E) -> Bool
    func addVertex(v: V) -> Bool
    func containsEdge(sourceVertex: V, targetVertex: V) -> Bool
    func containsEdge(e: E) -> Bool
    func containsVertex(v: V) -> Bool
    func edgeSet() -> Set<E>
    func edgesOf(v: V) -> Set<E>
    func removeAllEdges<T: CollectionType where T.Generator.Element == E>(edges: T) -> Bool
    func removeAllEdges(sourceVertex: V, targetVertex: V) -> Set<E>?
    func removeAllVertices<T: CollectionType where T.Generator.Element == V>(vertices: T) -> Bool
    func removeEdge(sourceVertex: V, targetVertex: V)
    func removeEdge(e: E) -> Bool
    func removeVertex(v: V) -> Bool
    func vertexSet() -> Set<V>
    func getEdgeSource(e: E) -> V
    func getEdgeTarget(e: E) -> V
    func getEdgeWeight(e: E) -> Double
}

public protocol DirectedGraph: Graph {
    func inDegreeOf(vertex: V) -> Int
    func incomingEdgesOf(vertex: V) -> Set<E>
    func outDegreeOf(vertex: V) -> Int
    func outgoingEdgesOf(vertex: V) -> Set<E>
}

public protocol UndirectedGraph: Graph {    
    func degreeOf(vertex: V) -> Int
}

这是导致麻烦的类的定义:

public class CrossComponentIterator
    <V: Hashable, E: Hashable, D, G: Graph
        where G.V == V, G.E == E>
    : AbstractGraphIterator<V, E>

也就是说,它有一个方法,该方法应该根据传递的图形的实际类型初始化其变量之一 - DirectedGraph 或 UndirectedGraph。

我尝试通过声明执行此操作的函数的多个版本来解决此问题:

func createGraphSpecifics<DG: Graph where DG: DirectedGraph, DG.V == V, DG.E == E>(graph: DG)
    -> CrossComponentIteratorSpecifics<V, E>
{
    return DirectedSpecifics<V, E, DG>(graph: graph)
}

func createGraphSpecifics<UG: Graph where UG: UndirectedGraph, UG.V == V, UG.E == E>(graph: UG)
    -> CrossComponentIteratorSpecifics<V, E>
{
    return UndirectedSpecifics<V, E, UG>(graph: graph)
}

func createGraphSpecifics<GG: Graph where GG.V == V, GG.E == E>(graph: GG)
    -> CrossComponentIteratorSpecifics<V, E>
{
    fatalError("Unknown graph type instance")
}

但不幸的是,只有最后一个版本的函数会被任何图形实例调用(即使它符合“DirectedGraph”或“UndirectedGraph”)

我知道,也许我可以通过将 DirectedGraph 和 UndirectedGraph 协议转换为抽象类来解决这个问题 (我的意思是每个声明的函数中都有 fatalError() 的类,因为 Swift 不支持法律上的抽象类)。

但也许还有另一种更优雅、更快捷的解决方案?

在 Java 中这很简单——在运行时检查接口的一致性:

if (g instanceof DirectedGraph<?, ?>) {
    return new DirectedSpecifics<V, E>((DirectedGraph<V, E>) g);
} else {
    return new UndirectedSpecifics<V, E>(g);
}

编辑这是我想要实现的最少代码:

protocol P {
    // This typealias makes impossible to use 'P'
    // (or its descendants) as a type.
    // It can be used only as generic constraint.
    typealias A

    // myfunc is needed for compiler to infer 'A'
    func myfunc(a: A)
}
protocol P1:P {
    func p1specific(a: A)
}
protocol P2:P {
    func p2specific(a: A)
}

struct S<T:P> {
    init(t: T) {
        // TODO: check if 't' conforms to 'P1', 'P2', both or neither
    }
}

// Examples of concrete implementations of 'P1' and 'P2'
struct S1<X>:P1{
    func myfunc(a: X) {}
    func p1specific(a: X) {}
}
struct S2<X>:P2{
    func myfunc(a: X) {}
    func p2specific(a: X) {}
}

【问题讨论】:

  • 您找到解决方案了吗?是的,请分享。谢谢

标签: swift generics swift2 type-erasure


【解决方案1】:

有什么方法可以确定子协议的一致性吗? 符合 Swift 2 中的通用协议(在运行时或期间 编译)?

是的。

这是我实现类型擦除的技巧,因此可以利用运行时。观察_P

protocol _P {
    static var _A: Any.Type { get }
    func _myfunc(_a: Any) -> Void?
}

extension _P where Self: P {

    static var _A: Any.Type {
        return A.self
    }

    func _myfunc(_a: Any) -> Void? {
        return (_a as? A).map(myfunc)
    }
}

protocol P {
    typealias A

    func myfunc(a: A)
}

protocol _P1:_P {
    func _p1specific(_a: Any) -> Void?
}

extension _P1 where Self: P1 {
    func _p1specific(_a: Any) -> Void? {
        return (_a as? A).map(p1specific)
    }
}

protocol P1:_P1, P {
    func p1specific(a: A)
}

protocol _P2:_P {
    func _p2specific(_a: Any) -> Void?
}

extension _P2 where Self: P2 {
    func _p2specific(_a: Any) -> Void? {
        return (_a as? A).map(p2specific)
    }
}

protocol P2:_P2, P {
    func p2specific(a: A)
}

您现在可以确定一个值是否符合P1P2,并相应地强制转换。此外,通用参数A 现在可以通过不透明的Any.Type 获得。

(x as? _P1) != nil ? true : false

【讨论】:

  • @user3441734:您的反对票是不合理的。请看看我的编辑,希望这能澄清任何疑问。
  • @user3441734:但我没有测试它们是否符合Any.Type。我正在测试A 类型是否符合(x._A as? _P1.Type) != nil ? true : false 中的_P1
  • _A 的类型为 Any.Type。我正在测试这种不透明类型是否符合_P1.Type
  • 请向我展示一个示例,如何在“OP 试图实现的最小代码”中使用它
  • @user3441734:你缺乏知识让我感到震惊。但是,给你:gist.github.com/vmanot/a764f32290ab83550f42
【解决方案2】:
import XCPlayground
import Foundation

protocol P {}
protocol P1:P {}
protocol P2:P {}

struct S1:P1{}
struct S2:P2{}

struct S<T:P> {
    var p1: P1?
    var p2: P2?

    init(t: T) {
        p1 = t as? P1
        p2 = t as? P2
    }
}

let p1 = S1()
let p2 = S2()
let s1 = S(t: p1)
let s2 = S(t: p2)

dump(s1)
dump(s2)
/*
▿ S<S1>
  ▿ p1: S1
    - Some: S1
  - p2: nil
▿ S<S2>
  - p1: nil
  ▿ p2: S2
    - Some: S2
*/

使用

g is Type             // trur or false

let v2 = v1 as? Type   // v2 = v2 or nil

迅速 更新

protocol P {
    typealias A
}
protocol P1:P {}
protocol P2:P {}

struct S1:P1{
    typealias A = Int
}
struct S2:P2{
    typealias A = Double
}

struct S<T:P> {
    var p1: S1?
    var p2: S2?

    init(t: T) {
        p1 = t as? S1
        p2 = t as? S2
    }
}

let p1 = S1()
let p2 = S2()
let s1 = S(t: p1)
let s2 = S(t: p2)

.....

protocol P {
    // This typealias makes impossible to use 'P'
    // (or its descendants) as a type.
    // It can be used only as generic constraint.
    typealias A

    // myfunc is needed for compiler to infer 'A'
    func myfunc(a: A)
}
protocol P1:P {}
protocol P2:P {}

// aka 'abstract' conforming to P1
struct S1:P1{
    typealias A = AnyObject
    func myfunc(a: A) {}
}
// aka 'abstract' conforming to P2
struct S2:P2{
    typealias A = Int
    func myfunc(a: A) {}
}
// generic struct with type conforming to P
struct S<T:P> {
    init(t: T) {
        // TODO: check if 't' conforms to 'P1', 'P2', both or neither
        if t is S1 {
            print("t conforms to P1, because it is type S1")
        }
        if t is S2 {
            print("t conforms to P2, besause it is type S2")
        }
    }
}

let s1 = S(t: S1()) // t conforms to P1, because it is type S1
let s2 = S(t: S2()) // t conforms to P2, besause it is type S2

// WARNING !!!!!!
// var s = s1
// s = s2   // error: cannot assign value of type 'S<S2>' to type 'S<S1>'

【讨论】:

  • 您的代码有效,但如果您将“typealias A”添加到“protocol P”(即使其类似于 Java 中的通用接口),您将在编译期间收到以下错误:“error: protocol ' P1' 只能用作通用约束,因为它具有 Self 或关联的类型要求”。 IE。你不能使用“作为?”因为 P1 和 P2 不是类型,而是泛型约束。
  • 有什么问题吗?给我一个小而简化的示例代码。抽象类的概念在 swift 中不起作用。
  • 当然。我在问题中添加了最小的示例。
  • 我认为,不幸的是,转换为具体的结构/类实现 (p1 = t as? S1) 并不是很有用,因为 P1 和 P2 都可以有很多具体的实现(其中一些可以是通用的)。
  • @PeterProkop 没有办法在 java 和 swift 之间直接“翻译”。你必须考虑到它......
猜你喜欢
  • 2021-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多