I. Introduction▲
J'ai joué avec Swift depuis que Apple l'a annoncé à la WWDC de 2014. J'ai eu la chance d'être là, et j'ai passé une bonne partie de mon temps dans les laboratoires, à coder et à poser des questions aux ingénieurs qui l'ont créé. Il n'y a rien comme être parmi les premiers à mettre la main sur un langage, et parmi les premiers à l'explorer. Même si vous trouvez immédiatement les moyens de tout casser.
II. Prémisse▲
Dans le passé, j'ai aimé deux aspects des langages de programmation : le code-comme-données — comme en Lisps — et les systèmes forts de typage, comme en Haskell et Scala. Par coïncidence, ce sont des langages fonctionnels. Je dis par coïncidence, parce que leur aspect fonctionnel n'est pas la principale raison pour laquelle je les aimais. Ce que j'aimais était leur capacité de simplifier une partie du problème général de la programmation.
Lisps simplifie les détails de la syntaxe et de l'implémentation ; Haskell et Scala simplifient la rectitude logique. La spécification d'un système de type dès le départ permet à un développeur d'éliminer plusieurs grandes classes d'erreurs courantes et de se concentrer sur la logique interne du problème, logique façonnée par les types qu'elle définit et aidée par un compilateur qui l'avertit si elle tente de travailler d'une manière incohérente au sein de son système de type.
Ces deux aspects ne sont pas mutuellement exclusifs, mais en général, au maximum un se retrouve dans un langage. Swift a opté pour des génériques et un vérificateur statique de type. Et j'ai commencé à essayer de mettre en œuvre les choses.
III. Premier essai▲
La première chose que j'ai remarquée, c'est qu'il n'y a aucun concept de foncteur en Swift. Longue histoire courte, un foncteur est un type de données pour lequel une fonction de mappage a du sens. Dans un environnement Swift, il ressemblerait à quelque chose comme ceci :
protocol
Functor
{
typealias
T
func
map
<
P
>(
mappingFunction
:
T
->
P
)
->
Self
<
P
>
}
Si cela ressemble à une fonction map normale pour un tableau, c'est parce que c'est le cas : les tableaux sont des foncteurs. Mais les dictionnaires peuvent aussi être des foncteurs. Supposons que vous avez un Dictionary<Int, String> qui fait le mappage entre les numéros de maillot et les noms des joueurs d'une équipe de football. Si vous avez une fonction String -> GuidePhonetique qui mappe des chaînes à des prononciations en anglais, vous pouvez utiliser une fonction de mappage pour obtenir un Dictionary<Int, GuidePhonetique> des numéros de maillot à des guides phonétiques pour les noms des joueurs. La fonction de mappage ressemblerait à ceci :
extension
Dictionary
<
K
,
V
>
{
func
map
<
P
>(
mappingFunction
:
V
->
P
)
->
Dictionary
<
K
,
P
>
{
var
newDict
:
Dictionary
<
K
,
P
>
=
[:]
for
(
key
,
value
)
in
self
{
newDict
[
key
]
=
mappingFunction
(
value
)
}
return
newDict
}
}
Et son utilisation ressemblerait à ceci :
let
numerosVersNoms
:
Dictionary
<
Int
,
String
>
=
…
// un dictionnaire
let
guidePhonetique
:
String
->
GuidePhonetique
=
…
// une fonction
let
numerosVersGuides
=
numerosVersNoms
.
map
(
GuidePhonetique
)
Assez simple. Et Functor permet à tout type qui l'adopte de prendre en charge cette opération — et donc d'informer les utilisateurs qu'il le fait.
Sauf qu'il y a un hic. Le protocole que j'ai spécifié ci-dessus n'est pas possible en Swift. Essayez-le : il échouera, parce que vous ne pouvez pas paramétrer les fonctions définies dans les protocoles ; en d'autres termes, le P n'est pas supporté. Déception.
IV. Deuxième essai▲
Bon, donc les foncteurs n'ont pas fonctionné. Ensuite, j'ai essayé de réimplémenter en Swift l'API BFTask de Bolts. C'est une candidate de premier choix : la fonctionnalité est puissante, mais l'utilisation standard est souvent encombrée par la gestion des erreurs. Ou encore pire, elle ne l'est pas et les erreurs sont ignorées. J'ai essayé d'utiliser les génériques pour faire mieux et retravaillé l'API. À la base, j'ai créé une énumération Result comme ceci :
/*
Une enveloppe autour du résultat spécifique renvoyé par un calcul. Permet le traitement cohérent d'erreurs,
y compris le court-circuit des calculs qui sont dépendants les uns des autres.
*/
enum
Result
<
ResultType
>
{
/*
Décrit un aboutissement normal : le calcul a été finalisé et a
abouti à un résultat.
*/
case
Normal
(
ResultType
)
/*
Décrit une erreur, le calcul a échoué.
*/
case
Error
(
String
)
/*
Décrit l'annulation de la tâche par l'utilisateur.
*/
case
Cancelled
}
Avec l'utilisation de deux méthodes de mappage un peu comme celle que j'ai montrée plus haut, je peux abstraire la gestion des erreurs et l'annulation et laisser la focalisation de l'utilisateur sur la séquence de tâches qu'il voulait exécuter. Mais, vous l'aurez deviné, cela ne fonctionne pas.
Le problème ici n'est pas si grave que tout à l'heure : le langage prend en charge la compilation. Le compilateur, cependant, ne le fait pas. (Encore.) Si vous collez cela dans un fichier Swift, vous découvrirez que le compilateur se plante et vous indique que l'IR (la représentation intermédiaire) n'est pas encore mise en place pour ce type de construction. Déception à nouveau.
V. Troisième essai▲
Enfin, j'ai décidé d'apprendre quelque chose de nouveau. À la suggestion de mon frère (alors c'est vraiment de sa faute), j'ai décidé de mettre en œuvre en Swift quelque chose qui ressemble aux Streams de Scala. Les flux sont des séquences paresseuses qui calculent les éléments de 0 à N lorsque l'élément N est demandé. Pour éviter le recalcul, elles les mémoïsent ensuite ; au moins, la mise en œuvre en Scala sur laquelle je fondais mon travail le fait. Voici donc une esquisse d'un type Stream en Swift :
class
Stream
<
P
>
{
var
memoizedElements
:
P
[]
init
(
initialElements
:
P
[]
=
[])
{
self
.
memoizedElements
=
initialElements
}
}
Encore une fois, assez simple : un flux contient une variable tableau pour accueillir memoizedElements et peut accepter des éléments précalculés (ce qui est utile si nous voulons faire des opérations comme le mappage sur des flux sans recalculer chaque fois tout le flux).
Quoi ? Vous pensez que cela ne fonctionne pas ? Je pourrais avoir un peu moins de négativité ici. Je pourrais. Sauf que je ne peux pas. Le tableau par défaut provoque un autre plantage du compilateur, différent, cette fois-ci — un segfault tous azimuts. Déception. Déception, déception, déception.
(Mise à jour : @mkonutgan souligne que je devrais préciser : la cause de l'échec est le tableau vide passé comme paramètre par défaut. @maxpow4h a suggéré sur HN une solution de contournement.)
VI. Analyse des résultats▲
Donc, au bout de trois tentatives j'ai rencontré trois situations qui brisent le compilateur de point de vue du système de type. L'une d'elles n'était pas prise en charge par le langage, point. La deuxième est théoriquement prise en charge, mais pas encore mise en œuvre. La troisième crée des erreurs de segmentation du compilateur Swift. Quel genre de demandes insensées ai-je placés à ce pauvre programme ?
Pour moi, les choses que j'ai essayées n'étaient pas insensées ; elles semblaient des choses évidentes à essayer. Et quand j'ai comparé l'API Promises de Scala à mon API Thunderbolts, j'ai été agréablement surpris par les similitudes — je n'essaie pas des façons bizarres (révolutionnaires...) de faire les choses ; je redécouvre le découvert. Et pourtant, les auteurs de Swift n'ont évidemment pas utilisé ce genre de constructions dans leur propre code.
C'est fascinant. C'est le genre de chose qui me donne envie de spéculer. Mais le problème avec Apple, c'est que c'est une boîte noire — il peut y avoir n'importe quel nombre de raisons pour lesquelles ils ont choisi de limiter ces fonctionnalités et c'est une insulte aux gens qui connaissent beaucoup plus de langages que moi de suggérer qu'ils ne comprennent pas ce que j'essaie de faire. Alors je suis simplement resté avec un point d'interrogation.
VII. Conclusions▲
Mis à part ce point d'interrogation, j'aime Swift. J'adore ce qu'il me permet déjà de faire et j'aime ce dont les erreurs de compilateur me permettent d'espérer qu'il fera, une fois qu'il sera stable. Et vu combien les développeurs Swift sont actifs sur les forums d'Apple, j'ai même de l'espoir pour les choses qu'il me permettra de faire quand ils l'amélioreront. Mais jusqu'à ce que ces choses arrivent, je dois l'admettre : je me sens comme travailler avec un vase en porcelaine fine que je pourrais briser en soufflant dessus. Mais combien c'est important, après tout, d'avoir quelque chose à briser.
VIII. Remerciements Developpez▲
Nous remercions Alexandros Salazar de nous avoir aimablement autorisés à publier son article. Cet article est une traduction autorisée dont le texte original peut être trouvé sur http://nomothetis.svbtle.com. Nous remercions aussi Mishulyna pour sa traduction, LeBzul pour sa relecture technique ainsi que Claude Leloup pour sa relecture orthographique.