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

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

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?).


person Coder-256    schedule 16.03.2016    source источник


Ответы (6)


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

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

Единственный раз, когда ваша проблема существует, это когда вы передаете литерал nil. Как отмечает @Valentin, если вы передаете переменную, которая оказывается be 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
person Rob Napier    schedule 16.03.2016
comment
Отличный ответ Роба, комментирующий, потому что это определенно должен быть принятый ответ - person Ilias Karim; 24.07.2020

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

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

somethingGeneric(Optional<String>.None)

person Mike Pollard    schedule 16.03.2016
comment
nil as String?, о котором упоминал OP, в основном делает это тоже - person Will M.; 16.03.2016
comment
@УиллМ. Я также упомянул, что не хочу такого поведения. - person Coder-256; 16.03.2016
comment
Я не думаю, что есть способ обойти это. Кроме того, что у вас есть func somethingGenericWithNil() и вместо этого вызывается :) - person Mike Pollard; 16.03.2016

Хороший вопрос и ответ. У меня есть обновление 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*
person stonecanyon    schedule 28.03.2018

Я понял:

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*
person Coder-256    schedule 16.03.2016
comment
Однако ваша функция somethingGeneric<T>(input:T?) по-прежнему не принимает nil в качестве значения. Он просто проходит через пустую функцию и ничего не печатает, потому что эта функция ничего не делает. При этом, если вы вызовете функцию с nil, она пройдет через функцию с input: NilLiteralConvertible в качестве параметра, но если вы вызовете ее с nil Optional, например, сказав let x: String?, она пройдет через функцию с input: T? в качестве параметра, поэтому вам придется дублировать свою логику обработки нуля. - person Will M.; 16.03.2016
comment
@УиллМ. somethingGeneric<T>(input: T?) не берет nil; somethingGeneric(input: NilLiteralConvertible?) делает. Я выбрал этот способ, потому что он работает для всех трех, но вы правы в том, что я мог бы преобразовать somethingGeneric<T>(input: T?), чтобы не принимать необязательный параметр, но это сломало бы nil as String? (я просто не хотел, чтобы было требование, чтобы функция вызывалась именно так). путь). - person Coder-256; 16.03.2016
comment
Обратите внимание, что, например, let ptr : UnsafePointer<Int>? = ... ; somethingGeneric(ptr) вызовет функцию вторая, потому что UnsafePointer — это NilLiteralConvertible, а вторая общая функция более специфична. - person Martin R; 16.03.2016
comment
@MartinR Хороший вопрос. В данном случае это не имеет значения, но я мог бы использовать if (input != nil), чтобы проверить, что input на самом деле является nil. - person Coder-256; 16.03.2016
comment
@MartinR Все необязательные параметры являются NilLiteralConvertible, но необязательная строка по-прежнему относится к первой функции, независимо от того, является ли она необязательным.Some или необязательным.None - person Will M.; 16.03.2016
comment
@WillM.: Вторая функция принимает NilLiteralConvertible?, а String? не соответствует этому типу (поскольку String не является NilLiteralConvertible). - person Martin R; 16.03.2016
comment
@MartinR Строка? является NilLiteralConvertible, даже если String нет. Если вы избавитесь от первой функции, вторая функция примет somethingGeneric(OptionalString) Возможно, поскольку, как вы сказали, она не совсем подходит для опциональности, компилятор считает, что первая функция подходит лучше. - person Will M.; 16.03.2016
comment
@УиллМ. Это необязательное продвижение. В случае, который вы описываете, компилятор продвигается к String??, что соответствует NilLiteralConvertible?. По моему опыту, такое использование дженериков очень хрупкое, и такое необязательное продвижение обожжет вас неожиданным образом, создав ?? типов. Я бы порекомендовал каким-то образом ограничить T. - person Rob Napier; 16.03.2016
comment
@RobNapier приятно знать. Я никогда раньше не слышал этого термина. Я не удивлен, что это сожжет вас, учитывая, что если вы возьмете необязательную строку и проверите, является ли она NilLiteralConvertible? он возвращает false, но все равно входит в функцию. Теперь я знаю кое-что новое о Swift. - person Will M.; 16.03.2016

Я думаю, что вы никогда не будете вызывать 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).

person Valentin    schedule 16.03.2016

Вот решение, которое я придумал, которое компилируется на Swift 5, так как многие решения здесь не скомпилировались для меня. Это может считаться хакерским, поскольку я использую хранимую переменную, чтобы помочь делу. Мне не удалось придумать версию параметров nil для Swift 5, которые разрешают тип 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
}
person Dylan Reich    schedule 11.11.2019