Protocol can only be used as a generic constraint because it has Self or associated type requirements

Autor: | Última modificación: 13 de noviembre de 2022 | Tiempo de Lectura: 5 minutos
Temas en este post:

Tipos Avanzados en Swift

Protocol can only be used as a generic constraint because it has Self or associated type requirements.

¿Te suena de algo este error? Si la respuesta es afirmativa, es que has estado currando con Swift tal y como Lattner te lo ha indicado:

  • Manejas solo structs y protocolos.
  • Los “value types” son la repanocha.

golpe-de-sarten_Protocol can only be used as a generic constraint because it has Self or associated type requirements

Has hecho todo lo que te han pedido, y tu recompensa ha sido ese sartenazo en los morros del compilador.

Para entender lo que está ocurriendo, tendremos que darnos un paseo por el sistema de tipos de Swift. Prometo que la senda será interesante y volveremos de ella con un conocimiento del lenguaje más propio de Grokk que de un humano cualquiera.

¿Qué es un tipo?

Lo primero que tenemos que hacer, curiosamente, es poner en tela de juicio algo fundamental. Algo tan básico que rara vez paramos a pensar en ello.

Un ejemplo podría ser: ¿Qué es el dinero? Todos los días lo manejamos, pasamos a menudo 8 horas de nuestro día dedicados a obtenerlo y es algo que domina nuestras vidas. Sin embargo, ¿alguna vez has parado para pensar qué es el dinero?

Si yo fuese un alienígena que acabase de aterrizar en tu jardín y te hiciese esa pregunta (¿qué es el dinero?), ¿sabrías darme una definición?

Recientemente, mientras aprendía sobre Bitcoin y Blockchain, me hice esa pregunta, y sorprendemente me llevó, como a Alicia, hasta el fondo de la madriguera del conejo. Esa pregunta tonta, cambió mi forma de ver el mundo en el que vivimos.

alicia-en-el-pais-de-las-maravillas

Hoy no hablaremos sobre el dinero, eso lo haremos más adelante en otro artículo sobre Bitcoin, pero os haré una pregunta igual de evidente (para un friki): ¿Qué es un tipo?

Recomiendo que pares un momento e intentes responder a esa pregunta. Al fin y al cabo, usas tipos todos los días, sin parar, en tu código. Pues bien, ¿qué es?

Definición de tipo y cómo ligar con éxito

Os voy a dar mi definición, y veremos hasta dónde nos puede llevar. Pues bien:

un tipo es un conjunto de valores y un conjunto de operaciones que se pueden hacer con ellos.

Es decir, un Int sería Z + {+, -, *, mod, etc}.

Por cierto, si alguna vez te has preguntado por qué en Matemáticas a los enteros se les representa con la letra Z, que sepas que proviene de la palabra alemana Zahlen. La próxima vez que le quieras tirar los tejos a un chica en un bar, prueba eso y me cuentas qué tal te ha ido.

Clases de tipos

Había pensado en titular esta sección como «tipos de tipos», pero sería muy evidente que intentaba hacerme el gracioso, así que lo dejaremos en «clases de tipos».

Pues bien, en Swift (y muchos otros lenguajes) sólo hay dos clases de tipos:

  • Tipos completos
  • Tipos incompletos

Que conste que estos son nombres que me he inventado yo solito por razones didácticas. En Swift tienen un nombre un poco distinto y espero que pronto los reconozcas.

Tipos Completos: Tienen toda la información que el compilador necesita y alguna que otra que no le hace falta, pero oiga, mejor ir sobrao que quedarse corto.

Tipos Incompletos: No te lo vas a creer, pero resulta que NO tienen toda la información que el compilador necesita para generar código máquina.

Tipos Completos

En Swift, los tipos completos se caracterizan por dos cosas:

  • Tienen una implementación única y no ambigua
  • Pueden ser instanciados

¿Ejemplos? ¡Por supuesto, maifrén!

let n = 32 // Un entero

let name = “Lucas Grijander” // Una cadena

let z = [3.14, 2.7] // Un array de Doubles

El compilador sabe muy bien cual es y dónde está su implementación. Se pueden instanciar sin problemas.

Hasta aquí, todo bastante evidente. Echemos un vistazo ahora a los protocolos (otro concepto «evidente»).

Protocolos Completos

Vamos a llamar protocolos completos a aquellos protocolos que sólo tienen referencias a tipos completos.

