Some: algunas novedades en Swift 5.1 y SwiftUI WWDC 2019

Autor: | Última modificación: 8 de marzo de 2024 | Tiempo de Lectura: 5 minutos
Temas en este post: ,

SwiftUI & some

Una de las grandes novedades para los desarrolladores iOS que ha salido en este WWDC 2019, es sin duda  SwiftUI. Se trata de un nuevo mecanismo para crear interfaces de usuario, mediante un DSL (domain specific language) en Swift. Supuestamente, es una capa de abstracción que nos aísla de otras tecnologías como DynamicType y otras. Es algo relevante por varias razones:
  1. Demuestra que mediante clausuras y la sintaxis especial que tiene Swift para las mismas, es posible crear DSLs en Swift. Los DSLs son una herramienta fundamental en el Domain Driven Development y por lo tanto deben de ser una prioridad para el desarrollador cuyo concepto de buen gusto va más allá de moños y barbas atusadas y aceitadas.
  2. Parece ser un calco de los Widgets de Flutter y su forma de gestionar el estado. Bien podría ser éste el camino que puedan seguir las herramientas para crear interfaces de usuario en el futuro inmediato.
Por supuesto, no todo es orégano o maría en este monte:
  1. Se trata de una beta, y ultimamente Apple parece que llama beta a cualquier cosa que compila sin errores. Habrá que ver si de verdad será usable cuando salga Xcode 11.
  2. El modelo de Flutter es algo cuestionable, por la gran cantidad de «magia» y «cosas ocultas» que conlleva. Esto hace que entender qué pasa de verdad resulte innecesariamente complicado, especialmente al empezar. Como todos los lenguajes declarativos, a menudo sacrifican el rendimiento, y cuando eso se vuelve un problema, toca bajar a un nivel procedural, lo cual suele ser complicado (vease SQL, Prolog y otros).  Si quieres entender un poco mejor qué se trae entre mano Flutter, este artículo lo explica bastante bien, al intentar crear una versión de Flutter para Go.

some

Aunque no sepamos qué futuro tendrá SwiftUI, una cosa sí que tenemos clara: ¿qué porras es ese some que aparece en el código de ejemplo? El código en cuestión es algo tal que asín:
var body : some View {

}
Primero lo evidente:
  • Se declara una variable de tipo View
  • Dicho tipo tiene una clausura asociada.
Hasta aquí vamos bien. ¿Pero qué es eso de some?  En Swift 5 no existe.

some, Swift 5.1 & SwiftUI

En efecto, la palabra clave some es algo nuevo en Swift 5.1 y claramente ha sido añadida para facilitar la vida a los creadores de SwiftUI. Veamos primero qué hace y luego veremos el porqué ha sido usada en SwiftUI.
some limita el polimorfismo.
Mesplico:

Polimorfismo para gente con prisa

Some
Implementan el protocolo «LLevableAlFuego»
El polimorfismo nos permite tratar cosas dispares como si fuesen lo mismo, dado que tienen algo en común. Es decir, si soy cocinero, me importa un cojón de pato si el cacharro que voy a usar es una sartén o una olla (nada de rimas fáciles, por favor): todos esos objetos tienen algo en común:
Los llevas al fuego
Si tienes una función que devuelve un LlevableAlFuego,  quien la llama normalemente le tiene sin cuidado el tipo exacto de lo que recibe. No importa si es sartén u olla, ya que lo único que importa es que se pueda llevar al fuego.

El tipo exacto no importa, excepto cuando sí que importa

Ahora bien, qué pasa si tenemos una función que devuelve sartenes de distintos materiales. Normalmente seguiría sin importarme de qué esta hecha dicha sartén… a no ser que tengas una vitrocerámica de inducción. En este caso, es vital que la sartén tenga un fondo ferromagnético o no funcionará. Por lo tanto, tendríamos 2 implementaciones de la función o método que devuelve sartenes: una para las placas de inducción y otra para el resto (gas, leña, vitrocerámica, etc). Podríamos hacerlo de dos formas:
  1. Confiar en el programador
  2. Confiar en el compilador
