有没有人知道为什么Swift编译器的错误信息是如此奇怪和违反直觉?
我正在修补带有闭包和$ 0,$ 1语法的集合函数。我试过这个:
let dict1 = ["a" : true, "b": false, "c": true]
dict1.forEach
{
print($1.1)
dict2[$0.0] = String($0.1)
}
似乎当您将字典中的条目传递给闭包时,您可以将每个键/值对称为$ 0和$ 1,或$ 0.0和$ 0.1。
如果使用$ 0和$ 1,$ 0参数是键,$ 1是每个键/值对的值。如果你使用$ 0.0和$ 1.1,那么$ 0.0是关键,$ 0.1是每个键/值对的值。
print语句无效。闭包只需要1个参数,即传递给它的字典中的键/值对。
编译器使用相当混乱的错误消息标记print语句“类型'Bool'的值没有成员'1'。但是,如果考虑它,它确实有意义。$ 1映射到值< / em>一个字典条目,因为我们传入[String:Bool]
字典条目,值是Bool
。尝试然后获取一个bool的$ 1成员是没有意义的。
没有意义的是,使用上面的代码, SECOND 行:
dict2[$0.0] = String($0.1)
也会抛出错误:
“String类型的值没有成员'0'”。咦?为什么上面的行导致下面的行失败?
更糟糕的是,如果你输入这两行作为闭包的主体:
print($0, $1)
dict2[$0.0] = String($0.1)
然后第二行抛出错误。注释掉print语句和赋值作品。注释掉赋值和打印作品,但尝试将两者都包含在闭包中,你会收到错误。
这似乎表明您可以将{/ 1}}或$0:$1
中传递给闭包的键/值对引用,但不能同时引用两者。一旦使用给定的符号,您必须在整个闭包期间遵循该符号。一个语句的内容导致下一个语句抛出错误,这似乎很奇怪和可怕。
答案 0 :(得分:2)
如果没有明确的输入,那么归结为类型推断。
要么推断闭包采用类型为$0
的单个元组参数(String, Bool)
,在这种情况下,您需要将事物称为$0.0
和$0.1
< / p>
或者推断出采用两个参数$0: String
和$1: Bool
。
你无法双管齐下,$1.1
没有任何意义。
答案 1 :(得分:1)
$0
是闭包中第一个参数的简写。在介绍$1
时,编译器推断您将键和值都视为闭包中的单独参数($0
是键,$1
是值),而是比$0
作为单参数元组(key, value)
。 $1.1
试图说&#34;嘿,$1
是一些具有附加值的集合类型,访问索引1
&#34;的值。但在你的情况下,它只是一个无法下标的值。
我认为这是有道理的。您不应该在闭包中使用这两种类型的语法,因为$0
和$0.1
可以表示不同的值(这会更改$0
的值)。
至于为什么你会得到奇怪的错误信息,只有SourceKit和llvm的作者可以回答这个问题,这使得这个问题基于意见为基础。
答案 2 :(得分:1)
$0
/ $1
)与元组($0.0
/ $0.1
)之间的非模糊性需求要求在元组($0.0
/ $0.1
)或两个key
- value
参数($0
/ $1
之间进行明确选择})是预期的行为;否则,Swift编译器无法推断匿名闭包的参数类型。
许多功能性(.forEach
,.map
,.flatMap
)工具涵盖在序列之上,使用next() -> Element?
方法在{Element
方法进行迭代3}}。对于词典,(Key, Value)
的关联类型是next() -> (Key, Value)?
类型,GeneratorType
protocol按预期实现了带有签名(Key, Value)
的生成方法。
.next()
// some tuple
let tuple : (String, Int) = ("One", 1)
// #1 assign tuple simply as a tuple
let tup : (String, Int)
tup = tuple
// #2 assign tuple to two seperate mutables: key & val
let key : String
let val : Int
(key, val) = tuple
print(key) // One
print(val) // 1
.forEach
回归的选择模糊选择的可能性是元组的特殊性,而不是闭包或词典本身。
$0.1
匿名闭包,在类型推断的核心,自然不允许在这个选择中有任何歧义,正如我们在下面看到的,编译器做选择推理,甚至如果看似毫无疑问。关闭(比如$1
)需要知道上面的哪个分配要选择。一旦做出这个选择,我们就无法将其从赋值转换为 in 相同的闭包,例如尝试使用Element
以及for ... in dict1
(因为上面只有一个&#34;赋值&#34;在封闭范围内有效。)
与字典for
:s的显式迭代比较
与显式迭代字典(使用next()
循环)进行比较是一个很好的类比;当然,我们需要在/* #1: Iterating over dictionary elements as tuples */
for tup in dict1 {
print(tup.0, tup.1)
}
dict1.forEach { print($0.0, $0.1) }
/* #2: Iterating over dictionary elements as key-value
(separate) entities pairs */
for (key, val) in dict1 {
print(key, val)
}
dict1.forEach { print($0, $1) }
循环的签名中选择是否将不可变的tup.0
分配给元组或显式的双参数键值对。
tup.1
如果是#1,我们会将字典中的条目作为元组访问,我们可以key
和value
进行$0.0
子访问和$0.1
访问权限(分别与key
和val
进行比较)。对于案例#2,我们将&#34; vars分开...&#34;分为两个单独的实体,$0
和$1
,与单独的匿名闭包参数.next()
和key
相当。在闭包的情况下 - 就像上面的循环一样 - 编译器需要决定上面哪个value
&#34;赋值情况&#34;使用:通过元组或键值对。
从上面的讨论和您发布的示例+错误消息,我们可以推断(没有双关语意)编译器在以下情况下将匿名参数推断为双参数dict1.forEach {
print($1.1) // (E1)
dict2[$0.0] = String($0.1) // (E2)
}
- {{1}赋值(而不是元组)
$1
为什么呢?我们可以做出有根据的猜测,这是因为$1
是第一个出现在闭包中的匿名参数,并且使用.next()
指向value
的键值赋值,而不是一个元组(我们很快就会看到这个猜测是错误的,它的优先级而不是那个控制它的出现时间)。在这种情况下,你发布的两个错误毕竟是有意义的
(E1)&#34;类型&#39; Bool&#39;没有会员&#39; 1&#39;&#34;
(E2)&#34; String类型的值没有成员&#39; 0&#39;&#34;
(E1)有意义,因为$ 1被推断为let foo : Bool = false
foo.1 // same error (E1)
,这是一个布尔值。
key
(E1)有意义,因为$ 0被推断为let bar : String = "bar"
foo.0 // same error (E2)
,这是一个字符串。
dict1.forEach {
print($0, $1) // OK, anon. arguments inferred as key-value par (not tuple)
dict2[$0.0] = String($0.1) // (E2); none of the arguments is a tuple.
}
通过这个解释,显而易见的是,下一部分中的单个(E2)错误也是可以预期的(并且,imho,spot on)。
$0.0
这导致我们测试一个非常有趣的案例:如果我们对匿名参数的第一次引用不足以明确地推断出闭包,那该怎么办呢?参数类型?例如,如果我们的第一个参考是dict1.forEach {
print($0.0) // error (E2)
dict2[$0.0] = String($1) // error (E2)
}
// ... or
dict1.forEach {
print($0.0) // error (E2)
dict2[$0.0] = String($0.1) // error (E2)
print($1)
}
.next()
所以它似乎推断了这个。作为单独的两个参数对的参数具有比推理更高的推理优先级作为元组:编译器并不关心哪个是否为anon。首先出现的参数,但我们是否将key
视为
value
- {{1}}对,关于奇怪的定点错误消息,一般来说:我同意,即使这里(imho)不是这种情况,我们做看了很多。我主要在闭包的上下文中遇到这些混淆的错误消息,我认为它们中的很多都与类型推断有关,而且闭包中成员使用的底层标准函数是抛出函数这一事实被懒惰地评估。这些功能倾向于引发他们自己的错误&#34;由于呼叫者的一些无效(例如关闭),甚至没有达到他们自己的实际错误的点。参见例如the generator DictionaryGenerator
这样的例子。