Неможливо явно спеціалізувати загальну функцію


92

У мене проблема з таким кодом:

func generic1<T>(name : String){
}

func generic2<T>(name : String){
     generic1<T>(name)
}

результат generic1 (ім'я) до помилки компілятора "Неможливо явно спеціалізувати загальну функцію"

Чи є спосіб уникнути цієї помилки? Я не можу змінити підпис функції generic1, тому вона повинна бути (String) -> Void


2
Який сенс використовувати узагальнений тип тут, коли його не можна вивести для контексту? Якщо загальний тип використовується лише внутрішньо, ви повинні вказати тип у тілі функції.
Кірштейнс

Чи Tвзагалі використовується тип заповнювача в generic1()? Як би ви викликали цю функцію, щоб компілятор міг зробити висновок про тип?
Martin R

3
Я сподіваюся, що є якийсь спосіб викликати такі функції, як генетичний1 <blaClass> ("SomeString")
Greyisf,

2
Узагальнення стосуються не лише випадків, коли компілятор може зробити висновок про контекст. Проста спеціалізація функції дозволить інфікувати інші частини коду. func foo<T>() -> T { ... }приклад : що робить щось із об'єктом tтипу Tта поверненням t. Явно спеціалізація Tдозволила t1б потрапити в систему var t1 = foo<T>(). Я хотів би, щоб був спосіб викликати функції і цим способом. C # дозволяє це
nacho4d

1
Я теж натрапив на це. Немає сенсу створювати загальну функцію, якщо ви змушені передавати тип як параметр. Це має бути помилка правильно ?!
Нік

Відповіді:


160

У мене також була ця проблема, і я знайшов обхідний шлях для своєї справи.

У цій статті у автора така сама проблема

https://www.iphonelife.com/blog/31369/swift-programming-101-generics-practical-guide

Отже, проблема полягає в тому, що компілятору потрібно якось зробити висновок про тип T. Але не можна просто використовувати загальний <type> (параметри ...).

Зазвичай компілятор може шукати тип T, скануючи типи параметрів, оскільки саме тут у багатьох випадках використовується T.

У моєму випадку це було трохи інакше, тому що тип повернення моєї функції був T. У вашому випадку здається, що ви взагалі не використовували T у своїй функції. Я думаю, ви просто спростили приклад коду.

Отже, у мене така функція

func getProperty<T>( propertyID : String ) -> T

І у випадку, наприклад

getProperty<Int>("countProperty")

компілятор видає мені помилку:

Неможливо явно спеціалізувати загальну функцію

Отже, щоб надати компілятору ще одне джерело інформації, з якого можна зробити висновок про тип T, потрібно чітко оголосити тип змінної, у якій зберігається повертане значення.

var value : Int = getProperty("countProperty")

Таким чином, компілятор знає, що T має бути цілим числом.

Отже, я думаю, загалом це просто означає, що якщо ви вказуєте загальну функцію, ви повинні принаймні використовувати T у своїх типах параметрів або як тип повернення.


2
Врятував мені купу часу. Дякую.
chrislarson

4
Чи є спосіб зробити це, якщо немає поверненого значення? тобто,func updateProperty<T>( propertyID : String )
Кайл Bashour

1
Не розумію, чому ти хотів би це зробити? Оскільки ви нічого не повертаєте, не вигідно оголосити свою функцію загальною.
ThottChief

@ThottChief є багато переваг, наприклад, якщо ви використовуєте / будуєте шляхи до ключів, або отримуєте ім'я типу як рядок тощо
zaitsman

Чудова відповідь спасибі. Я хотів би, let value = foo() as? Typeщоб це працювало, щоб його можна було використовувати в ifабо guardрезультат є необов’язковим, але це не так ...
agirault

54

Стрімкий 5

Зазвичай існує багато способів визначити загальні функції. Але вони засновані на умові, яку Tпотрібно використовувати як a parameter, або як a return type.

extension UIViewController {
    class func doSomething<T: UIView>() -> T {
        return T()
    }

    class func doSomethingElse<T: UIView>(value: T) {
        // Note: value is a instance of T
    }

    class func doLastThing<T: UIView>(value: T.Type) {
        // Note: value is a MetaType of T
    }
}

Після цього ми повинні надати Tпри дзвінку.

let result = UIViewController.doSomething() as UIImageView // Define `T` by casting, as UIImageView
let result: UILabel = UIViewController.doSomething() // Define `T` with property type, as UILabel
UIViewController.doSomethingElse(value: UIButton()) // Define `T` with parameter type, as UIButton
UIViewController.doLastThing(value: UITextView.self) // Define `T` with parameter type, as UITextView

Посилання:

  1. http://austinzheng.com/2015/01/02/swift-generics-pt-1/
  2. https://dispatchswift.com/type-constraints-for-generics-in-swift-d6bf2f0dbbb2

Чудова відповідь на загальну проблему ... оскільки існує так багато способів її виправити. Я також зрозумів, що self.result = UIViewController.doSomething()роботи працюють самостійно, якщо ви ввели властивість, коли заявляєте про це.
терадил

чудова відповідь, яка пояснює багато речей.
Охан Окбай

Java doLastThing<UITextView>()стає Swift doLastThing(UITextView.self), що ... принаймні, не найгірше. Краще, ніж явно вводити складні результати. Дякуємо за обхідний шлях.
Ерханніс

18

Рішення приймає тип класу як параметр (як у Java)

Щоб компілятор знав, з яким типом він має справу, передайте клас як аргумент

extension UIViewController {
    func navigate<ControllerType: UIViewController>(_ dump: ControllerType.Type, id: String, before: ((ControllerType) -> Void)?){
        let controller = self.storyboard?.instantiateViewController(withIdentifier: id) as! ControllerType
        before?(controller)
        self.navigationController?.pushViewController(controller, animated: true)
    }
}

Зателефонувати як:

self.navigate(UserDetailsViewController.self, id: "UserDetailsViewController", before: {
        controller in
        controller.user = self.notification.sender
    })

1
Це чудово і набагато краще прийнятої відповіді. Будь ласка, розгляньте можливість редагування, щоб видалити історичну частину або, принаймні, поставити її нижче рішення. Дякую!
Dan Rosenstark

1
@DanRosenstark Дякую за відгук :)
Орхан Аліханов,

