Необязательная привязка нулевого литерала к переменной, которая равна нулю в Swift

Почему в Swift

var x: Int? = nil
if let y: Int? = x { ... }

вести себя иначе, чем

if let y: Int? = nil { ... }

Мое понимание того, почему первый случай успешен, предполагает, что и второй должен быть таким же, поэтому я не должен понимать.

Последний не работает из-за недопустимого присваивания или из-за необязательной цепочки; а в остальном он кажется таким же, как прежний. Почему последний терпит неудачу и чем он отличается от первого. В какой именно момент и по какой причине отказались от второй необязательной привязки?


person orome    schedule 28.10.2014    source источник


Ответы (3)


if let y: Int? = nil { ... }

эквивалентно

if let y: Int? = nil as Int?? { ... }

эквивалентно

if let y: Optional<Int> = Optional<Optional<Int>>() { ... }

Это пытается преобразовать Optional<Optional<Int>> в Optional<Int>, и это не удается, потому что Optional<Optional<Int>>() не содержит значения Optional<Int>.


Oneliner эквивалентен

var x: Int? = nil
if let y: Int? = x { ... } 

is

if let y: Int? = nil as Int? { ... } 

ДОБАВЛЕН:

Как я ответил на Что делает необязательная привязка Swift? к типу его аргументов?.

if let y: Int? = nil as Int? { ... }

компилируется как:

if let y:Int? = nil as Int? as Int?? { ... }

Здесь следует помнить, что nil as Int? as Int?? не эквивалентно nil as Int??. Первому Optional(Optional<Int>()), а второму Optional<Optional<Int>>().

Имея это в виду, я думаю,

Таким образом, необязательная привязка в моем первом примере (действительно) «проверяет внутри» и находит nil, что вполне допустимо для присваивания Int?; но мой второй проверяет и находит необязательный (ноль), который не может быть назначен Int?

Первый пример "проверить внутри" Int?? и просто находит значение Optional<Int>() типа Int?, которое можно присвоить Int?; Но второй не находит ничего для назначения и терпит неудачу.

person rintaro    schedule 28.10.2014
comment
Это очень познавательно — в вашем последнем примере устанавливается связь между разворачиванием опционов с помощью необязательной привязки и необязательно связанными приведениями, такими как if cell = tableViewCell as? MyTableViewCell. Спасибо! - person Nate Cook; 28.10.2014
comment
Да, очень интересно! Таким образом, необязательная привязка в моем первом примере (действительно) проверить внутри и находит nil, что вполне допустимо для назначения Int? ; но мой второй проверяет и находит Optional(nil), который не может быть назначен Int?. Это правильно? - person orome; 28.10.2014
comment
И если это правильно: Почему if let y: Int? = nil { ... } совпадает с if let y: Int? = nil as Int?? { ... }, особенно когда (вне if) let y: Int? = nil является допустимым назначением ? - person orome; 28.10.2014

let объявляет константу, которая устанавливается во время инициализации. Используя let в операторе if с Optional<T>, он связывает инициализированную константу с результатом инициализатора failable, а не буквально с nil или другим литералом.

Компилятор позволил вам использовать nil напрямую вместо '4', например, поскольку nil имеет использование/контекст за пределами Необязательно; однако это буквальное использование nil обходит инициализатор, поэтому нет результата для привязки. В случаях 4 или Int(4) компилятор знает, что что-то не так, и не будет компилировать.

Просмотрите следующий пример:

var xnil: Int? = nil
var x4: Int? = Int?(4)
var xnone: Int? = Int?()
var xdefault: Int? = Int()

if let znil: Int? = nil {
    println("znil:\(znil)")
} else {
    println("znil: did not bind")
}

if let zn0: Int? = Int?(nilLiteral: ()) {
    println("zn0:\(zn0)")
}

if let zn1: Int? = Int?(()) {
    println("zn1:\(zn1)")
}

if let zn2: Int? = Int?() {
    println("zn1:\(zn2)")
}

if let zn3: Int? = Int?.None {
    println("zn3:\(zn3)")
}

if Int?.None == nil {
    println(".None == nil")
}

if let zo0: Int? = Int?(4) {
    println("zo0:\(zo0)")
}

