全局函数的Swift协议和返回类型

6
这是对问题Protocol func returning Self的跟进。该协议如下:
protocol Copyable {
    init(copy: Self)
    func copy() -> Self
}

以下内容可以正常工作,但每个实现中的copy()函数都完全相同。
func copy() -> Self {
   return self.dynamicType(copy: self)
}

根据http://nshipster.com/swift-default-protocol-implementations/的内容,我尝试了一个全局函数。
func copy<T : Copyable>(makeCopy: T) -> T {
    return makeCopy.dynamicType(copy: makeCopy)
}

然而,当它在实现下面协议的类中被调用时
protocol Mutatable : Copyable {
    func mutated() -> Self
}

class C : Mutatable {

    var a = 0

    required init(_ a: Int) {
        self.a = a
    }

    required init(copy: C) {
        a = copy.a
    }

    func mutated() -> Self {
        let mutated = copy(self)

        mutated.a++

        return mutated // error: 'C' is not convertible to 'Self'
    }

}

我遇到了如下错误。当我输入mutated时,自动完成会将mutated显示为(C),我不知道这是什么意思。我还尝试在func mutated()中添加required,但显然required只允许在inits中使用。有没有办法让它工作?
1个回答

1
这个问题与复制的问题形式相同,解决方案也相同。将变异(mutations)作为初始化器而不是方法(method)。
protocol Copyable {
  init(copy: Self)
}

protocol Mutatable : Copyable {
  init(byMutating: Self)
}

class C : Mutatable {
  var a = 0

  required init(_ a: Int) {
    self.a = a
  }

  required init(copy: C) {
    a = copy.a
  }

  required convenience init(byMutating: C) {
    self.init(copy: byMutating)
    self.a++
  }
}

// These are purely for convenience
func copy<T : Copyable>(x: T) -> T {
  return x.dynamicType(copy: x)
}

func mutated<T: Mutatable>(x: T) -> T {
  return x.dynamicType(byMutating: x)
}

但是需要重申Mattt在链接文章中的观点,你可以相当方便地拥有一个C(copy: x)语法,也可以相当方便地拥有一个copy(x)语法,而且总有x.dynamicType(copy: x)。但是如果没有一些烦人的工作,你就无法拥有x.copy()语法。你要么必须在每个类中复制func copy() -> Self { return copy(self) },要么必须创建某个具体类来实现这个方法,并最终从C继承。这是Swift目前的基本限制。我同意Mattt提出的可能解决方案的诊断,并怀疑未来可能会添加一些类似于Scala的特征系统。

值得关注的是Mattt的评论:“所有这些都凸显了Swift中方法和函数之间的重要紧张关系。”这另一种说法是,在面向对象范式和函数范式之间存在紧张关系,而在它们之间移动可能会造成一些不连贯性。语言试图用各种功能掩盖这个问题,但是对象与消息和属性之间以及带有数据和组合器的函数之间存在重要差异,“兼顾两者之长”有时可能会产生一些粗糙的边缘。

当我们将Swift与其他编程语言进行比较时,很容易忽略v0.9和v2.11之间的巨大差异。在我们喜欢的其他语言中,许多我们认为理所当然的东西在它们的v1中也不存在。


针对你的评论,你可能认为mutatedSelf类型。但是根据自动完成,它是C类型。与之前一样,除非你可以保证没有子类(C要么是final,要么是一个结构体),否则C不同于Self。Swift类型在编译时解析,而不是运行时,除非你使用dynamicType
更具体地说,Swift查看这一行:
    let mutated = copy(self)

它指出copy是其参数类型的通用函数,必须在编译时构造一个版本的copy才能调用。没有类型Self,它只是一个占位符,必须在编译时解析。在此词法范围内,self的类型为C。因此,它构造了copy<C>。但如果你子类化了C,这可能是错误的函数(在这种情况下,确实如此)。这与https://dev59.com/eV8e5IYBdhLWcg3wwMlW#25549841密切相关。
类型自动补全中出现 (C) 而不是 C 的事实是 Swift 函数和元组的一个小副作用,并且经常出现,但我还没有遇到过它真正重要的情况。像 func f(x: Int, y:Int) 这样的 Swift 函数实际上并没有两个参数,它有一个类型为 (Int, Int) 的二元组参数。这个事实对于柯里化语法的工作方式很重要(有关 Swift 中柯里化的更多信息,请参阅《Swift 编程语言》)。因此,当你专门使用 copy 时,你将它与类型为 (C) 的1元组进行了具体化。(或者可能编译器只是试图尝试各种尝试之一,而这正是它报告的结果。)在 Swift 中,任何值都可以轻松地交换为相同类型的1元组。因此,copy 的返回值实际上是写成 (C) 的1元组,即 C 的1元组。我猜 Swift 编译器随着时间的推移会改进它的消息以消除多余的括号,但这就是为什么有时会出现它们的原因。

好的,所以一切都使用初始化器。明白了,再次感谢! - aeubanks
我猜我的问题是,如果copy(T) -> T返回调用它的类,为什么原始代码不起作用呢?在mutated()函数中,它应该返回Self,对吧?即使有更好的方法,我仍然很想知道Swift是如何工作的。 - aeubanks

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