Хоча це працює, я не думаю, що це найкраща практика. Причина в тому, що зараз визначення функції приймає рішення щодо того, що робити, якщо є проблема з акторським складом. Отже, тут ви просто розбиваєтесь, якщо акторський склад не вдається. Краще дозволити клієнту вирішити, що робити, оскільки це може залежати від клієнта. Отже, я збираюся проголосувати проти цієї відповіді з цієї причини.
посмішка Бот

@smileBot Ви маєте на увазі приклад поганий чи підхід?
Орхан Аліханов

Підхід. Я думаю, що загальним правилом програмування є відкладання спеціалізації, коли це практично. Це частина ідеї розуміння відповідальності. Спеціалізація загального не входить до функції. Це відповідальність абонента, оскільки функція не може знати, що потрібно всім абонентам. Якщо функція потрапляє в цю роль, це обмежує використання функції без посилення. Ви можете узагальнити це для багатьох проблем кодування. Ця єдина концепція мені надзвичайно допомогла.
посмішка Бот

4

Тут вам не потрібен загальний, оскільки у вас є статичні типи (String як параметр), але якщо ви хочете, щоб загальна функція викликала іншу, ви можете зробити наступне.

Використання загальних методів

func fetchObjectOrCreate<T: NSManagedObject>(type: T.Type) -> T {
    if let existing = fetchExisting(type) {
       return existing
    }
    else {
        return createNew(type)
    }
}

func fetchExisting<T: NSManagedObject>(type: T.Type) -> T {
    let entityName = NSStringFromClass(type)
     // Run query for entiry
} 

func createNew<T: NSManagedObject>(type: T.Type) -> T {
     let entityName = NSStringFromClass(type)
     // create entity with name
} 

