Swift 使用';懒惰';

Swift 使用';懒惰';,swift,lazy-evaluation,overloading,swift4,Swift,Lazy Evaluation,Overloading,Swift4,我不知道为什么会有歧义。(很抱歉没有在这里添加代码,因为代码太长了。) 我已经添加了前缀(u.maxLength)作为LazyDropWhileBidirectionalCollection的重载下标(位置)在LazyPrefixCollection上定义。然而,上面示例中的以下代码不应该是含糊不清的,但它是: print([0, 1, 2].lazy.drop(while: {_ in false}).prefix(2)[0]) // Ambiguous use of 'lazy' 我的理解

我不知道为什么会有歧义。(很抱歉没有在这里添加代码,因为代码太长了。)

我已经添加了
前缀(u.maxLength)
作为
LazyDropWhileBidirectionalCollection
的重载<代码>下标(位置)在
LazyPrefixCollection
上定义。然而,上面示例中的以下代码不应该是含糊不清的,但它是:

print([0, 1, 2].lazy.drop(while: {_ in false}).prefix(2)[0]) // Ambiguous use of 'lazy'
我的理解是,协议层次结构中较高的重载将被使用

根据编译器,它不能在两种类型中进行选择;即
LazyRandomAccessCollection
LazySequence
。(这没有意义,因为
下标(位置)
不是
LazySequence
的方法)
lazydrandomAccessCollection
将是这里的逻辑选择

如果我删除下标,它会工作:

print(Array([0, 1, 2].lazy.drop(while: {_ in false}).prefix(2))) // [0, 1]

可能是什么问题?

将最后一行更改为:

let x = [0, 1, 2]

let lazyX:  LazySequence                = x.lazy
let lazyX2: LazyRandomAccessCollection  = x.lazy
let lazyX3: LazyBidirectionalCollection = x.lazy
let lazyX4: LazyCollection              = x.lazy

print(lazyX.drop(while: {_ in false}).prefix(2)[0])

你可以注意到这个数组有4种不同的惰性构象——你必须是明确的。

这里的线索太复杂和模糊了。通过删除元素可以看到这一点。特别是,删除最后一个下标:

let z = [0, 1, 2].lazy.drop(while: {_ in false}).prefix(2)
在此配置中,编译器希望键入
z
作为
lazyprefexcollection
。但这不能用整数来表示。我知道感觉应该是这样,但当前的编译器无法证明它。(见下文)因此您的
[0]
失败。回溯还不足以从这个疯狂的迷宫中走出来。有太多具有不同返回类型的重载,编译器不知道您想要哪一种

但这一特殊情况得到了细微的修正:

print([0, 1, 2].lazy.drop(while: {_ in false}).prefix(2).first!)
也就是说,我绝对不会把编译器推得这么厉害。这对今天的斯威夫特来说太聪明了。特别是返回不同类型的重载在Swift中通常是个坏主意。当它们很简单的时候,是的,你可以不受惩罚。但是,当您开始将它们分层时,编译器没有足够强大的证明引擎来解决它。(也就是说,如果我们研究的时间足够长,我敢打赌它在某种程度上是模糊的,但诊断是误导性的。这是一种非常常见的情况,当你进入过度聪明的Swift。)


既然您(在评论中)描述了它,那么推理就简单明了了

LazyDropWhileCollection
不能有整数索引。索引订阅要求为O(1)。这就是
索引
下标相对于其他下标的含义。(
索引
下标还必须返回
元素
类型或崩溃;它不能返回
元素?
。这样就有了一个与
分开的
字典索引

由于集合是惰性的,并且缺少任意数量的元素,因此查找任何特定的整数“count”(第一个、第二个等)都是O(n)。如果不经过至少100个元素,就不可能知道第100个元素是什么。要成为一个集合,它的O(1)索引必须是一种只能通过先前遍历序列来创建的形式。它不能是
Int

这一点很重要,因为当您编写以下代码时:

for i in 1...1000 { print(xs[i]) }
您希望它大约是1000个“步骤”,但是如果这个集合有一个整数索引,它将大约是100万个步骤。通过包装索引,它们首先会阻止您编写该代码


这在高度通用的语言(如Swift)中尤其重要,在Swift中,通用算法层可以轻松地将意外的O(n)操作级联到完全不可行的性能中(所谓“不可行”是指您希望花费毫秒或更多时间的事情).

编译器输出显示了两个可能的
lazy
候选对象。请发布相关的“问题代码”?@shallow尽管相关问题代码是第一句中链接的最后一行。我已经在上面的帖子中添加了它。据我所知,协议链中较高的重载将被使用。根据编译器,它不能在两种类型中进行选择;即
LazyRandomAccessCollection
Sequence
。(这是没有意义的,因为
下标(位置)
不是
顺序
的方法)
LazyRandomAccessCollection
在这种情况下是合乎逻辑的选择。谢谢您的回答。经过一些挖掘后,
[0]
失败的原因可能与
drop(while:)
(和
prefix(while:)
)将底层索引包装在
LazyDropWhileIndex
(和
lazyprefix whileindex
)中有关。这是一个
struct
,它有一个隐式
init
(因此不能在标准库之外进行初始化)。符合
LazyCollectionProtocol
的所有其他类型不包装其索引,而是“转发”基础基集合的索引。(我还没有完全理解包装的原因。)谢谢你的编辑。还有一些想法
LazyPrefixWhileIndex
做了一个很酷的技巧,
endIndex
忽略基本集合的索引,只返回一个
enum
值,表示O(1)中的结束索引。为任何涉及
LazyPrefixWhileIndex
的内容编写单元测试都是不可能的,因为它的
init
是隐式的,而保存索引表示的底层值是
内部的
。使用
LazyDropWhileIndex
我们至少可以访问
base
索引,这样我们就可以根据基本集合的
索引类型的实例对其进行测试。总之,我可以理解
LazyPrefixWhileIndex
背后的原因,但是对于
LazyDropWhileIndex
来说,他们可以只使用底层的
索引