可以使用条件来确定泛型的类型吗?

Can a condition be used to determine the type of a generic?

我将首先解释我正在尝试做什么,以及我是如何到达问题所在的地方的。


作为我自己的学习练习,我用 Objective-C 解决了一些问题,看看我如何用 Swift 解决它们。我遇到的具体情况是一小块,它在变化前后捕获一个值,并在两者之间进行插值以创建动画的关键帧。

为此,我有一个对象 Capture,其中包含对象的属性、键路径和前后值的两个 id 属性。后来,在对捕获的值进行插值时,我确保可以通过将它们中的每一个包装在一个 Value class 中来进行插值,该 class 簇用于 return 一个适当的 class 取决于它包装的值的类型,或者 nil 对于不受支持的类型。

这行得通,我也能够按照相同的模式使其在 Swift 中工作,但感觉不像 Swift。


什么有效

我没有将捕获的值包装成启用插值的方式,而是创建了一个 Mixable 类型可以遵守的协议,并在类型支持必要的基本算法时使用协议扩展:

protocol SimpleArithmeticType {
    func +(lhs: Self, right: Self) -> Self
    func *(lhs: Self, amount: Double) -> Self
}

protocol Mixable {
    func mix(with other: Self, by amount: Double) -> Self
}

extension Mixable where Self: SimpleArithmeticType {
    func mix(with other: Self, by amount: Double) -> Self {
        return self * (1.0 - amount) + other * amount
    }
}

这部分工作得很好并且强制执行同质混合(一个类型只能与其自己的类型混合),这在 Objective-C 实现中没有强制执行。

我卡在哪里

下一个合乎逻辑的步骤,也是我被卡住的地方,似乎是让每个 Capture 实例(现在是一个结构)保存两个相同可混合类型的变量,而不是两个 AnyObject。我还将初始化参数从一个对象和一个关键路径更改为一个 return 是一个对象 ()->T

的闭包
struct Capture<T: Mixable> {
    typealias Evaluation = () -> T
    let eval: Evaluation

    let before: T
    var after: T {
        return eval()
    }

    init(eval: Evaluation) {
        self.eval = eval
        self.before = eval()
    }
}

这适用于可以推断类型的情况,例如:

let captureInt = Capture {
    return 3.0
}
// > Capture<Double>

但不使用键值编码,即return AnyObject:\

let captureAnyObject = Capture {
    return myObject.valueForKeyPath("opacity")!
}

error: cannot invoke initializer for type 'Capture' with an argument list of type '(() -> _)'

AnyObject 不符合 Mixable 协议,所以我能理解为什么这不起作用。但是我可以检查对象的真正类型,并且由于我只涵盖了少数可混合类型,所以我虽然可以涵盖所有情况和 return 正确的捕获类型。太清楚了 if 这甚至可以工作 我做了一个更简单的例子

一个更简单的例子

struct Foo<T> {
    let x: T
    init(eval: ()->T) {
        x = eval()
    }
}

在保证类型推断时有效:

let fooInt = Foo {
    return 3
}
// > Foo<Int>
let fooDouble = Foo {
    return 3.0
}
// > Foo<Double>

但不关闭时可以return不同类型

let condition = true
let foo = Foo {
    if condition {
        return 3
    } else {
        return 3.0
    }
}

error: cannot invoke initializer for type 'Foo' with an argument list of type '(() -> _)'

我什至无法自己定义这样的闭包。

let condition = true // as simple as it could be
let evaluation = {
    if condition {
        return 3
    } else {
        return 3.0
    }
}

error: unable to infer closure type in the current context

我的问题

这完全可以做到吗?可以使用条件来确定泛型的类型吗?或者是否有另一种方法来保存两个相同类型的变量,其中类型是根据条件决定的?


编辑

我真正想要的是:

  1. 捕获更改前后的值并保存这对(旧 + 新)供以后使用(同类对的异构集合)。
  2. 遍历所有收集的值并去除无法插值的值(除非此步骤可以与收集步骤集成)
  3. 单独插入每个同类对(混合旧+新)。

但在解决这个问题时,这个方向似乎是死胡同。我将不得不后退几步并尝试不同的方法(如果我再次遇到困难,可能会问一个不同的问题)。

正如在 Twitter 中所讨论的,类型必须在编译时已知。不过,对于问题末尾的简单示例,您可以明确键入

let evaluation: Foo<Double> = { ... }

它会起作用的。

因此,在 CapturevalueForKeyPath: 恕我直言的情况下,您应该将值强制转换(安全地或强制转换)为您期望的 Mixable 类型它应该工作正常。毕竟,我不确定 valueForKeyPath: 是否应该根据条件 return 不同的类型。

您想要 return 2 种完全不同的类型的确切情况是什么(不能像上面 IntDouble 的简单情况那样隐式转换) 在同一个评估结束?

in my full example I also have cases for CGPoint, CGSize, CGRect, CATransform3D

由于 Swift 的严格输入,限制正如您所说。在编译时必须明确知道所有类型,并且每个事物只能是一种类型——甚至是泛型(通过在编译时调用 的方式来解析)。因此,您唯一能做的就是将您的类型变成更像 Objective-C 本身的伞形类型:

let condition = true
let evaluation = {
    () -> NSObject in // *
    if condition {
        return 3
    } else {
        return NSValue(CGPoint:CGPointMake(0,1))
    }
}