Nie można jawnie specjalizować funkcji ogólnej

92

Mam problem z następującym kodem:

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

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

wynik generic1 (nazwa) do błędu kompilatora „Nie można jawnie wyspecjalizować funkcji ogólnej”

Czy jest jakiś sposób, aby uniknąć tego błędu? Nie mogę zmienić sygnatury funkcji generic1, dlatego powinno to być (String) -> Void

Greyisf
źródło
2
Jaki jest sens używania tutaj typu ogólnego, skoro nie można go wywnioskować z kontekstu? Jeśli typ ogólny jest używany tylko wewnętrznie, należy określić typ w treści funkcji.
Kirsteins,
Czy Tw ogóle jest używany typ symbolu zastępczego generic1()? Jak nazwałbyś tę funkcję, aby kompilator mógł wywnioskować typ?
Martin R
3
Mam nadzieję, że istnieje sposób na wywołanie funkcji takiej jak genetic1 <blaClass> ("SomeString")
Greyisf
2
Typy generyczne są przeznaczone nie tylko do przypadków, w których kompilator może wywnioskować kontekst. Wyraźna specjalizacja funkcji pozwoliłaby na wyciągnięcie wniosków z innych części kodu. np. func foo<T>() -> T { ... }robi coś z obiektem ttypu Ti wynikiem t. Wyraźna specjalizacja Tumożliwiłaby t1ingerencję var t1 = foo<T>(). Żałuję, że nie ma sposobu na tak nazywanie funkcji. C # na to pozwala
nacho4d
1
Po prostu w to też wpadłem. Tworzenie funkcji ogólnej nie ma sensu, jeśli musisz podać typ jako parametr. To musi być błąd, prawda ?!
Nick

Odpowiedzi:

161

Miałem też ten problem i znalazłem obejście dla mojego przypadku.

W tym artykule autor ma ten sam problem

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

Wydaje się więc, że problem polega na tym, że kompilator musi jakoś wywnioskować typ T. Ale nie wolno po prostu używać generycznego <type> (params ...).

Zwykle kompilator może szukać typu T, skanując typy parametrów, ponieważ w wielu przypadkach jest to miejsce, w którym T jest używany.

W moim przypadku było trochę inaczej, ponieważ typem zwracanym przez moją funkcję był T. W twoim przypadku wygląda na to, że w ogóle nie użyłeś T w swojej funkcji. Chyba właśnie uprościłeś przykładowy kod.

Mam więc następującą funkcję

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

A w przypadku np

getProperty<Int>("countProperty")

kompilator podaje mi błąd:

Nie można jawnie specjalizować funkcji ogólnej

Aby więc udostępnić kompilatorowi inne źródło informacji, z którego będzie mógł wywnioskować typ T, musisz jawnie zadeklarować typ zmiennej, w której zapisywana jest wartość zwracana.

var value : Int = getProperty("countProperty")

W ten sposób kompilator wie, że T musi być liczbą całkowitą.

Myślę więc, że ogólnie oznacza to po prostu, że jeśli określisz funkcję ogólną, musisz przynajmniej użyć T w typach parametrów lub jako typ zwracany.

ThottChief
źródło
2
Zaoszczędziło mi mnóstwo czasu. Dziękuję Ci.
chrislarson
4
Czy można to zrobić, jeśli nie ma wartości zwracanej? tj.func updateProperty<T>( propertyID : String )
Kyle Bashour
1
Nie rozumiem, dlaczego chcesz to zrobić? Ponieważ nic nie zwracasz, nie ma korzyści z deklarowania swojej funkcji jako generycznej.
ThottChief
@ThottChief ma wiele zalet, np. Jeśli używasz / budujesz ścieżki klawiszy lub uzyskujesz nazwę typu jako ciąg znaków itp.
zaitsman
Świetna odpowiedź, dzięki. Chciałbym, żeby to zadziałało, let value = foo() as? Typeżeby można go było użyć w a iflub guardwynik jest opcjonalny, ale nie ...
agirault
54

Szybki 5

Zwykle istnieje wiele sposobów definiowania funkcji ogólnych. Ale są one oparte na warunku, który Tmusi być używany jako a parameterlub jako 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
    }
}

Po tym musimy podać Tpodczas rozmowy.

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

Odniesienie:

  1. http://austinzheng.com/2015/01/02/swift-generics-pt-1/
  2. https://dispatchswift.com/type-constraints-for-generics-in-swift-d6bf2f0dbbb2
nahung89
źródło
Świetna odpowiedź na ogólny problem ... ponieważ istnieje wiele sposobów rozwiązania tego problemu. Zrozumiałem również, że self.result = UIViewController.doSomething()działa samodzielnie, o ile wpisałeś właściwość podczas jej deklarowania.
teradyl
świetna odpowiedź wyjaśniająca wiele rzeczy.
Okhan Okbay
Java doLastThing<UITextView>()staje się Swift doLastThing(UITextView.self), co nie jest ... przynajmniej najgorsze. Lepsze niż jawne wpisywanie skomplikowanych wyników. Dzięki za obejście.
Erhannis
18

Rozwiązanie polega na przyjęciu typu klasy jako parametru (jak w Javie)

Aby kompilator wiedział, z jakim typem ma do czynienia, podaj klasę jako argument

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)
    }
}

Zadzwoń jako:

self.navigate(UserDetailsViewController.self, id: "UserDetailsViewController", before: {
        controller in
        controller.user = self.notification.sender
    })
