练习是在map()
上编写我自己的Collection
函数(不使用任何功能原语,例如reduce()
)。它应该处理如下情况:
func square(_ input: Int) -> Int {
return input * input
}
let result = input.accumulate(square) // [1,2,3] => [1,4,9]
我的第一次尝试是:
extension Collection {
func accumulate(_ transform: (Element) -> Element) -> [Element] {
var array: [Element] = []
for element in self {
array.append(transform(element))
}
return array
}
}
这在游乐场中运行良好,但无法针对测试进行构建,发出错误:
Value of type '[Int]' has no member 'accumulate'
解决方案是对accumulate
方法进行泛化:
extension Collection {
func accumulate<T>(_ transform: (Element) -> T) -> [T] {
var array: [T] = []
for element in self {
array.append(transform(element))
}
return array
}
}
我认识到通用版本的限制性较低(不要求转换返回相同类型),但鉴于测试不需要这种通用性,编译器为什么会这样做?
出于好奇,我试过了:
extension Collection {
func accumulate<Element>(_ transform: (Element) -> Element) -> [Element] {
var array: [Element] = []
for element in self {
array.append(transform(element))
}
return array
}
}
会在'(Self.Element) -> Element' is not convertible to '(Element) -> Element'
语句中抛出引人入胜的构建错误:append()
。
所以编译器(当然)知道第一个Element是Self.Element,但不会将其他Element类型视为相同。为什么呢?
更新:
基于答案,似乎拒绝第一个版本是一个编译器错误,在XCode 9.2中固定(我在9.1上)。
但我还是想知道是否在
func accumulate(_ transform: (Element) -> Element) -> [Element]
它会看到两种类型(Self.Element
和Element
),或者认识到它们是相同的。
所以我做了这个测试:
let arr = [1,2,3]
arr.accumulate {
return String(describing: $0)
}
果然,得到了预期的错误:error: cannot convert value of type 'String' to closure result type 'Int'
所以正确答案是:编译器会将对Element的引用视为相同,只要不存在重载名称的泛型类型。
奇怪的是,这成功了:
[1,2,3].accumulate {
return String(describing: $0)
}
PS。感谢大家的投入!赏金是自动授予的。
答案 0 :(得分:8)
原始构建错误是编译器错误。事实上,编译器将认识到Element
的所有实例都是相同的,因为长Element
未作为函数上的泛型类型重载。
答案 1 :(得分:4)
关于第一个问题,使用Xcode 9.2和Swift 4我没有收到任何构建错误,例如:
类型'[Int]'的值没有成员'accumulate'
这样做:
var mystuff:[Int] = [1,2,3]
let result = mystuff.accumulate(square)
它只是给了我正确的结果[1,4,9]
对于第二个问题,函数原型是错误的,你应该尝试Self.Element
:
extension Collection {
func accumulate<Element>(_ transform: (Self.Element) -> Element) -> [Element] {
var array: [Element] = []
for element in self {
array.append(transform(element))
}
return array
}
}
答案 2 :(得分:1)
我会专注于你的第二个问题。
func accumulate<Element>(_ transform: (Element) -> Element) -> [Element]
编写此签名的麻烦在于您有两种不同类型的名称。
Element
,它是您的通用类型(尖括号之间的位)。 Self.Element
,即协议本身声明的集合中元素的类型。通常你不必显式地写Self.
部分,但因为你的泛型类型与这个类型相同,所以Swift不能区分它们。如果更改泛型类型的名称,差异会更明显:
func accumulate<E>(_ transform: (E) -> E) -> [E]
这相当于accumulate<Element>
版本 - 更改名称只会突出显示实际发生的情况。
从更一般的意义上说,Swift会让你根据自己的意愿命名你的类型。但是如果类型的名称与来自不同范围的另一种类型冲突,那么您要么必须消除它的歧义。如果你不消除歧义,Swift会选择最本地的比赛。在您的函数中,泛型类型是“最本地的”。
想象一下,您要定义自己的String类型:
struct String {
// ...
}
这是完全有效的,但是如果你想使用Swift标准库提供的String类型,你必须这样消除歧义:
let my_string: String = String()
let swift_string: Swift.String = ""
这就是Andrea改变功能签名的原因。您需要告诉编译器您指的是哪种“元素”类型。
func accumulate<Element>(_ transform: (Self.Element) -> Element) -> [Element]
一般情况下,我建议不要让泛型类型匹配您正在使用的其他类型的名称。这让每个人都感到困惑。
答案 3 :(得分:1)
我对你的第一个问题不太确定。如果它在游乐场工作,但不在你的测试中,我的第一个猜测是将该功能公之于众。测试通常在单独的模块中定义,并且为了在另一个模块中显示某些内容,应将其声明为public。
extension Collection {
public func accumulate //...
}
答案 4 :(得分:1)
在实施Collection
的扩展时,默认情况下,该集合的关联类型为Element
;将通用名称命名为“元素”(func accumulate<Element>
)会是令人困惑的,但是,对于您的情况,甚至无需按如下方式声明方法签名:
func accumulate<Element>(_ transform: (Self.Element) -> Element) -> [Element]
相反,它应该是:
func accumulate(_ transform: (Element) -> Element) -> [Element]
,如果您的目标是让您的方法仅对整数起作用,则约束您的扩展名应仅适用于 {的集合{3}} ,如下所示:
extension Collection where Element: BinaryInteger {
func accumulate(_ transform: (Element) -> Element) -> [Element] {
var array: [Element] = []
for element in self {
array.append(transform(element))
}
return array
}
}
或者,为了扩大范围,可以改为 BinaryInteger 。