***Cette pépite est la deuxième d’une série de 3. Elles forment la base du classroom « From Existential to Opaque return types » qui s’est tenu pendant la FrenchKit 2019***
Qu’est-ce que le Type Erasure ?
Type Erasure ou en français l’effacement du type, est l’action d’effacer ou supprimer le type d’une variable.
Pourquoi avons-nous besoin d’effacer le type ?
Swift est un langage ‘type-safe’, ce qui veut dire que swift nous aide à être clair sur les types que l’on peut utiliser. Le but est de réduire le nombre de bugs et d’accidents au moment de passer une variable avec un type different du type attendu.
Ceci dit, on voudrait parfois que le système des types soit moins précis pour être capable de contourner quelques limitations du langage.
Une de ces limitations est l’impossibilité d’utiliser les Protocoles avec Associated Types (PATs) comme une contrainte générique.
Pour vous expliquer le pourquoi de cette limitation, prenons par exemple ce protocole :
protocol Xebian { associatedtype XebiaProductType func work() -> XebiaProductType }
Si on voulait définir une constante firstXebian, le compilateur nous reprocherait qu’il n’est pas possible d’utiliser ce protocole comme contrainte vu qu’il possède un ‘associated type’ qui n’a pas encore été défini. L’associated type est défini juste au moment de l’implementation du protocole et pas avant.
C’est dans ce type de situation que ‘Type Erasure’ vient à notre secours.
Comment Type Erasure est implémenté ?
Disons que nous avons un couple de Xebians, un FrontEndXebian et un iOSXebian.
struct XebiaProduct {} struct FrontEndXebian: Xebian { typealias XebiaProductType = XebiaProduct func work() -> XebiaProduct { return XebiaProduct() } } struct iOSXebian: Xebian { typealias XebiaProductType = XebiaProduct func work() -> XebiaProduct { return XebiaProduct() } }
Si on veut les faire travailler et on les recueille dans un array alors on obtiendrait la même erreur de compilation qu’en haut.
C’est grâce à l’aide d’un wrapper que nous allons être capable d’effacer le type concret d’une instance.
Voici le wrapper de notre protocole Xebian.
struct AnyXebian<T>: Xebian { private let _work: () -> T init<X: Xebian>(_ xebian: X) where X.XebiaProductType == T { _work = xebian.work } func work() -> T { return _work() } }
3 points importants à retenir de ce wrapper :
- La valeur de T sera déterminée par le système de génériques swift ;
- Grâce à ce même système, notre fonction init n’acceptera que des Xebians qui ont un XebiaProductType égal à cette valeur T ;
- Ce wrapper doit se conformer au protocole et pour cela on a dû garder une référence de la fonction pour l’utiliser plus tard quand la fonction du wrapper sera appelée.
Cela nous permet de définir une variable comme anotherXebian et recueillir les Xebians dans un array pour ensuite pouvoir les faire travailler.
var anotherXebian: AnyXebian<XebiaProduct> another = AnyXebian(iOSXebian()) let xebians = [AnyXebian(iOXebian()), AnyXebian(FrontEndXebian())] xebians.map { $0.work() }
Cela n’est qu’une des implementations de Type-Erasure. Il y en a d’autres comme celle utilisé par AnySequence et AnyHashable dans la librairie standard swift
Pouvons-nous attendre quelque chose de mieux ? Est-ce que la sortie de Swift 5.1 amène avec elle la réponse a tous nos problèmes ?
La réponse à ces questions, vous pourrez la retrouver dans une prochaine pépite où nous allons parler de « Opaque return types« .