Orkhan Alikhanov
źródło
1
To jest świetne i znacznie lepsze niż akceptowana odpowiedź. Rozważ edycję, aby usunąć część historyczną lub przynajmniej umieścić ją poniżej rozwiązania. Dzięki!
Dan Rosenstark
1
@DanRosenstark Dzięki za informację zwrotną :)
Orkhan Alikhanov
Chociaż to działa, nie sądzę, że jest to najlepsza praktyka. Powodem jest to, że teraz definicja funkcji decyduje o tym, co zrobić, jeśli wystąpi problem z rzutowaniem. Więc tutaj po prostu się zawieszasz, jeśli obsada się nie powiedzie. Lepiej pozwolić klientowi zdecydować, co zrobić, ponieważ może się to różnić w zależności od klienta. Z tego powodu zamierzam obniżyć głosowanie na tę odpowiedź.
smileBot
@smileBot Masz na myśli zły przykład czy podejście?
Orkhan Alikhanov
Podejście. Myślę, że ogólną zasadą programowania jest odroczenie specjalizacji, gdy jest to praktyczne. To część idei zrozumienia odpowiedzialności. Specjalizacja rodzaju ogólnego nie jest obowiązkiem funkcji. Jest to odpowiedzialność dzwoniącego, ponieważ funkcja nie może wiedzieć, czego będą potrzebować wszyscy dzwoniący. Jeśli funkcja wejdzie w tę rolę, ogranicza to użycie funkcji bez zysku. Możesz uogólnić to na wiele problemów z kodowaniem. Ta pojedyncza koncepcja bardzo mi pomogła.
smileBot
4

Nie potrzebujesz tutaj generycznego, ponieważ masz statyczne typy (String jako parametr), ale jeśli chcesz, aby funkcja ogólna wywołała inną, możesz wykonać następujące czynności.

Korzystanie z metod ogólnych

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
} 

Korzystanie z klasy ogólnej (mniej elastyczna, ponieważ rodzaj ogólny można zdefiniować tylko dla 1 typu na instancję)

class Foo<T> {

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

   func doOtherStuff(text: String) -> T {

   }  

}

let foo = Foo<Int>()
foo.doStuff("text")
aryaxt
źródło
3

Myślę, że kiedy określasz funkcję ogólną, powinieneś określić niektóre parametry typu T, jak następuje:

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

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

a jeśli chcesz wywołać metodę handle (), możesz to zrobić, pisząc protokół i określając ograniczenie typu dla 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)
}

więc możesz wywołać tę ogólną funkcję za pomocą String:

generic2("Some")

i będzie się kompilować

Valerii Lider
źródło
1

Jak dotąd, moja najlepsza praktyka wykorzystywała odpowiedź @ orkhan-alikhanov. Dzisiaj, patrząc na SwiftUI i jak .modifier()iViewModifier implementację, znalazłem inny sposób (czy jest to bardziej obejście?)

Po prostu zawiń drugą funkcję w plik struct.

Przykład:

Jeśli ten daje Ci komunikat „Nie można jawnie wyspecjalizować funkcji ogólnej”

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

Ten może pomóc. Zawinąć deklarację generic1w struct:

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

i zadzwoń z:

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

Uwagi:

  • Nie wiem, czy to pomaga w jakimkolwiek możliwym przypadku, kiedy pojawia się ten komunikat o błędzie. Wiem tylko, że utknąłem wiele razy, kiedy to się pojawiło. Wiem, że to rozwiązanie pomogło dzisiaj, kiedy pojawił się komunikat o błędzie.
  • Sposób, w jaki Swift obsługuje generyczne, jest dla mnie wciąż mylący.
  • Ten przykład i obejście problemu structjest dobrym przykładem. To obejście nie ma trochę więcej informacji - ale przekazuje kompilatorowi. Te same informacje, ale inne wyniki? Więc coś jest nie tak. Jeśli jest to błąd kompilatora, można go naprawić.
jboi
źródło
0

Miałem podobny problem z funkcją klasy ogólnej class func retrieveByKey<T: GrandLite>(key: String) -> T?.

Nie mógłbym tego nazwać, let a = retrieveByKey<Categories>(key: "abc")gdzie Kategorie jest podklasą GrandLite.

let a = Categories.retrieveByKey(key:"abc")zwrócił GrandLite, a nie Categories. Funkcje ogólne nie wnioskują o typie na podstawie klasy, która je wywołuje.

class func retrieveByKey<T: GrandLite>(aType: T, key: String>) -> T?dał mi błąd, gdy próbowałem, let a = Categories.retrieveByKey(aType: Categories, key: "abc")dał mi błąd, że nie można przekonwertować Categories.Type na GrandLite, mimo że Categories jest podklasą GrandLite. JEDNAK...

class func retrieveByKey<T: GrandLite>(aType: [T], key: String) -> T? zrobił pracę gdybym próbował let a = Categories.retrieveByKey(aType: [Categories](), key: "abc")widocznie wyraźne przypisanie podklasy nie działa, ale niejawna assigment przy użyciu innego typu rodzajowego (tablica) działa w Swift 3.

adamek
źródło
1
Aby naprawić błąd, musisz podać aTypejako wystąpienie T, zamiast stawiać Categoriessię. Np let a = Categories.retrieveByKey(aType: Categories(), key: "abc"). : . Innym rozwiązaniem jest zdefiniowanie aType: T.Type. Następnie wywołaj metodę jakolet a = Categories.retrieveByKey(aType: Categories.self, key: "abc")
nahung89