Error del compilador rápido: "Expresión demasiado compleja" en una concatenación de cadenas

143

Esto me parece más divertido que nada. Lo arreglé, pero me pregunto por la causa. Aquí está el error: DataManager.swift:51:90: Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions. ¿Por qué se queja? Parece una de las expresiones más simples posibles.

El compilador apunta a la columns + ");";sección

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

la solución es:

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

esto también funciona (a través de @efischency) pero no me gusta tanto porque creo que (se pierden:

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

Kendrick Taylor
fuente
10
¿Viste si esto funciona var statement = "create table if not exists \(self.tableName()) (\(columns))"?
efischency
55
La interpolación de cadenas, como lo recomienda @efischency, generalmente es una mejor opción que la concatenación manual +.
mattt
55
Claro, pero ese no es el punto. No me importa si es la forma "sugerida" o no, solo quiero saber por qué el compilador se atraganta. Tengo una solución que funciona, no se trata de corregir el error, se trata de comprender el error.
Kendrick Taylor
2
Por lo que he escuchado, el compilador Swift sigue siendo un trabajo en progreso. El equipo podría apreciar un informe de error al respecto.
molbdnilo
No tuve ningún problema compilando esto con 6.3.1. Tenía mensajes ridículos similares en el pasado. Necesitamos esperar hasta que Swift deje su estado alfa.
qwerty_so

Respuestas:

183

No soy un experto en compiladores. No sé si esta respuesta "cambiará su forma de pensar de manera significativa", pero mi comprensión del problema es la siguiente:

Tiene que ver con la inferencia de tipos. Cada vez que usa el +operador, Swift tiene que buscar a través de todas las sobrecargas posibles +e inferir qué versión de +usted está usando. Conté poco menos de 30 sobrecargas para el +operador. Esas son muchas posibilidades, y cuando encadena 4 o 5 +operaciones juntas y le pide al compilador que infiera todos los argumentos, está pidiendo mucho más de lo que podría parecer a primera vista.

Esa inferencia puede complicarse; por ejemplo, si agrega un UInt8y un Intuso +, la salida será un Int, pero hay algo de trabajo en evaluar las reglas para mezclar tipos con operadores.

Y cuando usa literales, como los Stringliterales en su ejemplo, el compilador realiza el trabajo de convertir el Stringliteral en a String, y luego realiza el trabajo de inferir el argumento y los tipos de retorno para el +operador, etc.

Si una expresión es suficientemente compleja, es decir, requiere que el compilador haga demasiadas inferencias sobre los argumentos y los operadores, se cierra y le dice que se cierra.

Hacer que el compilador se cierre una vez que una expresión alcanza un cierto nivel de complejidad es intencional. La alternativa es dejar que el compilador lo intente y lo haga, y ver si puede, pero eso es arriesgado: el compilador podría seguir intentándolo para siempre, atascarse o simplemente bloquearse. Entonces, entiendo que hay un umbral estático para la complejidad de una expresión que el compilador no irá más allá.

Tengo entendido que el equipo de Swift está trabajando en las optimizaciones del compilador que harán que estos errores sean menos comunes. Puede aprender un poco al respecto en los foros de desarrolladores de Apple haciendo clic en este enlace .

En los foros de desarrollo, Chris Lattner ha pedido a las personas que presenten estos errores como informes de radar, porque están trabajando activamente para solucionarlos.

Así es como lo entiendo después de leer varias publicaciones aquí y en el foro de desarrollo al respecto, pero mi comprensión de los compiladores es ingenua, y espero que alguien con un conocimiento más profundo de cómo manejan estas tareas se expanda en lo que yo He escrito aquí.

Aaron Rasmussen
fuente
Pensé algo en ese sentido, pero fue una respuesta útil, no obstante. Gracias por responder. ¿Contaste el número de operadores + a mano o hay alguna forma ingeniosa que desconozco?
Kendrick Taylor
Le eché un vistazo en SwiftDoc.org y los conté a mano. Esta es la página de la que estoy hablando: swiftdoc.org/operator/pls
Aaron Rasmussen
28
Esto es un error, independientemente de si lo llamarán así. Los compiladores de otros idiomas no tienen problemas con códigos similares a los publicados. Sugerir que el usuario final debería arreglarlo es una tontería.
John
77
¿Tipo de inferencia? ¿Cuál es el punto de tener un lenguaje de tipo fuerte como Swift (en el que ni siquiera puedes concatenar String + Int sin tener que lanzar el Int) en esta situación ridícula? Una vez más, Swift intenta resolver problemas que nadie tenía en primer lugar.
Azurlake
10
@John ¡No es un error, solo un mal diseño de lenguaje si me preguntas! Swift va demasiado lejos simplemente tratando de ser diferente.
T. Rex
31

Esto es casi lo mismo que la respuesta aceptada pero con un diálogo adicional (tuve con Rob Napier, sus otras respuestas y Matt, Oliver, David de Slack) y enlaces.

Ver los comentarios en esta discusión. La esencia de esto es:

+ está muy sobrecargado (Apple parece haber solucionado esto en algunos casos)

El +operador está muy sobrecargado, a partir de ahora tiene 27 funciones diferentes, por lo que si está concatenando 4 cadenas, es decir, tiene 3 +operadores, el compilador debe verificar entre 27 operadores cada vez, por lo que es 27 ^ 3 veces. Pero eso no es todo.

También hay una comprobación para ver si las funciones lhsy rhsde +ambas son válidas si son llamadas a través del núcleo de las appendllamadas. Allí puede ver que hay varias comprobaciones intensivas que pueden ocurrir. Si la cadena se almacena de forma no contigua, lo que parece ser el caso si la cadena con la que está tratando está realmente unida a NSString. Swift luego tiene que volver a ensamblar todos los búferes de matriz de bytes en un solo búfer contiguo y eso requiere crear nuevos búferes en el camino. y finalmente obtienes un búfer que contiene la cadena que estás intentando concatenar juntos.

En pocas palabras, hay 3 grupos de comprobaciones del compilador que lo retrasarán, es decir, cada subexpresión debe reconsiderarse a la luz de todo lo que pueda devolver . Como resultado, la concatenación de cadenas con interpolación, es decir, el uso " My fullName is \(firstName) \(LastName)"es mucho mejor que "My firstName is" + firstName + LastNamedado que la interpolación no tiene ninguna sobrecarga

Swift 3 ha realizado algunas mejoras. Para obtener más información, lea ¿Cómo fusionar varias matrices sin ralentizar el compilador? . No obstante, el +operador todavía está sobrecargado y es mejor utilizar la interpolación de cadenas para cadenas más largas.


Uso de opcionales (problema continuo - solución disponible)

En este proyecto muy simple:

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

El tiempo de compilación de las funciones es el siguiente:

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

Observe cuán loca es la duración de la compilación concatenatedOptionals.

Esto se puede resolver haciendo:

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

que compila en 88ms

La causa raíz del problema es que el compilador no identifica el ""como a String. En realidad esExpressibleByStringLiteral

El compilador verá ??y tendrá que recorrer todos los tipos que se hayan conformado a este protocolo , hasta que encuentre un tipo que pueda ser el predeterminado String. Al usar el emptyStringque está codificado String, el compilador ya no necesita recorrer todos los tipos deExpressibleByStringLiteral

Para aprender cómo registrar tiempos de compilación, vea aquí o aquí


Otras respuestas similares de Rob Napier en SO:

¿Por qué la adición de cadenas tarda tanto en construirse?

¿Cómo fusionar varias matrices sin ralentizar el compilador?

Swift Array contiene función que hace que los tiempos de construcción sean largos

Miel
fuente
19

¡Esto es bastante ridículo sin importar lo que digas! :)

ingrese la descripción de la imagen aquí

Pero esto se pasa fácilmente

return "\(year) \(month) \(dayString) \(hour) \(min) \(weekDay)"
karim
fuente
2

Tuve un problema similar:

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

En Xcode 9.3 la línea es así:

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

Después de cambiarlo a algo como esto:

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

Todo salió bien.

Probablemente tenga algo que ver con el compilador Swift que intenta inferir el tipo de datos del código.

vedrano
fuente