Swift - возьмите ноль в качестве аргумента в общей функции с дополнительным аргументом

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

func somethingGeneric<T>(input: T?) {
    if (input != nil) {
        print(input!);
    }
}

somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric(nil) // Errors!

Работает с String как показано, но не с nil, Используя это с nil дает следующие две ошибки:

error: cannot invoke 'somethingGeneric' with an argument list of type '(_?)'
note: expected an argument list of type '(T?)'

Что я делаю не так и как мне правильно объявить / использовать эту функцию? Кроме того, я хочу максимально упростить использование функции (я не хочу делать что-то вроде nil as String?).

7 ответов

Решение

Я понял:

func somethingGeneric<T>(input: T?) {
    if (input != nil) {
        print(input!);
    }
}

func somethingGeneric(input: NilLiteralConvertible?) {}


somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric(nil) // *nothing printed*
somethingGeneric(nil as String?) // *nothing printed*

Я думаю, компилятор не может понять, что T только из nil,

Следующее работает просто отлично, хотя, например:

somethingGeneric(Optional<String>.None)

Я полагаю, что вы слишком усложнили проблему, потребовав способности передавать нетипизированный nil (который на самом деле не существует; даже nil имеет тип). Хотя подход в вашем ответе, кажется, работает, он позволяет создавать ?? типы из-за Факультативного продвижения. Вам часто везет, и это работает, но я видел, как это действительно расстраивает, и вызывается неправильная функция. Проблема в том, что String может быть косвенно повышен до String? а также String? может быть косвенно повышен до String??, когда ?? обнаруживается неявно, замешательство почти всегда следует.

Как указывает MartinR, ваш подход не очень интуитивен в отношении того, какая версия вызывается. UnsafePointer это также NilLiteralConvertible, Поэтому сложно рассуждать о том, какая функция будет вызываться. "Хитро рассуждать" делает его вероятным источником запутанных ошибок.

Единственный раз, когда ваша проблема существует, когда вы передаете буквальный nil, Как отмечает @Valentin, если вы передадите переменную, nil нет проблем; вам не нужен особый случай. Зачем заставлять звонящего проходить нетипизированный nil? Просто пусть звонящий ничего не пропустит.

Я предполагаю что somethingGeneric делает что-то действительно интересное в том случае, если это передается nil, Если это не так; если код, который вы показываете, указывает на реальную функцию (т. е. все if (input != nil) проверьте), то это не проблема. Просто не звони somethingGeneric(nil); это доказуемое бездействие. Просто удалите строку кода. Но я предполагаю, что есть какая-то "другая работа".

func somethingGeneric<T>(input: T?) {
    somethingGeneric() // Call the base form
    if (input != nil) {
        print(input!);
    }
}

func somethingGeneric() {
   // Things you do either way
}

somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric() // Nothing

Хороший вопрос и ответ. У меня есть обновление Swift 4, чтобы внести свой вклад:

var str: String? = "Hello, playground"
var list: Array<String>? = ["Hello", "Coder256"]

func somethingGeneric<T>(_ input: T?) {
  if (input != nil) {
    print(input!);
  }
}

func somethingGeneric(_ input: ExpressibleByNilLiteral?) {}


somethingGeneric("Hello, World!")    // Hello, World!
somethingGeneric(nil)                // *nothing printed*
somethingGeneric(nil as String?)     // *nothing printed*
somethingGeneric(str)                // Hello, playground
str = nil
somethingGeneric(str)                // *nothing printed*
somethingGeneric(list)               // ["Hello", "Coder256"]
list = nil
somethingGeneric(list)               // *nothing printed*

На самом деле есть способ сделать это, вдохновленный внутренним кодом Alamofire.

Вам не нужно устанавливать Alamofire, чтобы использовать это решение.

Применение

Ваше проблемное определение метода

      func someMethod<SomeGenericOptionalCodableType: Codable>(with someParam: SomeGenericOptionalCodableType? = nil) {
        // your awesome code goes here
}

Что работает ✅

      // invoke `someMethod` correctly 
let someGoodParam1 = Alamofire.Empty.value
someMethod(with: someGoodParam1)

думаю можно использовать Alamofire.Empty.valueкак значение по умолчанию в someMethodопределение как параметр.

Что не работает ❌

      // invoke `someMethod` incorrectly
let someBadParam1: Codable? = nil
let someBadParam2 = nil
someMethod(with: someBadParam1)
someMethod(with: someBadParam2)

Определение решения (источник )

      /// Type representing an empty value. Use `Empty.value` to get the static instance.
public struct Empty: Codable {
    /// Static `Empty` instance used for all `Empty` responses.
    public static let value = Empty()
}

Я думаю, что ты никогда не позвонишь somethingGeneric(nil) Но в основном somethingGeneric(value) или же somethingGeneric(function()) для которого у компилятора достаточно информации, чтобы не застрять, пытаясь угадать тип:

func somethingGeneric<T>(input: T?) {
    if let input = input {
        print(input);
    }
}

func neverString() -> String? {
    return nil
}

let a: String? = nil

somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric(a) // Nothing and no error
somethingGeneric(neverString()) // Nothing and no error

Кроме того, я бы использовал if let синтаксис вместо if(value != nil),

Вот решение, которое я придумал, которое компилируется на Swift 5, поскольку многие из решений здесь не компилировались для меня. Это может показаться хакерским, поскольку я использую хранимую переменную, чтобы помочь делу. Мне не удалось придумать версию Swift 5 с параметрами nil, которые разрешают вводитьT.

class MyClass {
    func somethingGeneric<T>(input: T?) {
        if let input = input {
            print(input)
        }
    }

    func somethingGeneric() {
        somethingGeneric(Object.Nil)
    }

}

final class Object {
    static var Nil: Object? //this should never be set
}
Другие вопросы по тегам