Generic Swift 4 enum с типом, связанным с Void

tl; dr

Можно ли создать экземпляр универсального члена перечисления Swift 4 со связанным значением типа Void?

Фон

Я использую простое перечисление Result (похожее на antitypical Result):

enum Result<T> {
  case success(T)
  case error(Error?)
}

Теперь я хотел бы использовать это перечисление для представления результата операции, которая не дает фактического значения результата; операция либо выполнена, либо не удалась. Для этого я бы определил тип как Result<Void>, но я борюсь с тем, как создать экземпляр Result, ни let res: Result<Void> = .success, ни let res: Result<Void> = .success() не работают.


person dr_barto    schedule 23.08.2017    source источник
comment
ты уверен во втором способе? похоже, что это работает swift.sandbox.bluemix.net/#/repl/599d61b1b459cc41aee76d >   -  person pacification    schedule 23.08.2017
comment
Интересно, спасибо за пример. Я перепроверил, но внутри Xcode 9 Beta 5 получаю Missing argument for parameter #1 in call.   -  person dr_barto    schedule 23.08.2017
comment
@Hamish: Вы, наверное, правы (я все еще был в SE-0110 и SE-0029 ... :)   -  person Martin R    schedule 23.08.2017
comment
@Hamish: Это также могло быть следствием SE-0029: конструктор let f = Result<Void>.success имеет тип (Void) -> Result<Void> как в Swift 3, так и в 4b5. В Swift 3 вы можете вызывать let r = f(), в 4b5 вам нужно добавить аргумент: let r = f(())   -  person Martin R    schedule 23.08.2017


Ответы (5)


В Swift 3 вы можете опустить связанное значение типа Void:

let res: Result<Void> = .success()

В Swift 4 вам нужно передать связанное значение типа Void:

let res: Result<Void> = .success(())
// Or just:
let res = Result.success(())
person Martin R    schedule 23.08.2017
comment
Есть ли менее уродливое решение? - person Rodrigo Ruiz; 20.09.2017
comment
@RodrigoRuiz Вы можете определить расширение, чтобы избавиться от (()), см. Мой ответ. - person Hamish; 21.10.2017

В Swift 4 случай перечисления со связанным значением Void больше не эквивалентен случаю перечисления с пустым списком связанных значений.

Я считаю, что как говорит Мартин , результат SE-0029, где вы больше не можете передавать кортеж аргументов в функцию и заставлять их «распределять» по параметрам (хотя предложение было отмечено как реализованное в Swift 3, я считаю, что этот конкретный случай был рассмотрен позже при реализации SE-0110 для Swift 4).

В результате это означает, что вы больше не можете вызывать (Void) -> T как () -> T в Swift 4. Теперь вам нужно явно передать Void:

let result = Result.success(())

Однако я считаю это довольно уродливым, поэтому обычно использую такое расширение:

extension Result where Success == Void {
    static var success: Result {
        return .success(())
    }
}

Что позволяет говорить такие вещи:

var result = Result.success
result = .success

Стоит отметить, что этот обходной путь не ограничивается только случаями перечисления, его также можно использовать с методами в целом. Например:

struct Foo<T> {
  func bar(_ a: T) {}
}

extension Foo where T == Void {
  func bar() { bar(()) }
}

let f = Foo<Void>()

// without extension:
f.bar(())

// with extension:
f.bar()
person Hamish    schedule 21.10.2017
comment
((Хороший)). Не осознавал, что вы можете определять вары на основе типа дженериков. - person GoldenJoe; 30.01.2018
comment
Замечательное решение! Я перешел на это из .success (Void ()) - person Lensflare; 01.03.2019
comment
Версия Swift 5 будет extension Result where Success == Void { ... }, но по какой-то причине let r1 = Result<Void, Error>.success не может быть скомпилирована с «неоднозначным использованием слова« success »», но let r2: Result<Void, Error> = .success компилируется без проблем. - person Martin R; 20.05.2019
comment
@MartinR Это отстой - проблема в том, что Result<Void, Error>.success может относиться как к конструктору enum case для .success, так и к статической переменной var. Уже существует правило ранжирования, которое предпочитает переменные функции функциям, но в настоящее время не учитывает конструкторы случая перечисления :(. let r2: Result<Void, Error> = .success работает, потому что только статическая переменная имеет тип Result<Void, Error>. заполнитель не указан, поэтому может разрешаться только в статическую переменную, поскольку это удовлетворяет заполнитель с Void. - person Hamish; 22.05.2019
comment
К сожалению, я не верю, что в настоящее время есть хорошее решение для этого, кроме let r2: Result<Void, Error> = .success. В идеале правила ранжирования перегрузки должны измениться, чтобы рассматривать конструкторы case enum больше как статические функции. Еще лучше, было бы круто, если бы конструкторы enum case были представлены как неявно сгенерированные статические функции в AST, что помогло бы устранить другие несоответствия с ними (хотя это может быть невозможно после стабильности после ABI, поскольку я не уверен, что искажение будет то же самое). - person Hamish; 22.05.2019

Void - это простой typealias для пустого кортежа: (), поэтому вы можете использовать его как любое из следующих:

let res1: Result<Void> = .success(())
let res2 = Result<Void>.success(())
let res3 = Result.success(() as Void)
let res4 = Result.success(())
person KaQu    schedule 23.08.2017

Swift 5 обновил Result, чтобы требовать параметр Failure, но по-прежнему требует связанного значения:

let res: Result<Void, Error> = .success(())
person WongWray    schedule 21.02.2020

Я считаю, что .success(Void()) более наглядный и простой.

person Saqib Saud    schedule 03.04.2020