【问题标题】:Can a condition be used to determine the type of a generic?可以使用条件来确定泛型的类型吗?
【发布时间】:2015-08-04 16:23:39
【问题描述】:

在回答问题之前,我将首先解释我正在尝试做什么以及我是如何陷入困境的。


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

为此,我有一个对象Capture,其中包含对象属性、键路径和两个id 属性,用于之前和之后的值。后来,在对捕获的值进行插值时,我确保可以通过将它们中的每一个包装在 Value 类中进行插值,该类使用类簇返回适当的类,具体取决于它所包装的值的类型,或者 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。我还将初始化参数从一个对象和一个关键路径更改为一个返回对象()->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>

但不使用键值编码,返回 AnyObject:\

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

错误:无法使用类型为“(() -> _)”的参数列表调用类型“Capture”的初始化程序

AnyObject 不符合Mixable 协议,所以我可以理解为什么这不起作用。但是我可以检查对象的真正类型,并且由于我只介绍了少数可混合的类型,但我可以涵盖所有情况并返回正确的 Capture 类型。太看如果这甚至可以工作我做了一个更简单的例子

一个更简单的例子

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>

但是当闭包可以返回不同的类型时就不行

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

错误:无法使用类型为 '(() -> _)' 的参数列表调用类型 'Foo' 的初始化程序

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

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

错误:无法在当前上下文中推断闭包类型

我的问题

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


编辑

我真正想要的是:

  1. 捕获更改前后的值并保存对(旧 + 新)以供以后使用(同质对的异构集合)。
  2. 遍历所有收集到的值,去掉那些不能插值的(除非这个步骤可以和收集步骤整合)
  3. 单独插入每个同质对(混合旧的 + 新的)。

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

【问题讨论】:

  • 我认为解决这个问题的唯一方法是从当前的实现中退后一步,重新考虑这个闭包,这样它就不会返回多种类型(毕竟这看起来也不是很快!)
  • 编辑后,请考虑查看此 SO 帖子:stackoverflow.com/questions/29796654/…

标签: swift generics swift2


【解决方案1】:

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

let evaluation: Foo&lt;Double&gt; = { ... }

它会起作用的。

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

您希望在同一个评估闭包中返回 2 个完全不同的类型(不能像上面的简单情况 IntDouble 那样隐式转换)的确切情况是什么?

【讨论】:

  • 我会为 Int 和 Double 工作,但在我的完整示例中,我也有 CGPoint、CGSize、CGRect、CATransform3D 等案例。
  • 我很害怕。不过,我不清楚为什么要让一个闭包返回多种不同的类型。无论如何,您要将这个 Capture 值存储在哪里?
  • 如果我猜对了,您确实希望捕获是同质的。所以在任何时候你都不会有一个 Capture 与 Capture 混合在一起的列表,对吧?这就是仍然有解决问题的方法的原因。我只是想看看你实际使用这些捕获的一些 sn-ps
  • 啊。我不能混合 Capture 和 Capture ...不敢相信我从来没有检查过 :(。然后看来泛型不是我的问题的解决方案。
  • 但是让我们假设您可以,并且您可以拥有Capture&lt;Whatever&gt; 的列表。您将如何处理该列表?您不能将每个值与其他值混合,因为无论如何Capture 具有Self 要求。我仍然缺少一些东西:-/
【解决方案2】:

在我的完整示例中,我还有 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))
    }
}

【讨论】:

  • 但是 NSObject 不能再符合 Mixable
  • @Vik 我完全支持你。
猜你喜欢
  • 1970-01-01
  • 2019-08-26
  • 2019-11-04
  • 1970-01-01
  • 2013-10-18
  • 2017-04-11
  • 2011-12-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多