Swift sólo confía en el compilador, así que de alguna manera tenemos de indicarle esa restricción. Veamos un ejemplo más real. En vez de sartenes, hablemos de vistas.

SwiftUI & some

Supongamos que estás trabajando en un sótano secreto de Cupertino en un sistemas de vistas. Tim Cook te ha informado que si no es multiplataforma (iOS, macOS, etc), serás despedido en el ascensor. «Acongojado», tomas las siguientes decisiones de diseño para tu nuevo invento, al que vas a llamar SwiftUI:
  1. Defines un protocolo llamado View que determina todo lo que hace una vista.
  2. Te creas varias structs que lo implementan: una para iOS, otra para macOS, etc
  3. Creas algunas funciones que construyen la struct en cuestión. Una AbstractFactory de toda la vida.
El tipo exacto de la struct no importa demasiado al usuario, lo único que le importa es que implementa View. Por lo tanto, el tipo exacto lo escondemos debajo de la alfombra. A eso, en fisno, se le llama un tipo opaco. Algo parecido a esto:
// El protocolo de las Vistas
protocol View{
    func display()->String
    
}

// Los tipos ocultos
fileprivate struct MacOSView: View{
    func display() -> String {
        // en swift 5.1 las funciones que solo tienen una
        // expresión, no necesitan return
        "a macOS view"
    }
}

fileprivate struct IOSView: View{
    func display() -> String {
        "an iOS view"
    }
}

// Factory
func makeView(iOS: Bool)->View{
    if (iOS == true){
        return IOSView()
    }else{
        return MacOSView()
    }
}
Por supuesto que la decisión sobre qué tipo exacto devolver no se haría en tiempo de ejecución con un if, sino en tiempo de compilación mediante directivas del compilador: si estamos compilando para iOS, pues IOSView y así sucesivamente. Sin embargo, independientemente de cuándo se toma la decisión, la idea es la misma. Tal y como está escrita la función makeView, no se puede decir que vaya a devolver el tipo correcto de View. Sólo se sabe que devolverá una View.

Un solo tipo de View. Siempre.

Sería interesante que el compilador pudiese comprobar que makeView sólo devuelve un tipo de View. El que sea, pero siempre el mismo. Algo así como comprobar que cuando tienes una placa de inducción, sólo te darán sartenes ferromagnéticas.
Para eso sirve some. Para limitar el polimorfismo cuando haga falta
Si cambiamos la declaración de makeView incluyendo some, el compilador dará un error:
// El compilador nos dará el siguiente error:
// "Function declares an opaque return type, 
// but the return statements in its body do not 
// have matching underlying types"
// No es que quede muy claro, pero al menos avisa de algo...
func makeView(iOS: Bool)->some View{
    if (iOS == true){
        return IOSView()
    }else{
        return MacOSView()
    }
}
Con esto, lo que te aseguras es que si estás en iOS (por ejemplo), da igual lo que haga la función makeView o cualquier otra a la que llame: si no se devuelve un IOSView, el compilador gritará.

Algunas dudas con respecto a some en Swift

¿Qué pasa si la función devuelve siempre el mismo tipo, pero el equivocado?
Es decir, si en iOS, siempre devuelve el mismo tipo oculto… MacOSView. Te aguantas, hamijo. Lo único que some te asegura, es que siempre devolverá el mismo tipo, no que sea el correcto.
¿¿¡Y entonces por qué coño no devuelvo IOSView o MacOSView y me dejo de historias!??
Porque quiero que el mismo código funcione en ambas plataformas.
some no impide que metas la pata, solo reduce su probabilidad.
De todas formas, como intentes compilar una MacOSView en iOS (o vice-versa) seguro que casca por algún lado.

some en Swift más allá de SwiftUI

Bueno, pues ya sabemos de dónde sale some, qué hace, y por qué viene de la mano de SwiftUI.  Ahora bien, ¿lo usaremos en nuestro propio código? Creo que es poco probable. Responde a una necesidad muy específica de frameworks multiplataforma y en general el polimorfismo no restringido es mucho más útil y común. ¿Se te ocurre algún uso en tu código?