Błąd kompilatora Swift: „Wyrażenie zbyt złożone” w konkatenacji ciągów

143

Uważam to za bardziej zabawne niż cokolwiek innego. Naprawiłem to, ale zastanawiam się nad przyczyną. Tutaj jest błąd: DataManager.swift:51:90: Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions. Dlaczego narzeka? Wydaje się, że jest to jedno z najprostszych możliwych wyrażeń.

Kompilator wskazuje na columns + ");";sekcję

func tableName() -> String { return("users"); } 

func createTableStatement(schema: [String]) -> String {

    var schema = schema;

    schema.append("id string");
    schema.append("created integer");
    schema.append("updated integer");
    schema.append("model blob");

    var columns: String = ",".join(schema);

    var statement = "create table if not exists " + self.tableName() + "(" + columns + ");";

    return(statement);
}

poprawka to:

var statement = "create table if not exists " + self.tableName();
statement += "(" + columns + ");";

to również działa (przez @efischency), ale nie podoba mi się to tak bardzo, ponieważ myślę, że (się zgubisz:

var statement = "create table if not exists \(self.tableName()) (\(columns))"

Kendrick Taylor
źródło
10
Czy widziałeś, czy to działa var statement = "create table if not exists \(self.tableName()) (\(columns))":?
efischency
5
Interpolacja ciągów, zgodnie z zaleceniami @efischency, jest ogólnie lepszą opcją niż ręczne łączenie z +.
mattt
5
Jasne, ale nie o to chodzi. Nie obchodzi mnie, czy to „sugerowany” sposób, czy nie, chcę tylko wiedzieć, dlaczego kompilator się tym dławi. Mam rozwiązanie, które działa, nie chodzi o naprawienie błędu, chodzi o zrozumienie błędu.
Kendrick Taylor
2
Z tego, co słyszałem, kompilator Swift wciąż jest w toku. Zespół może docenić zgłoszenie błędu w tej sprawie.
molbdnilo
Nie miałem problemu z kompilacją tego w wersji 6.3.1. W przeszłości miałem podobne śmieszne wiadomości. Musimy poczekać, aż Swift opuści stan alfa.
qwerty_so

Odpowiedzi:

183

Nie jestem ekspertem od kompilatorów - nie wiem, czy ta odpowiedź „zmieni Twój sposób myślenia w znaczący sposób”, ale moje zrozumienie problemu jest następujące:

Ma to związek z wnioskiem o typie. Za każdym razem, gdy używasz +operatora, Swift musi przeszukać wszystkie możliwe przeciążenia +i wywnioskować, której wersji +używasz. Naliczyłem dla +operatora nieco poniżej 30 przeciążeń . To wiele możliwości, a kiedy łączysz razem 4 lub 5 +operacji i prosisz kompilator o wywnioskowanie wszystkich argumentów, żądasz znacznie więcej, niż mogłoby się wydawać na pierwszy rzut oka.

To wnioskowanie może się skomplikować - na przykład, jeśli dodasz a UInt8i Intusing +, wynikiem będzie an Int, ale jest pewna praca, która wymaga oceny reguł mieszania typów z operatorami.

A kiedy używasz literałów, takich jak Stringliterały w twoim przykładzie, kompilator wykonuje pracę polegającą na konwersji Stringliterału na a String, a następnie wykonuje pracę polegającą na wywnioskowaniu argumentu i zwracaniu typów dla +operatora itp.

Jeśli wyrażenie jest wystarczająco złożone - tj. Wymaga od kompilatora wyciągnięcia zbyt wielu wniosków na temat argumentów i operatorów - kończy pracę i informuje, że zakończyło pracę.

Zamknięcie kompilatora, gdy wyrażenie osiągnie pewien poziom złożoności, jest zamierzone. Alternatywą jest pozwolić kompilatorowi spróbować to zrobić i sprawdzić, czy może, ale jest to ryzykowne - kompilator może próbować bez końca, grzęznąć lub po prostu się zawiesić. Rozumiem więc, że istnieje statyczny próg złożoności wyrażenia, którego kompilator nie wykroczy.

Rozumiem, że zespół Swift pracuje nad optymalizacjami kompilatora, które sprawią, że te błędy będą mniej powszechne. Możesz dowiedzieć się o tym trochę na forach Apple Developer, klikając to łącze .

Na forach deweloperów Chris Lattner poprosił ludzi o zgłaszanie tych błędów jako raportów radarowych, ponieważ aktywnie pracują nad ich naprawieniem.

Tak to rozumiem po przeczytaniu kilku postów tutaj i na forum deweloperów na ten temat, ale moje rozumienie kompilatorów jest naiwne i mam nadzieję, że ktoś z głębszą wiedzą o tym, jak radzi sobie z tymi zadaniami, rozwinie to, co ja napisałem tutaj.

Aaron Rasmussen
źródło
Wymyśliłem coś takiego, ale była to pomocna odpowiedź. Dzięki za odpowiedź. Czy policzyłeś liczbę operatorów + ręcznie, czy jest jakiś sprytny sposób, którego nie jestem świadomy?
Kendrick Taylor
Po prostu rzuciłem okiem na SwiftDoc.org i policzyłem je ręcznie. To jest strona o której mówię: swiftdoc.org/operator/pls
Aaron Rasmussen
28
To jest błąd, niezależnie od tego, czy tak to nazywają. Kompilatory innych języków nie mają problemu z kodem podobnym do tego, który został opublikowany. Sugerowanie, że użytkownik końcowy powinien to naprawić, jest głupie.
John
7
Typ wnioskowania? Jaki jest sens posiadania silnie wpisanego języka, takiego jak Swift (w którym nie można nawet łączyć String + Int bez rzucania Int) w tej absurdalnej sytuacji? Po raz kolejny Swift próbuje rozwiązać problemy, których nikt na początku nie miał.
Azurlake,
10
@John To nie błąd, po prostu zły projekt językowy, jeśli o mnie chodzi! Szybki posuwa się za daleko, próbując być inny.
T. Rex
31

Jest to prawie taka sama jak zaakceptowana odpowiedź, ale z dodatkowymi dialogami (miałem z Robem Napierem, jego innymi odpowiedziami oraz Mattem, Oliverem, Davidem ze Slacka) i linkami.

Zobacz komentarze w tej dyskusji. Istota tego jest taka:

+ jest mocno przeciążony (wydaje się, że Apple naprawił to w niektórych przypadkach)

+Operator jest mocno przeciążony, ponieważ od teraz ma 27 różnych funkcji, więc jeśli złączenie 4 strings czyli masz 3 +operatorów kompilator musi się sprawdzić między 27 operatorów za każdym razem, więc to 27 ^ 3 razy. Ale to nie wszystko.

Istnieje również sprawdzić , czy lhsi rhsz +funkcjami są zarówno ważne, jeżeli są one wywołuje aż do jądrze appendnazywa. Jak widać , może dojść do szeregu dość intensywnych kontroli . Jeśli ciąg jest przechowywany w sposób nieciągły, co wydaje się mieć miejsce, jeśli ciąg, z którym masz do czynienia, jest faktycznie mostkowany do NSString. Swift musi następnie ponownie złożyć wszystkie bufory tablicy bajtów w jeden ciągły bufor, co wymaga tworzenia nowych buforów po drodze. a potem w końcu otrzymujesz jeden bufor, który zawiera ciąg, który próbujesz połączyć.

Krótko mówiąc, istnieją 3 klastry kontroli kompilatora, które spowalniają Cię, tj. Każde wyrażenie podrzędne musi zostać ponownie przemyślane w świetle wszystkiego, co może zwrócić . W rezultacie łączenie ciągów za pomocą interpolacji, tj. Używanie " My fullName is \(firstName) \(LastName)"jest znacznie lepsze niż "My firstName is" + firstName + LastNameponieważ interpolacja nie ma żadnego przeciążenia

Swift 3 wprowadził pewne ulepszenia. Aby uzyskać więcej informacji, przeczytaj artykuł Jak scalić wiele tablic bez spowalniania kompilatora? . Niemniej jednak +operator jest nadal przeciążony i lepiej jest używać interpolacji ciągów dla dłuższych ciągów


Wykorzystanie opcji (bieżący problem - dostępne rozwiązanie)

W tym bardzo prostym projekcie:

import UIKit

class ViewController: UIViewController {

    let p = Person()
    let p2 = Person2()

    func concatenatedOptionals() -> String {
        return (p2.firstName ?? "") + "" + (p2.lastName ?? "") + (p2.status ?? "")
    }

    func interpolationOptionals() -> String {
        return "\(p2.firstName ?? "") \(p2.lastName ?? "")\(p2.status ?? "")"
    }

    func concatenatedNonOptionals() -> String {
        return (p.firstName) + "" + (p.lastName) + (p.status)
    }

    func interpolatedNonOptionals() -> String {
        return "\(p.firstName) \(p.lastName)\(p.status)"
    }
}


struct Person {
    var firstName = "Swift"
    var lastName = "Honey"
    var status = "Married"
}

struct Person2 {
    var firstName: String? = "Swift"
    var lastName: String? = "Honey"
    var status: String? = "Married"
}

Czas kompilacji funkcji jest następujący:

21664.28ms  /Users/Honey/Documents/Learning/Foundational/CompileTime/CompileTime/ViewController.swift:16:10 instance method concatenatedOptionals()
2.31ms  /Users/Honey/Documents/Learning/Foundational/CompileTime/CompileTime/ViewController.swift:20:10 instance method interpolationOptionals()
0.96ms  /Users/Honey/Documents/Learning/Foundational/CompileTime/CompileTime/ViewController.swift:24:10 instance method concatenatedNonOptionals()
0.82ms  /Users/Honey/Documents/Learning/Foundational/CompileTime/CompileTime/ViewController.swift:28:10 instance method interpolatedNonOptionals()

Zwróć uwagę, jak szalony jest czas trwania kompilacji concatenatedOptionals.

Można to rozwiązać, wykonując:

let emptyString: String = ""
func concatenatedOptionals() -> String {
    return (p2.firstName ?? emptyString) + emptyString + (p2.lastName ?? emptyString) + (p2.status ?? emptyString)
}

który się kompiluje 88ms

Główną przyczyną problemu jest to, że kompilator nie identyfikuje ""pliku jako String. Właściwie toExpressibleByStringLiteral

Kompilator zobaczy ??i będzie musiał przeglądać wszystkie typy, które są zgodne z tym protokołem , aż znajdzie typ, który może być domyślny String. Używając emptyStringktóre jest zakodowane na stałe String, kompilator nie musi już przechodzić przez wszystkie zgodne typyExpressibleByStringLiteral

Aby dowiedzieć się, jak rejestrować czasy kompilacji, zobacz tutaj lub tutaj


Inne podobne odpowiedzi Roba Napiera na SO:

Dlaczego tworzenie dodawania ciągów trwa tak długo?

Jak połączyć wiele tablic bez spowalniania kompilatora?

Swift Array zawiera funkcję, która wydłuża czas kompilacji

kochanie
źródło
19

Bez względu na to, co mówisz, jest to dość śmieszne! :)

wprowadź opis obrazu tutaj

Ale to łatwo przechodzi

return "\(year) \(month) \(dayString) \(hour) \(min) \(weekDay)"
karim
źródło
2

Miałem podobny problem:

expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions

W Xcode 9.3 linia wygląda tak:

let media = entities.filter { (entity) -> Bool in

Po zmianie na coś takiego:

let media = entities.filter { (entity: Entity) -> Bool in

wszystko się udało.

Prawdopodobnie ma to coś wspólnego z kompilatorem Swift, który próbuje wywnioskować typ danych z otaczającego go kodu.

vedrano
źródło