//nil-test.swift:36:20: error: bound value in a conditional binding must be of Optional type
//if let zo1: Int? = 4 {
//                   ^
/*
if let zo1: Int? = 4 {
    println("zo1:\(zo1)")
}
*/

//nil-test.swift:51:20: error: bound value in a conditional binding must be of Optional type
//if let zo2: Int? = Int(4) {
//                   ^
/*
if let zo2: Int? = Int(4) {
    println("zo2:\(zo2)")
}
*/


if let zxn0: Int? = xnil {
    println("zxn0:\(zxn0)")
}

if let zxn1: Int? = x4 {
    println("zxn1:\(zxn1)")
}

if let zxn2: Int? = xnone {
    println("zxn2:\(zxn2)")
}

if let zxn3: Int? = xdefault {
    println("zxn3:\(zxn3)")
}

... выводит:

znil: did not bind
zn0:nil
zn1:nil
zn1:nil
zn3:nil
.None == nil
zo0:Optional(4)
zxn0:nil
zxn1:Optional(4)
zxn2:nil
zxn3:Optional(0)

ОБНОВЛЕНИЕ: объяснение разницы между Type и Type?.

См. этот пример:

if let a: Int? = nil {
    println("a: \(a)")
} else { 
    println("a: let fail")
}

if let b1: Int? = Int?(nilLiteral: ()) { // same as Int?() -- added nilLiteral to be explicit here
    println("b1: \(b1)")
}

if let b2: Int? = Int?(44) {
    println("b2: \(b2); b2!: \(b2!)")
}

if let c1: Int = Int?(44) {
    println("c1: \(c1)")
}

if let c2: Int = Int?(nilLiteral: ()) { // Again, Int?() would suffice
    println("c2: \(c2)")
} else {
    println("c2: let fail")
}

/// NOTE: these 'd' examples represents a more idiomatic form
var m: Int? = Int?()
if let d1 = m {
    println("d1: \(d1)")
} else {
    println("d1: let fail")
}

m = Int?(444)
if let d2 = m {
    println("d2: \(d2)")
} else {
    println("d2: let fail")
}

m = Int?()
println("m?: \(m?)")
if let whyDoThis: Int? = m {
    println("whyDoThis?: \(whyDoThis?) -- the `let` is telling you nothing about 'm!'")
}

... выводит:

a: let fail
b1: nil
b2: Optional(44); b2!: 44
c1: 44
c2: let fail
d1: let fail
d2: 444
m?: nil
whyDoThis?: nil -- the `let` is telling you nothing about 'm!'!

... Итак, спросите себя:

  • Почему a потерпел неудачу, а b1 успешно привязался, даже если он содержит нулевое значение?
  • Почему if let whyDoThis ... завершается успешно, если m в этот момент явно содержит нулевое значение?
  • И что значение whyDoThis? говорит вам о m!?

В итоге идиоматический псевдокод Swift для этого случая должен выглядеть следующим образом:

var maybeT: MyType?
// ... maybe set maybeT to a MyType, maybe not
if let reallyHaveT = maybeT {
  // reallyHaveT has a bound value of type MyType
}

ОБНОВЛЕНИЕ 2: Хорошо, давайте посмотрим на типы...

См. следующий пример:

var none: Int? = Int?()
var one: Int? = Int?(1)

println("Int()   type: \(_stdlib_getTypeName(Int())), val: \(Int())")
println("Int(1)  type: \(_stdlib_getTypeName(Int(1))), val: \(Int(1))")
println("Int?()  type: \(_stdlib_getTypeName(Int?())), val: \(Int?())")
println("Int?(1) type: \(_stdlib_getTypeName(Int?(1))), val: \(Int?(1))")
println("none    type: \(_stdlib_getTypeName(none)), val: \(none)")
println("one     type: \(_stdlib_getTypeName(one)), val: \(one)")

if let x = none {
    println("`let x = none`       x type: \(_stdlib_getTypeName(x))")
} else {
    println("`let x = none`       FAIL")
}
if let x: Int = none {
    println("`let x: Int = none`  x type: \(_stdlib_getTypeName(x))")
} else {
    println("`let x: Int = none`  FAIL")
}
if let x: Int? = none {
    println("`let x: Int? = none` x type: \(_stdlib_getTypeName(x))")
}