Vamos a ver un ejemplo.

protocol Fooable{

   func foo(a: Int) -> Int

}

Todos los tipos que aparecen en Fooable son tipos completos. En concreto, Int.

Tenemos a dos estructuras que implementan este protocolo, cada una con su versión propia, única y no ambigua de la función foo.

struct Thing: Fooable{

   func foo(a: Int) -> Int {

       return a + 42

   }

}



struct Thong: Fooable{

   func foo(a: Int) -> Int {

       return a % 42

   }

}

Aunque Swift nos deja definir una variable como de “tipo” Fooable, a esa variable le tendremos que asignar una implementación clara y única de la función foo. Por ejemplo:

let t : Fooable = Thing()

En el momento que asignamos a Thing(), ya no hay duda posible para el compilador: en tiempo de ejecución, tendrá que sumar 42.

Colecciones Heterogéneas

Los protocolos completos nos permiten crear algo que recuerda las colecciones heterogéneas de Objective C, pero que en realidad, NO lo son.

Veamos un ejemplo muy sencillo:

let fs : [Fooable] = [Thing(), Thong()]

fs puede parecer una colección heterogénea, pero desde el punto de vista del compilador NO lo es.

Al compilador le falta cierta información sobre Thing y Thong, pero NO la necesita en este contexto.

ejemplo-lenguaje-swift

Por el contrario, cuando usamos un protocolo, parte de esa información se oculta, y sólo se ve aquello que hace falta en un momento dado:
lenguaje-swift-protocol

Por lo tanto, para el compilador, cuando le damos el array [Thing(), Thong()], a él no le parecen cosas distintas, sino que son más de lo mismo: ambos tienen una función que recibe un entero, devuelve otro y se llama foo. Lo demás lo ignora.

Usar Protocolos como si fueran Tipos

Una de las cosas que dicen en el libro de Swift, es que podemos usar protocolos como si fuesen tipos. Luego veremos que esto es una verdad a medias, y por lo tanto, una media mentira: ni los tipos y los protocolos son lo mismo, ni siempre podemos usarlos como si fuesen una misma cosa.

Tipos y Protocolos Completos

Ambos proporcionan toda la información que el compilador necesita.

Tipos completos: Aportan toda la información disponible

Protocolos completos: Solo la información que se necesita en un momento concreto.

Recordemos que los tipos, según nuestra definición, son un conjunto de tipos y un conjunto de operaciones.

tipos-valores-swift

Los protocolos, por el contrario, son tan solo un conjunto de operaciones.

Por lo tanto, usamos tipos cuando nos interesan tanto los valores como las operaciones (comportamiento). Por lo contrario, usamos los protocolos cuando sólo nos interesan las operaciones (el comportamiento).

Mediante los protocolos, podemos simular el «dynamic dispatch» de Objective C (o cualquier otro lenguaje dinámico) de cara al programador, pero sin tener que usarlo en tiempo de ejecución. Es decir, al programador le parece que está tiene en fs un lista de objetos diferentes, mientras que para el compilador son exactamente iguales.

Tipos Incompletos

Todo esto parece muy interesante, e incluso ahora entendemos lo que buscan Swift y otros lenguajes de tipado estático: lograr la flexibilidad de los lenguajes de tipado dinámico, pero sin incurrir en ningún coste en tiempo de ejecución.

Hasta aquí vamos bien, pero seguimos sin saber qué porras significa el error

Protocol can only be used as a generic constraint because it has Self or associated type requirements

y cómo resolverlo.

Para entenderlo, tendremos primero que descubrir qué son los tipos incompletos (repito que esta es una nomenclatura mía). Sin embargo ya os podéis ir haciendo una idea: son los tipos genéricos y los protocolos con un tipo asociado (un protocolo genérico).

En el próximo artículo, veremos qué son exactamente, qué es un tipo asociado, por qué los protocolos con tipo asociado dan tanto trabajo y cómo usarlos sin morir en el intento. Y por supuesto, cómo resolver el error maldito.

¡La semana que viene os lo cuento!

Y si quieres comprender Swift de una vez por todas, desde aquí puedes acceder al curso de Fundamentos Swift 3 con un descuento de 200€. Válido durante 24 horas.

Si tienes algo que deseas compartir o quieres formar parte de KeepCoding, escríbenos a [email protected].