Використання загального класу (менш гнучкий, оскільки загальний можна визначити лише для 1 типу на екземпляр)

class Foo<T> {

   func doStuff(text: String) -> T {
      return doOtherStuff(text)
   }

   func doOtherStuff(text: String) -> T {

   }  

}

let foo = Foo<Int>()
foo.doStuff("text")

3

Я думаю, що коли ви вказуєте загальну функцію, вам слід вказати деякі параметри типу T, як описано нижче:

func generic1<T>(parameter: T) {
    println("OK")
}

func generic2<T>(parameter: T) {
    generic1(parameter)
}

і якщо ви хочете викликати метод handle (), ви можете зробити це, написавши протокол і вказавши обмеження типу для T:

protocol Example {
    func handle() -> String
}

extension String: Example {
    func handle() -> String {
        return "OK"
    }
}

func generic1<T: Example>(parameter: T) {
    println(parameter.handle())
}

func generic2<T: Example>(parameter: T) {
    generic1(parameter)
}

тому ви можете викликати цю загальну функцію за допомогою String:

generic2("Some")

і він буде скомпільований


1

Наразі в моїй найкращій практиці використовувалася відповідь @ orkhan-alikhanov. Сьогодні, дивлячись на SwiftUI та як .modifier()і як ViewModifierце реалізовано, я знайшов інший спосіб (або це більше обхідний шлях?)

Просто оберніть другу функцію в a struct.

Приклад:

Якщо це дає вам "Неможливо явно спеціалізувати загальну функцію"

func generic2<T>(name: String){
     generic1<T>(name)
}

Це може допомогти. Оберніть декларацію generic1в struct:

struct Generic1Struct<T> {
    func generic1(name: String) {## do, whatever it needs with T ##}
}

і назвіть це за допомогою:

func generic2<T>(name : String){
     Generic1Struct<T>().generic1(name: name)
}

Примітки:

  • Я не знаю, чи допомагає це в будь-якому випадку, коли виникає це повідомлення про помилку. Я просто знаю, що я застряг багато разів, коли це з’явилося. Я знаю, що це рішення допомогло сьогодні, коли з’явилося повідомлення про помилку.
  • Те, як Swift поводиться із Generics, для мене все ще бентежить.
  • Цей приклад та обхідний шлях із цим structє хорошим прикладом. Обхідний шлях тут не має трохи більше інформації - але передає компілятор. Однакова інформація, але різні результати? Тоді щось не так. Якщо це помилка компілятора, її можна виправити.

0

У мене була подібна проблема з моєю загальною функцією класу class func retrieveByKey<T: GrandLite>(key: String) -> T?.

Я не міг назвати це let a = retrieveByKey<Categories>(key: "abc")там, де Категорії - це підклас GrandLite.

let a = Categories.retrieveByKey(key:"abc")повернуто GrandLite, а не Категорії. Загальні функції не виводять тип на основі класу, який їх викликає.

class func retrieveByKey<T: GrandLite>(aType: T, key: String>) -> T?дав мені помилку, коли я спробував, let a = Categories.retrieveByKey(aType: Categories, key: "abc")дав мені помилку, що він не міг перетворити Categories.Type в GrandLite, хоча Categories є підкласом GrandLite. Однак ...

class func retrieveByKey<T: GrandLite>(aType: [T], key: String) -> T? дійсно працював, якщо я спробував, let a = Categories.retrieveByKey(aType: [Categories](), key: "abc")очевидно, явне призначення підкласу не працює, але неявне призначення з використанням іншого загального типу (масиву) працює в Swift 3.


1
Щоб виправити помилку, вам слід надати aTypeяк примірник T, а не ставити Categoriesсебе. Приклад: let a = Categories.retrieveByKey(aType: Categories(), key: "abc"). Іншим рішенням є визначення aType: T.Type. Тоді назвіть метод якlet a = Categories.retrieveByKey(aType: Categories.self, key: "abc")
nahung89
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.