if let y = one {
    println("`let y = one`        y type: \(_stdlib_getTypeName(y))")
}
if let y: Int = one {
    println("`let y: Int = one`   y type: \(_stdlib_getTypeName(y))")
}
if let y: Int? = one {
    println("`let y: Int? = one`  y type: \(_stdlib_getTypeName(y))")
}

if let z: Int? = nil {
    println("`let z: Int? = nil`  z type: \(_stdlib_getTypeName(z))")
} else {
    println("`let z: Int? = nil`  FAIL")
}
if let z = Int?() {
    println("`let z = Int?()`     z type: \(_stdlib_getTypeName(z))")
} else {
    println("`let z = Int?()`     FAIL")
}

... выводит:

Int()   type: _TtSi, val: 0
Int(1)  type: _TtSi, val: 1
Int?()  type: _TtSq, val: nil
Int?(1) type: _TtSq, val: Optional(1)
none    type: _TtSq, val: nil
one     type: _TtSq, val: Optional(1)
`let x = none`       FAIL
`let x: Int = none`  FAIL
`let x: Int? = none` x type: _TtSq
`let y = one`        y type: _TtSi
`let y: Int = one`   y type: _TtSi
`let y: Int? = one`  y type: _TtSq
`let z: Int? = nil`  FAIL
`let z = Int?()`     FAIL

Я хочу подчеркнуть, что вы фактически вызываете let z = Int?(), когда пишете let z: Int? = nil. Это не свяжется при использовании в операторе if. Всегда.

person greymouser    schedule 28.10.2014
comment
Но почему if let y: Int? = nil { ... } то же самое, что и if let y: Int? = nil as Int?? { ... }, особенно когда (за пределами if) let y: Int? = nil является допустимым назначением? - person orome; 28.10.2014
comment
@raxacoricofallapatorius Они одинаковы друг с другом, поскольку присваивание nil приводит к тому, что данные в контейнере переменных устанавливаются на nil и, следовательно, делают let отказоустойчивым? Неважно, как вы приведете тип nil — это просто nil. Optional<T> хранит None или Some(T) -- если вы присваиваете nil Optional<T>, вы в основном получаете Optional<T>.None. - person greymouser; 28.10.2014
comment
Так они одинаковые или нет? Это утверждение является основой ответа @rintaro. - person orome; 28.10.2014
comment
@raxacoricofallapatorius добавил дополнительную информацию, помогающую объяснить различия в связывании во время if let .... - person greymouser; 29.10.2014
comment
Да: именно на эти три вопроса я пытаюсь ответить. Особенно первые 2. Но какие ответы?! - person orome; 29.10.2014
comment
@raxacoricofallapatorius Я пытался привести вас к ответу/ам. ;-) Когда вы объявляете связываемый тип вручную, чтобы имитировать идиоматический стиль if let x = maybeX { ..., вы должны написать if let x: Type = maybeX { .... Если вы пишете if let x: Type? = maybeX { ..., то вы объявляете, что хотите связать необязательное значение — это отличается от короткой идиоматической формы, которая извлекает значение, если оно установлено, и не будет, если оно равно нулю. конвертируемый тип. - person greymouser; 29.10.2014
comment
@raxacoricofallapatorius if let x: Type? = maybeX { ... имеет очень мало практического применения и сильно отличается от идиоматической формы. На самом деле, это всегда будет успешно, в то время как идиоматическая форма предназначена для обеспечения некоторой безопасности типов и отказа на месте, поэтому мы реже пишем конструкции типа if (x != nil) { NSObject *y = x; .... - person greymouser; 29.10.2014
comment
@raxacoricofallapatorius добавил еще один пример, подчеркивая типы - person greymouser; 29.10.2014

if let используется, чтобы избавиться от необязательного. Если вы хотите увидеть, когда переменная равна нулю, используйте

if let y: Int = x {
// This occurs if you are able to give a non-nil value to the variable
} else {
// This occurs if the optional x is nil
}

насколько я знаю, вы не должны пытаться объявить тип константы в операторе if let необязательным, потому что это противоречит цели оператора if let.

person Ian    schedule 28.10.2014