Почему оператор nil-coalescing возвращает необязательный параметр? - PullRequest
0 голосов
/ 11 декабря 2019

Я использую оператор Swift в цепочке, чтобы вывести одно из трех значений. Первые два являются необязательными, последний не является обязательным:

let optionalString: String? = nil
let optionalError: Error? = MyError.anError
let defaultString: String = "default"
print(optionalString ?? (optionalError ?? defaultString))

Я был удивлен, увидев, что этот код печатает

Optional(MyError.anError)

к консоли. Я ожидал, что не обязательно:

MyError.anError

Почему это необязательно?

Ошибка определяется следующим образом:

enum MyError: Error {
    case anError
}

Я знаю, что существует две реализации оператора nil-coalescing:

  1. func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T

и

func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?

В соответствии с этим, ?? должен возвращать необязательный (T?) , только если defaultValue также является необязательным (T?). Но это не так:

Значением по умолчанию для первого оператора ?? является (optionalError ?? defaultString). Чтобы оценить тип этого значения, нам нужно оценить результат второго оператора ??. Поскольку defaultString имеет тип String и, следовательно, не является обязательным, функция (1) используется для оператора слияния ноль. Следовательно, результат (optionalError ?? defaultString) также должен быть необязательным, что является значением по умолчанию для первого оператора ??. Итак, еще раз, функция (1) используется для оператора слияния ноль и, таким образом, optionalString ?? (optionalError ?? defaultString) должен возвращать * non- * необязательный.

Видимо, в моем недостаткелогика, но я этого не вижу. Любое объяснение этому?

1 Ответ

1 голос
/ 12 декабря 2019

Отказ от ответственности: Я не был уверен, должен ли я добавить это в качестве дополнения к своему вопросу или я должен опубликовать его в качестве ответа. Я решил для последнего. Это не пуленепробиваемое объяснение, но я думаю, что оно «достаточно близко» для многих, кто может столкнуться с той же проблемой.


Что не так: Общая картина

Двакомментарии о том, что предоставленный код не компилируется для комментаторов, подтолкнули меня в правильном направлении:

Я скопировал код на пустой площадке и запустил его. Для меня он сделал компиляцию, но он также показал предупреждение, которое не отображалось до (потому что по какой-то причине предупреждения об автозаполнении и компиляторе были нарушены в исходном проекте) .

Playground screenshot

Предупреждение

Выражение, неявно приведенное от «Any?»в 'Any'

говорит мне, что выражение (optionalError ?? defaultString), по-видимому, оценивается как тип Any?, что является необязательным. Затем компилятор выбирает операторную функцию ?? (1) для оценки всего выражения, которое требует, чтобы его значение по умолчанию (optionalError ?? defaultString) было необязательным. Таким образом, это значение неявно приводится от Any? до Any. И именно поэтому печатается Optional(MyError.anError).


Почему это идет не так?

Осталось два вопроса:

  1. Почему компилятор выбираетоператорная функция (1)?
  2. Почему (optionalError ?? defaultString) оценивается как Any??

У меня нет ответа на вопрос 1 ,

но я предполагаю, что компилятор просто назначает более высокий приоритет функции (1), когда типы параметров не совпадают, и неясно, какую функцию использовать. (Я буду рад, если меня поправят.)

Относительно вопрос 2 :

optionalError относится к типу MyError?, а defaultString относится к типу String. Подпись операторских функций ?? гласит:

  1. func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
  2. func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?

Итак, оба параметра, optional и defaultValue должен иметь «тот же» универсальный тип (только то, что в первом случае один параметр является необязательным для этого конкретного типа, а другой - нет). MyError и String, очевидно, не одного типа. Таким образом, чтобы это соответствовало критериям сигнатуры функции, компилятор должен привести оба типа к Any.

Так что MyError? приведен к Any? и, по-видимому, String также приведенAny? и, таким образом, используется функция (2), которая оценивает (optionError ?? defaultString) to Any? `.

Это снова поднимает вопрос, почему в этом случае используется функция (2), котораяВ некотором роде это противоречит моему предположению о вопросе (1), но Мой основной вывод здесь заключается в том, что использование двух разных типов с оператором nil-coalescing .


* - это просто плохая идея. 1094 * Справочная информация:

Я получил этот пример кода из учебника по RxSwift, который немного отличается, поскольку в нем используется универсальный тип, который требуется для соответствия CustomStringConvertible:

func print<T: CustomStringConvertible>(label: String, event: Event<T>) {
    print(label, event.element ?? event.error ?? event)
}

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

...