Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1536 lines (1134 sloc) 67.5 KB

Introduction

Voici la présentation que j'ai donnée aux Journées Perl 2018, le 18 mai 2018. J'y ajoute certaines réflexions que l'assistance m'a faites lors de l'exposé.

Je cherchais un projet utilisant Perl 6 et MongoDB et qui puisse intéresser l'auditoire des Journées Perl. J'ai donc exhumé des tréfonds de ma mémoire une idée consistant à écrire un programme jouant à l'As des As. Ce n'est que plus tard que j'ai constaté que ce projet pouvait bénéficier de l'utilisation de Bailador (la version Perl 6 de Dancer2) et que cela me permettait de célébrer le centenaire de la création de la RAF (1er avril 1918) et de la mort de Manfred von Richthofen (21 avril 1918) ou, plus approximativement, la disparition de Georges Guynemer (11 septembre 1917) et l'armistice (11 novembre 1918).

L'As des AS

Rappel

L'an dernier, j'ai présenté un système de reconnaissance de caractères basé sur une interface entre un humain (fournissant le moteur de reconnaissance de caractères) et une machine (pour la base de données). J'aurais pu le présenter autrement.

Comparaison entre une cellule l (Lima) et un glyphe 1 (unité)

Copie d'écran personnelle. Les conditions de licence sont les mêmes que pour le texte.

-- Maître, cette cellule contient un chiffre « 1 ».

-- Petit Scarabée, c'est la lettre « l ».

-- Ah, maître, je comprends la nature du « l ».

Présenté comme cela, vous avez reconnu, c'est du machine learning ou plus précisément du supervised machine learning (et ça fera bien dans mon CV).

Cette année

Récemment, nous avons appris que Google avait fait du supervised machine learning avec Google Alpha Go. Puis nous avons appris qu'ils avaient fait une autre expérience sur le jeu de Go, Google Alpha Go Zero, basé sur un mécanisme d'auto-apprentissage, ainsi que l'équivalent pour les échecs, Google Alpha Chess Zero. Le système ne connaît que les règles du jeu et le fait qu'une position finale est gagnante ou perdante. Le système joue un grand nombre de parties contre lui-même, analyse les résultats et en déduit les positions et coups avantageux et les positions et coups foireux.

Cette idée ne date pas de 2017 ou 2018, je l'ai déjà rencontrée dans les années 1970 avec le tic-tac-toe. Et depuis longtemps j'avais moi-même un projet de « machine self-learning », que je vous présente ci-dessous. Et c'était le moment rêvé de réaliser ce projet cette année, car 2018 c'est le centenaire de la création de la RAF, de la mort au combat de Mannfred von Richthofen et de l'armistice.

Digression sur les précurseurs des liens hypertextes

Quand a-t-on commencé à numéroter les pages des livres (ou des rouleaux de papyrus) ? Quand a-t-on écrit pour la première fois "cf. page n" ? Quand a-t-on pensé à ajouter un index des mots importants en fin d'ouvrage ? Quand a-t-on écrit un ouvrage qui utilise de façon intensive les renvois à une autre page ?

À la suite des travaux des naturalistes du XVIIe siècle (Jussieu, Buffon, Cuvier, etc) on a commencé à publier des flores, des livres décrivant l'ensemble des végétaux d'une région ou d'un biotope. Mon père m'en a montré une datant des années 1950 et qui était organisée sous la forme d'une série de questions-réponses :

Comment sont les nervures des feuilles ? Si elles sont parallèles, voyez page 17. Si elles forment un réseau arborescent avec une nervure centrale, des nervures primaires se détachant de la nervure centrale, des nervures secondaires se détachant d'une nervure primaire et ainsi de suite, voyez page 33.

Page 17 et page 33 vous aviez d'autres questions, d'autres réponses et d'autres renvois.

Années 1960 (je crois) : on m'a raconté qu'à cette époque IBM avait composé des manuels de dépannage sur le même principe :

Si de la fumée sort de l'unité de disque, voyez page 17. Si cela fait « crrr crrr crrr » voyez page 33.

(Le texte est évidemment apocryphe, mais le principe est conservé.)

Dans les années 1970, j'ai vu à la télévision le sketch d'un duo d'humoristes sur « le dictionnaire le plus petit du monde ». Ce qui prend de la place dans un dictionnaire ce sont les définitions. Le personnage de l'un des humoristes avait pu imprimer un dictionnaire au format de poche juste en éliminant les définitions. Il expliquait à son interlocuteur avec un exemple ressemblant à :

Clown : voir Cirque.
Cirque : voir Cercle.
Cercle : voir Club
Club : voir Assemblée
Assemblée : voir Parlement.
Parlement : voir Député.
Député : voir Politicien.
Politicien : voir Clown.

Les Livres dont vous êtes le héros, Le Sorcier de la Montagne de Feu

En 1982, est paru un livre d'un genre nouveau, Warlock of the Firetop Mountain, bientôt traduit en français par Gallimard Folio Junior sous le titre « le Sorcier de la Montagne de Feu », le premier de la série « les Livres dont vous êtes le héros ».

Paragraphe 1. Vous entrez dans une auberge. Au fond de la salle, vous voyez l'aubergiste à son comptoir en train d'essuyer des verres. À gauche, dans un recoin sombre, un homme seul à une table, la tête masquée par un capuchon noir. Sur la droite, une demi-douzaine de nains des montagnes boivent de la bière en rigolant bruyamment. Que faites-vous ? Vous vous adressez à l'aubergiste, allez au paragraphe 17. Vous vous asseyez à côté de l'homme encapuchonné, allez au paragraphe 33. Vous vous adressez aux nains en leur disant « Salut les ornements de jardin ! » allez au paragraphe 52.

J'en ai eu cinq, dont deux édités par Gallimard et trois par Solar, il m'en reste quatre.

Quatres livres dont vous êtes le héros

Photo personnelle. Les conditions de licence sont les mêmes que pour le texte.

L'As des As

En 1981, un dénommé Alfred Leonardi a déposé un brevet pour un nouveau type de jeu, mis au point avec Douglas Kaufman. Au lieu de réunir les deux joueurs sur une même carte où ils poussent des pions, chaque joueur est muni d'un livret.

Exemple de partie

Photo prise par Chris Norwood et publiée sur Boardgame Geek. Licence Creative Commons Attribution 3.0 Unported, CC BY 3.0.

Aperçu des règles

Le brevet donne un exemple de tour de jeu. Les deux joueurs commencent le tour page 1. Dans les deux livrets, cette page montre que les deux avions ont le même cap et que le DR1 est dans les 4 heures du Camel, c'est-à-dire que le Camel est dans les 10 heures du DR1.

Livret allemand page 1

Livret britannique page 1

Copies d'écran provenant du brevet USP 4,378,118, dans le domaine public depuis 2001.

En bas de la page, vous pouvez remarquer une série de flèches et une série de nombres, des numéros de page. Chaque joueur choisit une flèche et annonce le numéro de page correspondant à son adversaire. Par exemple, le joueur allemand choisit la flèche la plus à gauche dans la page et annonce :

-- Je t'envoie en page 8.

Page 1, flèche pointant vers la page 8

Page 1, flèche pointant vers la page 48

Copies d'écran provenant du brevet USP 4,378,118, dans le domaine public depuis 2001.

Simultanément, le Britannique choisit la manœuvre en dessous de « EF » « Cruising Left » et répond :

-- Et toi, tu vas page 48.

Le joueur allemand ouvre son livret page 48 et applique la même manœuvre que précédemment, ce qui donne la page 96. De même, le joueur britannique ouvre son livret page 8, repère la manœuvre choisie et obtient le même numéro de page, 96.

Livret allemand page 48

Livret britannique page 8

Page 48, flèche pointant vers la page 96

Page 8, flèche pointant vers la page 96

Copies d'écran provenant du brevet USP 4,378,118, dans le domaine public depuis 2001.

La page finale du tour est donc la page 96. Et comme vous pouvez le voir, non seulement l'As des As est un précurseur des liens hypertextes, mais c'est aussi un précurseur des jeux de tir en vision subjective (First Person Shooters en anglais), une douzaine d'années avant Wolfenstein 3D et Doom.

Livret allemand page 96

Livret britannique page 96

Copies d'écran provenant du brevet USP 4,378,118, dans le domaine public depuis 2001.

Quant au mécanisme des tirs, c'est très simple. Vous commencez avec 12 points de vie. Chaque fois que vous aboutissez sur une page finale où vous recevez des pruneaux de l'avion ennemi, vous perdez 1, 2 ou 4 points de vie, selon la page finale. On ne tient pas compte des pages intermédiaires. Quand votre total de points de vie est négatif ou nul, votre avion est abattu.

Dissection du mécanisme

Avec ce mécanisme, les pages finales sont toujours identiques. Cela émerveille les joueurs qui découvrent le jeu, mais cela peut s'expliquer simplement. Oublions les cieux au-dessus de Poelcapelle et de Vaux-sur-Somme, et imaginons un parking de supermarché. Toutes les voitures sont garées à proximité du bâtiment et il y a donc un large espace libre un peu plus loin. Dans cet espace libre, se trouvent une Mini Austin et une VW Coccinelle disposées ainsi :

Position de départ des deux voitures

Livret britannique page 1

Image personnelle. Les conditions de licence sont les mêmes que pour le texte. Copie d'écran provenant du brevet USP 4,378,118, dans le domaine public depuis 2001.

Cela correspond à la page 1 des deux livrets.

Maintenant, imaginons que la VW fasse ce mouvement pendant que la Mini reste immobile.

Mini dans sa position de départ et VW dans sa position d'arrivée

Livret britannique page 8

Image personnelle. Les conditions de licence sont les mêmes que pour le texte. Copie d'écran provenant du brevet USP 4,378,118, dans le domaine public depuis 2001.

On obtient alors la disposition de la page 8. À ce moment-là, la Coccinelle s'arrête et l'Austin démarre en faisant ce mouvement

Position d'arrivée des deux voitures

Livret britannique page 96

Image personnelle. Les conditions de licence sont les mêmes que pour le texte. Copie d'écran provenant du brevet USP 4,378,118, dans le domaine public depuis 2001.

Le résultat est celui de la page 96.

Position d'arrivée des deux voitures

Livret britannique page 96

Image personnelle. Les conditions de licence sont les mêmes que pour le texte. Copie d'écran provenant du brevet USP 4,378,118, dans le domaine public depuis 2001.

Deuxième étape de l'expérience. Les deux voitures se remettent dans la disposition correspondant à la page 1.

Position de départ des deux voitures

Livret allemand page 1

Image personnelle. Les conditions de licence sont les mêmes que pour le texte. Copie d'écran provenant du brevet USP 4,378,118, dans le domaine public depuis 2001.

Puis c'est la Mini qui bouge la première pendant que la VW reste immobile. On aboutit alors à la disposition de la page 48.

VW dans sa position de départ et Mini dans sa position d'arrivée

Livret allemand page 48

Image personnelle. Les conditions de licence sont les mêmes que pour le texte. Copie d'écran provenant du brevet USP 4,378,118, dans le domaine public depuis 2001.

Et lorsque la Coccinelle effectue son mouvement, on aboutit à la configuration de la page 96.

Position d'arrivée des deux voitures

Livret allemand page 96

Image personnelle. Les conditions de licence sont les mêmes que pour le texte. Copie d'écran provenant du brevet USP 4,378,118, dans le domaine public depuis 2001.

Troisième étape. Les voitures se remettent dans la position de la page 1, puis elles effectuent simultanément leur mouvement. Comme les deux fois précédentes, on obtient la disposition de la page 96.

Position d'arrivée des deux voitures

Livret britannique page 96

Livret allemand page 96

Image personnelle. Les conditions de licence sont les mêmes que pour le texte. Copie d'écran provenant du brevet USP 4,378,118, dans le domaine public depuis 2001.

Ainsi donc, il est équivalent de faire les mouvements simultanément ou de les faire séquentiellement dans un ordre ou dans l'autre. Et ce, aussi bien sur un parking de supermarché que dans l'espace aérien de la Picardie ou des Flandres.

Cas particulier, la page 223.

Si vous consultez certains fichiers d'aide fournis par Boardgame Geek, vous verrez que le jeu de base sur une grille de 37 hexagones, permettant de générer 222 pages, représentant toutes les positions relatives des deux avions du moment qu'ils sont à une distance inférieure ou égale à trois hexagones. Et que se passe-t-il si la distance est supérieure ? Si, du fait de leurs manœuvres, les avions se retrouvent plus loin que cette distance limite, le livret affiche une page spéciale, la page 223 qui représente des nuages derrière lesquels l'ennemi a disparu. Même si cette page montre le ciel en avant de l'avion, il est possible que l'ennemi ait disparu sur un côté ou derrière.

Si un seul joueur a une page intermédiaire à 223, on utilise la page intermédiaire de l'autre pour obtenir la page finale. Impossible dans ce cas de faire la vérification présentée, expliquée et démythifiée au paragraphe précédent. Un tel cas de figure se produit par exemple lorsque les deux avions se poursuivent à grande distance. Supposons que nous soyons page 180, où le Camel est à longue distance dans les onze heures du DR.1 et supposons que les deux avions avancent tout droit à vitesse moyenne. Si l'on applique d'abord le mouvement du Camel, le DR.1 le perd de vue temporairement et la page intermédiaire est la page 223, sur laquelle aucune manœuvre n'est indiquée. En revanche, si l'on applique d'abord la manœuvre du DR.1, la page intermédiaire est la page 60 où le Camel est à distance moyenne dans les 10 heures du DR.1. Puis on applique le mouvement du Camel, ce qui donne la page 180. Il n'est plus possible de vérifier que la page finale est la même selon les deux déterminations, mais tant pis.

D'un autre côté, si les deux pages intermédiaires sont 223, ou bien si la page finale est 223, alors les avions se sont perdus de vue. Chaque joueur décide alors de continuer le combat ou de s'éclipser. Si les deux avions fuient, c'est un match nul. Si les deux avions décident de continuer, le combat reprend sur une page avec une position neutre, chaque avion conservant les dégâts déjà subis. Si un seul avion fuit, il accorde une demi-victoire à son adversaire, mais c'est parfois préférable, plutôt que de prendre le risque de se faire descendre et d'accorder ainsi une victoire complète à son adversaire.

Autre cas particulier, la poursuite

Lorsqu'un avion est dans le quadrant arrière d'un autre (4 à 8 heures) et que son cap est identique ou presque, alors on dit qu'il poursuit l'autre avion. C'était le cas sur la page 1 présentée ci-dessus. Pour des raisons pédagogiques, j'ai préféré faire le silence sur cette particularité. La simultanéité du choix des manœuvres est partiellement annulée. La séquence est la suivante

1 L'avion poursuivi choisit une manœuvre.
2 Il indique au poursuivant s'il s'agit d'une manœuvre vers la gauche, d'une manœuvre vers la droite ou d'une manœuvre en ligne droite vers l'avant. Il ne donne pas le numéro de la page intermédiaire.
3 L'avion poursuivant choisit une manœuvre.
4 Les deux joueurs annoncent simultanément les pages intermédiaires.
5 Les deux joueurs déterminent la page finale.

Considérations diverses

Lors d'un tour de jeu, vous devez garder à l'esprit :

1 le numéro de la page initiale,
2 la manœuvre choisie
3 la page intermédiaire que vous communiquez à votre adversaire,
4 la page intermédiaire que votre adversaire vous indique,
5 la page finale du tour,
6 le nombre de points de dégâts encaissés,
7 optionellement le nombre de points de dégâts infligés à votre adversaire.

On dit que la mémoire à court terme d'un humain contient 7 ± 2 informations (à part les serveurs de café, qui dépassent largement cette valeur). À moins d'être dans la catégorie inférieure, il est possible de jouer à l'As des As sans avoir besoin de noter quoi que ce soit, ni de déplacer des marqueurs sur des tableaux. Il est possible de jouer à la plage, il est possible de jouer à côté de la machine à café, il est possible de jouer dans une salle d'attente, il est possible de jouer dans le bus. Et si vous êtes dans une catégorie supérieure pour cette faculté, vous pouvez même jouer dans le bus tout en gardant un œil sur les arrêts restants avant votre destination.

Lorsque j'ai découvert le jeu, je me suis dit que finalement, ce jeu consistait à combiner des nombres (les numéros de page) et des symboles (les manœuvres) pour obtenir d'autres nombres. L'image figurant sur chaque page a pour but d'aider le joueur humain à choisir le bon symbole de manœuvre, du moment que ce joueur a une idée suffisamment précise du vol d'un avion et qu'il connaît les Dicta Boelke, les conseils de Sailor Malan, l'ouvrage No Guts, No Glory de « Boots » Blesse et autres livres du même genre. Il est possible d'enlever tout cet habillage aéronautique pour ne conserver que le mécanisme abstrait reposant sur un automate à états finis éventuellement mâtiné d'une dose de fonction random. D'où l'idée d'un programme jouant à ce jeu sans rien connaître du contexte réel, mais se basant uniquement sur une analyse statistique des parties passées.

Extensions

Deux extensions sont sorties avec des avions plus récents (Powerhouse : Spad XIII contre Fokker D.VII) ou plus anciens (Flying Machines Airco DH2 contre Fokker E.III). Également, il y a la version Seconde Guerre Mondiale (Wingleader, P-51 Mustang contre FW190), la version Guerre Froide (Jet Eagles, F-15 Eagle contre MiG-29 Fulcrum) et même la version Guerre des Étoiles (Star Wars : Starfighter Battle Books, X-Wing contre TIE-Fighter, extension publiée par West End Games).

À remarquer que toutes ces versions ont la même pagination. Dans tous les livrets, la page 96 montre le « méchant » derrière le « gentil », à distance moyenne, avec le même cap et en train de lui tirer dessus. Il est possible de jouer avec un livret « gentil » d'un jeu et un livret « méchant » d'un autre jeu, quitte à faire combattre un X-Wing contre un Fokker E.III !

Juste quelques hics, l'échelle des distances n'est pas la même d'un jeu à l'autre et les points de dégâts n'ont rien à voir. Il vous faudra une bonne dose de suspension délibérée de l'incrédulité pour les parties de ce type !

Attention au sujet de Wingleader : il existe un autre jeu beaucoup plus récent du même nom chez GMT. Si vous êtes intéressé par le jeu décrit et que vous voulez l'acheter par correspondance, vérifiez bien qu'il s'agit du bon jeu.

L'As des As, les jeux de la Première Guerre Mondiale

L'As des As, Seconde Guerre Mondiale et Guerre Froide

Photos personnelles. Les conditions de licence sont les mêmes que pour le texte.

Page 96 pour Jet Eagles, livret 'Red Force'

Page 96 pour Jet Eagles, livret 'Blue Force'

Photos personnelles reprenant une page dans les deux livrets de Jet Eagles, copyright © 1990 Nova Game Designs, Inc.

Toutes ces variantes proposent également des règles avancées, pour l'altitude par exemple, ou le jeu 2-contre-2, ou encore pour les radars et les missiles dans le cas de Jet Eagles. Hélas, ces règles avancées font perdre la compatibilité avec la capacité 7 ± 2 de la mémoire à court terme. À part une règle sur la compatibilité entre deux manœuvres successives d'un même avion, je n'ai pas l'intention de les prendre en compte dans mes programmes.

Il y a également une extension Balloon Busters, où l'on fait du 2-contre-1. Le joueur allemand contrôle un ballon d'observation (assez passif) plus un canon anti-aérien, contre un avion britannique.

Autres extensions dans la même lignée, mais impossibles à mélanger avec les premières : Shootout at the Saloon traduit chez Gallimard avec le titre Le shérif et le hors-la-loi, Dragons Riders of Pern traduit par Gallimard avec le titre les Maîtres des Dragons et une série dont je n'avais jamais entendu parler avant de rédiger ces explications, Lost Worlds.

Le Shérif et le Hors-la-loi

Le shérif apercevant le hors-la-loi

Le hors-la-loi apercevant le shérif

Photos personnelles reprenant « Le Shérif et le Hors-la-loi », Copyright © 1982 Nova Games Design Inc, copyright © 1986 Emithill Limited, copyright © 1986 Gallimard pour la traduction française et pour les illustrations de couverture.

Mai 68, première édition

Mai 68, deuxième édition

Photos personnelles reprenant le jeu "Mai 68" de F. Nédelec et D. Vitale, Copyright © 1980, 1982, 1988

Oups ! Qu'est-ce que cela vient faire dans cet exposé sur le centenaire de la RAF et sur le centenaire de l'armistice ? Est-ce que Danny le Rouge a évincé le Baron Rouge ?

Pour en revenir au combat aérien, puisque le brevet est tombé dans le domaine public, n'importe qui peut publier des extensions dans la lignée :

  • Apache AH-64 vs Mi-24 Hind

  • Quidditch : Griffindor vs Slytherin

  • Superman vs Ironman

  • Épervier vs drone de loisir

C'est d'ailleurs cette dernière extension que j'ai choisie pour disposer de données de tests dans mes programmes. J'étais réticent à reprendre les caractéristiques des avions de l'un des jeux existants, car si le brevet est expiré, le copyright ne l'est pas.

Description des programmes

Il y a deux grandes parties dans le projet. Tout d'abord, la préparation, consistant à regénérer plutôt que recopier les enchaînements entre pages et manœuvres. Puis il y a le jeu proprement dit où des programmes combattent l'un contre l'autre.

Préparation

J'ai commencé par programmer cette partie, bien sûr. Je l'ai faite avant d'initialiser le dépôt Git. Du coup, les programmes sont dans un répertoire extérieur. Ces programmes ont été réintégrés après coup dans le dépôt Git, avec une phase de nettoyage. Vous ne verrez donc pas mes tout premiers pas en Perl 6.

Les programmes de ce dossier tiennent compte de la géométrie sous-jacente des pages. Dans un premier temps, en se basant sur un seul livret de la série, on cherche à retrouver l'interprétation de chaque page sur la grille hexagonale implicite. On commence par charger la base de données avec une page codée « en dur », plus la page spéciale 223. Ensuite, le processus est itératif. On prend une page déjà référencée dans la base de données, puis on fournit au programme la liste des associations manœuvres → page d'arrivée de cette page de départ. Cela permet de définir à quel hexagone et à quelle orientation ces pages d'arrivée correspondent. Au bout d'un certain temps, on a passé en revue toutes les pages du livret et donc on connaît la grille hexagonale entière.

Dans un deuxième temps, on initialise un fichier JSON avec les manœuvres d'un avion, on croise avec la base de données de la première étape, et on génère un livret donnant la liste des associations page de départ + manœuvre → page d'arrivée. Ce livret est codé sous la forme d'un fichier JSON avec en prime un fichier HTML.

Jeu

Le jeu entre humains est prévu pour deux joueurs sans arbitre. Pour les programmes, j'ai préféré utiliser un programme arbitre, ou plutôt un processus arbitre, communiquant avec deux processus joueurs. Les deux processus joueurs fonctionnent avec le même programme Perl 6, mais avec des arguments d'appel différents.

Pour rester dans la même optique que Google Alpha Go Zero et Google Alpha Chess Zero, le programme joueur n'a aucune connaissance intrinsèque des mécanismes de vol et des doctrines de combat aérien. Il se contente de recevoir une liste de manœuvres de l'arbitre, d'en sélectionner une et de renvoyer la réponse à l'arbitre.

Le programme joueur fonctionne en deux modes. Il y a le mode entraînement où le choix est totalement aléatoire. Et il y a le mode combat où le programme joueur extrait de la base de données les situations analogues pendant les parties précédentes pour savoir ce qui a bien marché et ce qui n'a pas donné de bons résultats. Il attribue des probabilités plus ou moins élevées aux différentes manœuvres proposées par le programme arbitre et tire au hasard une manœuvre, en tenant compte de la loi de probabilité attribuée.

J'ai choisi une méthode aléatoire pour le choix des manœuvres, de manière à éviter d'aboutir à un jeu stéréotypé, chaque programme joueur faisant un calcul de minimax sur les manœuvres pour toujours obtenir le même choix dans la même situation. Avec un tirage aléatoire, la meilleure solution est celle qui a la plus forte probabilité, mais cela n'exclut pas de temps en temps de choisir une manœuvre inhabituelle « pour dérouter l'adversaire » (non, l'argument est foireux dans le cas d'un combat programme contre programme).

Le programme arbitre, lui, a une meilleure connaissance des mécanismes de vol et de tir, mais rien sur la tactique ni la doctrine. C'est lui qui lit le fichier JSON créé par les programmes de préparation et qui connaît ainsi à quelle page on aboutit lorsque l'on exécute telle manœuvre sur telle page de départ. Ce qui ne veut pas dire qu'il connaît la géométrie sous-jacente, il se contente d'utiliser le fichier JSON comme un table d'associations page de départ + manœuvre → page d'arrivée..

Base de données

Le choix de la base de données MongoDB s'appuie sur les mêmes raisons que celles que j'ai données lors de ma présentation aux Journées Perl 2017. J'en sais déjà suffisamment sur les manipulations de données en SQL, alors que j'ai plus à découvrir avec les bases NoSQL. Il aurait été possible d'écrire les programmes avec une base de données SQLite ou une autre base SQL, mais je trouvais cela moins intéressant.

Lors de ma présentation, on m'a fait remarquer que MongoDB permet de stocker des images, ce qui est intéressant pour un jeu tel que l'As des As, basé sur des images représentant des avions. Le stockage des images n'a pas joué dans mon choix de MongoDB. Je n'ai jamais eu l'intention de recopier les images dans la base de données.

Notons que certaines données sont stockées dans un fichier texte avec la syntaxe JSON : les caractéristiques des avions. Au début, c'était la même chose pour les pilotes, qui figuraient dans des fichiers JSON. Puis je me suis dit que c'était plus facile dans certains cas si les pilotes étaient stockés dans une collection de la base MongoDB, en plus du fichier JSON.

Communication inter-processus

Ainsi donc, il y a trois processus qui s'échangent des informations. Ai-je eu recours à des sockets ? À des messages mis en file d'attente ? Non. Je me suis inspiré d'un mécanisme que l'on voit couramment dans les romans d'espionnage de John le Carré ou du même style, la « boîte aux lettres ». L'officier traitant donne ses instructions sur un microfilm qu'il dépose dans un endroit discret convenu : souche d'arbre, fissure entre deux briques, vous voyez le genre. L'espion récupère le microfilm, effectue les recherches demandées et livre les résultats au même endroit. Et l'officier traitant récupère les documents secrets de la même manière. C'est pareil entre le processus arbitre et les processus joueurs. L'arbitre écrit dans la base de données un enregistrement pour chaque joueur. Le processus joueur lit l'enregistrement qui le concerne, le met à jour et le réécrit dans la base. Puis l'arbitre lit l'enregistrement modifié.

Comme dans les romans de John le Carré, si un processus ne trouve pas l'enregistrement attendu dans la boîte aux lettres, il attend un petit peu et il refait une tentative de lecture. Et comme dans les romans de John le Carré, au bout d'un nombre important de tentatives infructueuses, le processus considère qu'il s'est passé quelque chose de louche et il arrête toute activité. La métaphore s'arrête là, car d'un côté l'espion se fait exfiltrer et survit, de l'autre côté le processus effectue un die.

« Quand on a un marteau, tout ressemble à un clou. » Certes, mais en procédant de la sorte, je n'avais qu'un module avec des bugs qu'il fallait contourner.

Perl 6

Installation

Je connais quatre méthodes pour installer Rakudo Star sur une machine Linux.

  1. Installer le paquet .rpm ou .deb fourni par votre distribution.

  2. Installer le paquet .rpm ou .deb fourni par un volontaire dans un dépôt Github

  3. Récupérer une image Docker, comme le propose Moritz Lentz dans son livre Perl 6 Fundamentals.

  4. Compiler à partir des sources.

Dans mon cas, la deuxième méthode est inapplicable (pas de paquet pour xubuntu 16.10) et la première méthode donne des versions vraiment trop anciennes. Mes différentes machines physiques et virtuelles et les paquets correspondants sont :

physique    xubuntu-12.04   32 bits  rakudo 2011.07
virtuelle   xubuntu-14.10   32 bits  rakudo 2013.12
virtuelle   lubuntu-15.10   32 bits  rakudo 2014.07
physique    xubuntu-16.10   64 bits  rakudo 2016.06
virtuelle   Mageia-5        32 bits  pas trouvé

Pour Docker, il faudrait d'une part que Docker soit installé sur ma machine, d'autre part que je sois à l'aise avec Docker. Or Docker n'est pas installé et les dépôts xubuntu 16.10 ne sont plus disponibles depuis un certain temps. Et je ne maîtrise pas l'utilisation de Docker.

Du coup, j'ai installé rakudo star 2018.01 à partir des sources. La première fois que j'ai compilé rakudo star, cela a pris beaucoup de temps, plusieurs heures. La deuxième fois, pour éviter d'avoir à surveiller ma machine pour saisir la commande suivante (make, make test, make install), j'ai écrit un script qui enchaîne ces commandes et donne la date et l'heure entre deux.

#!/bin/sh
# -*- encoding: utf-8; indent-tabs-mode: nil -*-

interlude() {
  echo
  echo
  date +'%Y-%m-%d %H:%M:%S'
  echo
  echo
}

mkdir ~/rakudo && cd $_
wget https://rakudo.perl6.org/downloads/star/rakudo-star-2018.01.tar.gz
interlude
tar -xvzf rakudo-star-2018.01.tar.gz
interlude
cd rakudo-star-2018.01/
interlude
perl Configure.pl --backend=moar --gen-moar
interlude
make
interlude
# If you wish, you can run the tests
# Depending on your machine, they could take over half an hour to run
make rakudo-test
interlude
make rakudo-spectest
interlude
make install
interlude
echo "export PATH=$(pwd)/install/bin/:$(pwd)/install/share/perl6/site/bin:\$PATH" >> ~/.bashrc
source ~/.bashrc

Certes, j'ai lancé ce script sur une machine virtuelle, donc nécessairement moins puissante que la machine physique hôte. Néanmoins, cela donne une idée du temps nécessaire.

make                   1h 40mn
make rakudo-test          20mn
make rakudo-spectest   3h 10mn
make install           1h

Notons qu'il y a des erreurs, mais que je suis passé outre. Et je pense que c'est justifié, au moins dans le cas de "spectest". En effet, on sait que Rakudo Star n'implémente pas complètement la spécification de Perl 6, ce qui se traduit par des erreurs dans "spectest". C'est normal.

Autres témoignages et remarques

On m'a fait remarquer que la compilation des sources nécessite beaucoup d'accès disque et que l'utilisation d'une machine virtuelle pénalise ainsi la compilation. Donc les durées que j'ai trouvées ne sont pas significatives. Néanmoins, cela montre que vous ne devez pas vous attendre à ce que l'installation par compilation des sources soit réglée en cinq minutes sur votre machine.

Un participant des journées Perl a essayé de compiler Rakudo Star pendant la conférence. Je ne sais pas s'il est passé par l'étape rakudo-spectest, je sais par contre que l'étape install lui a pris beaucoup de temps.

Un autre participant, qui utilise Perl 6 depuis plusieurs années et, même, participe à son développement, a dit qu'il compilait la version de développement de Rakudo Star chaque jour. En fait, il semble que le temps énorme que prend la compilation est dû à l'installation de mdules Perl 5 nécessaires pour certaines étapes de cette compilation ou des tests. Donc, si l'on compile Rakudo Star tous les matins, les modules utilitaires sont déjà installés et les étapes de la compilation prennent moins de temps que le premier jour.

Puis j'ai réinitialisé ma machine xubuntu-16.10 64 bits en remplaçant par une distribution xubuntu-18.04 64 bits. Il existe pur cette distribution des paquets NQP, Perl 6 et Rakudo. Toutefois, pour avoir le cœur net, j'ai préféré installer Rakudo Star à partir des sources. J'ai repris le même script que précédemment, auquel j'ai ajouté quelques appels à ifconfig pour avoir le nombre d'octets échangés avec Internet. De manière étonnante, l'installation totale a duré moins de 25 minutes ! Et à part le chargement de rakudo-star-2018.01.tar.gz, il n'y a eu quasiment aucun trafic réseau pendant l'installation, donc aucun module Perl 5 n'a été installé.

Utilisation

Identifiants

Il paraît qu'avec Perl 5, on pouvait utiliser des lettres accentuées dans les identifiants de variable ou de fonction. Je n'ai jamais osé le faire avec Perl 5, mais je ne m'en suis pas privé avec Perl 6. De plus, il est possible d'utiliser le tiret à l'intérieur d'un identifiant à la place du souligné. Avec quelques limites, par exemple le tiret ne peut pas être suivi d'un chiffre, il faut que ce soit une lettre. On ne peut pas utiliser $coef-1, il faut utiliser $coef_1, car $coef-1 représente la valeur de $coef diminuée de 1.

L'inconvénient, c'est que j'ai tendance à déclarer une variable $code-retour et à l'appeler ensuite $code_retour (ou l'inverse). Je devrais utiliser plus souvent l'auto-complétion d'Emacs (M-/).

Indices négatifs

À un moment, dans un programme, il fallait que j'additionne ainsi les éléments de deux tableaux (en syntaxe Perl 5)

$t[0] = $a[0] + $b[4];
$t[1] = $a[1] + $b[5];
$t[2] = $a[2] + $b[0];
$t[3] = $a[3] + $b[1];
$t[4] = $a[4] + $b[2];
$t[5] = $a[5] + $b[3];

Avec les index négatifs, on peut réécrire ces lignes ainsi en Perl 5 :

$t[0] = $a[0] + $b[-2];
$t[1] = $a[1] + $b[-1];
$t[2] = $a[2] + $b[ 0];
$t[3] = $a[3] + $b[ 1];
$t[4] = $a[4] + $b[ 2];
$t[5] = $a[5] + $b[ 3];

ou avec une boucle explicite

for (0..5) {
  $t[$_] = $a[$_] + $b[$_ - 2];
}

ou implicite

@t = map { $a[$_] + $b[$_ - 2] } 0..5;

Mais en Perl 6, les indices négatifs sont interdits, la syntaxe attendue est :

@t[0] = @a[0] + @b[* - 2];
@t[1] = @a[1] + @b[* - 1];
@t[2] = @a[2] + @b[ 0];
@t[3] = @a[3] + @b[ 1];
@t[4] = @a[4] + @b[ 2];
@t[5] = @a[5] + @b[ 3];

Donc impossible de faire cela en une seule boucle, il faut en faire au moins deux. C'est nul, Perl 6...

... Puis j'ai pensé à APL. En APL, comment aurais-je fait ? APL permet de faire cela sans même faire de boucle, en traitant les vecteurs (nom indigène pour « tableaux ») dans leur totalité. On commence par faire tourner le vecteur B sur lui-même pour amener l'élément d'indice 4 (*) en première position. Puis on l'additionne au vecteur A et on stocke le résultat dans la variable T

(*) En fait, en APL, on a coutume de faire commencer les indices en 1. Donc, c'est plutôt l'élément d'indice 5 que l'on place en position initiale.

T ← A + ¯2 ⌽ B

Éh bien avec Perl 6, on peut faire l'équivalent :

@t = @a «+» @b.rotate(-2);

APL, c'est génial, Perl 6 c'est génial !

Opérateurs Unicode

Un autre point que j'aime beaucoup dans APL, c'est que l'on peut comparer des valeurs avec ≤ et ≥ (U+2264 et U+2265) au lieu de <= et >=. Et surtout, on peut multiplier des nombres avec × (U+00D7) au lieu de cette abominable étoile que tous les autres langages utilisent. Éh bien on peut faire la même chose avec Perl 6 et utiliser ≤ ≥ et ×. Lorsque j'ai fait un projet APL en 2014-2015, je m'étais écrit une fonction apl-insert en Emacs-Lisp pour insérer des caractères spéciaux dans un source APL sous Emacs. Lorsque j'ai découvert la possibilité d'utiliser ≤ ≥ et × en Perl 6, j'ai dupliqué ma fonction dans le répertoire Perl 6 sans même purger les caractères APL qui ne servent pas pour Perl 6 ni changer le nom de la fonction.

Remarque. Perl 5 connaissait déjà x pour multiplier une chaîne par un nombre ou une liste par un nombre. Mais hélas, la multiplication numérique utilisait toujours l'étoile.

À ce propos, la répétition de chaîne et la répétition de liste sont deux opérateurs différents en Perl 6, respectivement « x » et « xx ». Évitez d'utiliser l'un pour l'autre comme je l'ai fait !

Modification d'une sous-chaîne de caractères

Lorsque j'ai appris Perl 5 il y a un vingtaine d'années, une fonctionnalité m'a fait comprendre que Perl 5 n'était pas simplement « un langage de programmation de plus » mais bien un domaine dont l'exploration promettait d'être passionnante. Cette fonctionnalité, c'est la possibilité d'utiliser substr en partie gauche d'une affectation :

substr($_,  0, 0) = "Larry";
substr($_,  0, 1) = "Moe";
substr($_, -1, 1) = "Curly";

dans Programming Perl 2e édition pages 227 et 228.

Avec Perl 6, je n'ai pas pensé à vérifier que cela continuait à fonctionner, pour moi c'était évident que Perl 6 reprendrait cette idée géniale.

Éh bien non, substr ne peut pas être utilisé en partie gauche en Perl 6. Le résultat est une erreur :

Cannot modify an immutable Str

Je n'ai rien trouvé sur ce sujet dans les livres qui m'ont servi à apprendre Perl 6. J'ai essayé avec la documentation en ligne de substr, rien non plus sur ce sujet. J'ai essayé de regarder dans la documentation du type Str et pour cela il n'y avait pas de lien hypertexte depuis la documentation en ligne de substr, j'ai dû passer par la liste des types standards. Et sur la documentation du type Str, j'ai enfin trouvé ce que je cherchais, la fonction substr-rw.

Notation fonctionnelle

Diagramme de Venn de la composition de deux fonctions

Image personnelle. Les conditions de licence sont les mêmes que pour le texte.

Lorsque j'ai appris les relations et les fonctions en classe de 6e en mathématiques, il y avait un point que je n'aimais pas et qui piégeait nombre de mes camarades de classe, la notation pour la composition des fonctions. Ainsi, « f suivie de g » se note « g rond f » et non pas « f rond g ». La raison invoquée est qu'avec la notation fonctionnelle proposée par Euler, on a :

y = f(x)
z = g(y)

donc

z = g(f(x)) = gof (x)

J'ai beau être accoutumé à cette notation où les fonctions s'emboîtent les unes dans les autres de façon que l'ordre chronologique se lit de droite à gauche, je persiste à ne pas aimer. Et en plus, presque tous les langages de programmation ont pris la suite du « traducteur de formules » de Backus (FORTRAN) et reprennent allègrement cette monstruosité syntaxique. Par exemple, vous prenez une valeur x, vous lui appliquez successivement la fonction logarithme, la fonction cosinus, vous en prenez la valeur absolue puis la racine carrée. En dessinant un diagramme de Venn, cela donne :

Diagramme de Venn de la composition de quatre fonctions

Image personnelle. Les conditions de licence sont les mêmes que pour le texte.

et en programmation, par exemple avec Perl 5 :

$y = sqrt(abs(cos(log($x))))

Seul rayon de soleil dans cette grisaille, les calculatrices HP mais aussi TI, qui permettent de coder dans l'ordre naturel :

RCL 0
LN
COS
ABS
SQRT
RTN

plus peut-être d'autres langages tels que Forth et Postscript que je n'ai jamais appris, ou Smalltalk que j'ai à peine pratiqué.

Et maintenant, il y a aussi Perl 6 :

$y = $x.log.cos.abs.sqrt;

Transformée schwartzienne

Si vous connaissez déjà la transformée schwartzienne, allez directement au paragraphe suivant.

Rappel de la Transformée schwartzienne en Perl 5

Supposons que je veuille trier une liste de chaînes de caractères en fonction d'une date qui s'y trouve. La solution basique consiste à écrire :

my @t = split /---\n/, <<'EOF';
Georges Guynemer disparaît le 11 septembre 1917 aux environs de Poelcappelle.
---
Le 21 avril 1918, Manfred von Richthofen est abattu à Vaux-sur-Somme.
L'a-t-il été par le canadien Albert Roy Brown ou bien par les artilleurs de la 53e Batterie Australienne ?
---
Première victoire aérienne à l'est : Petr Nicolaevitch Nesterov a volontairement percuté un Albatros autrichien le 8 septembre 1914.
---
5 octobre 1914, première victoire en combat aérien par Frantz et Quenault sur le front ouest.
---
Charles Nungesser, un temps surnommé « le Hussard de la Mors », est plus connu pour sa tentative de traversée
de l'Atlantique le 8 mai 1927 et qui s'est terminée tragiquement que pour ses 45 victoires.
---
La RAF a été créée le 1er avril 1918. Quel sens de l'humour !
---
Sarajevo, 28 juin 1914 : un attentat qui a fait plus de 18 millions de morts et combien de blessés...
---
11 novembre 1918, le cauchemar s'interrompt pour une vingtaine d'années.
EOF

my %mois = qw/janvier  1    février   2   mars      3
              avril    4    mai       5   juin      6
              juillet  7    août      8   septembre 9
              octobre 10    novembre 11   décembre 12/;
my $re = join '|', keys %mois;

sub extr {
  my ($ch) = @_;
  if ($ch =~ /(\d{1,2})(?:er)?\s+($re)\s+(\d{4})/) {
    return $3 * 10000 + $mois{$2} * 100 + $1;
  }
  else {
    return 1e8;
  }
}

my @t1 = sort { extr($a) <=> extr($b) } @t;
say join "\n", @t1;

En mettant un mouchard dans la fonction de comparaison, on constate qu'elle est appelée 15 fois, donc la fonction extr est appelée 30 fois, pour trier 8 paragraphes. L'idée est donc de remplacer

my @t1 = sort { extr($a) <=> extr($b) } @t;

par

my @t0 = map {  [ $_, extr($_) ] } @t;
my @t1 = sort { $a->[1] <=> $b->[1] } @t0;
my @t2 = map { $_->[0] } @t1;

Ainsi, la fonction d'extraction est appelée 8 fois au lieu de 30 et son résultat est conservé. Mais habituellement, on écrit de façon plus concise et plus synthétique :

my @t2 = map { $_->[0] }
         sort { $a->[1] <=> $b->[1] }
         map {  [ $_, extr($_) ] } @t;

Transformation schwartzienne en Perl 6

Le problème avec cette expression concise,

my @t2 = map { $_->[0] }
         sort { $a->[1] <=> $b->[1] }
         map {  [ $_, extr($_) ] } @t;

c'est qu'il faut la lire de bas en haut ou de droite à gauche, dans le sens inverse de la lecture normale (pour nous, habitués de l'alphabet latin). C'est comme la composition des fonctions avec la notation héritée d'Euler.

Avec Perl 6, nous avons l'opérateur feed, ou ==>, qui permet de faire comme les pipes Unix, travailler de gauche à droite et de haut en bas. Ainsi, le programme devient :

use v6;

my @t = split "---\n", q:to/EOF/;
Georges Guynemer disparaît le 11 septembre 1917 aux environs de Poelcappelle.
---
Le 21 avril 1918, Manfred von Richthofen est abattu à Vaux-sur-Somme.
L'a-t-il été par le canadien Albert Roy Brown ou bien par les artilleurs de la 53e Batterie Australienne ?
---
Première victoire aérienne à l'est : Petr Nicolaevitch Nesterov a volontairement percuté un Albatros autrichien le 8 septembre 1914.
---
5 octobre 1914, première victoire en combat aérien par Frantz et Quenault sur le front ouest.
---
Charles Nungesser, un temps surnommé « le Hussard de la Mors », est plus connu pour sa tentative de traversée
de l'Atlantique le 8 mai 1927 et qui s'est terminée tragiquement que pour ses 45 victoires.
---
La RAF a été créée le 1er avril 1918. Quel sens de l'humour !
---
Sarajevo, 28 juin 1914 : un attentat qui a fait plus de 18 millions de morts et combien de blessés...
---
11 novembre 1918, le cauchemar s'interrompt pour une vingtaine d'années.
EOF

my %mois = qw/janvier  1    février   2   mars      3
              avril    4    mai       5   juin      6
              juillet  7    août      8   septembre 9
              octobre 10    novembre 11   décembre 12/;

my $re = rx/janvier|février|mars|avril|mai|juin|juillet|août|septembre|octobre|novembre|décembre/;

sub extr(Str $ch) {
  if $ch ~~ /(\d+)'er'? \s+ ($re) \s+ (\d+)/ {
    return $2 × 10000 + %mois{$1} × 100 + $0;
  }
  else {
    return 1e8;
  }
}

@t ==> map { [ $_, extr($_) ] } \
   ==> sort { $^a[1] <=> $^b[1] } \
   ==> map { $_[0] } \
   ==> my @t1;

say join "\n", @t1;

Juste un point qui me chagrine, la définition de l'expression régulière $re. En Perl 5, j'ai généré cette expression régulière à partir du contenu de %mois. En Perl 6, j'ai été obligé de taper deux fois le nom des mois, une fois pour le hachage, une autre fois pour l'expression régulière. Les anglophones utilisent les rétro-acronymes DRY (Don't Repeat Yourself) et WET (Write Everything Twice) pour cela. Évidemment, il vaut mieux travailler en DRY qu'en WET. Je ne sais pas comment faire pour ce point particulier en Perl 6, mais un jour j'aurai la réponse.

Un autre point qui m'ennuie (sans pour autant me chagriner), c'est le fait d'être obligé d'ajouter un backslash en fin de ligne pour indiquer que l'instruction se poursuit à la ligne suivante. Comme l'a dit une personne dans la salle, « C'est un retour quarante ans en arrière. ». Voir le point suivant.

Au-delà de la transformation schwartzienne

En fait, comme on m'a fait remarquer, la transformation schwartzienne perd pas mal d'intérêt en Perl 6. Dans le cas particulier assez fréquent d'un tri avec une comparaison mono-critère, vous pouvez remplacer la fonction de comparaison par la fonction d'extraction de ce critère unique. Cela donne :

@t ==>  sort { extr($_) } \
   ==> my @t1;

Et à ce moment-là, Perl 6 mémorise en cache les valeurs des critères pour chaque élément à trier. Le map de stockage et le map de déstockage ne sont donc plus utiles. Vous pouvez faire le test en ajoutant un mouchard dans la fonction d'extraction (compteur ou impression de message).

La transformation schwartzienne reste utile pour les tris multi-critères. En reprenant l'exemple ci-dessus, on peut ne pas agglomérer le jour avec le mois et l'année et coder ainsi :

@t ==> map { [ $_, annee($_), mois($_), jour($_) ] } \
   ==> sort { $^a[1] <=> $^b[1] || $^a[2] <=> $^b[2] || $^a[3] <=> $^b[3] } \
   ==> map { $_[0] } \
   ==> my @t1;

Mais c'est plus laborieux et moins stylé.

Alignement vertical

Pour moi, l'alignement vertical dans le code est très important pour la compréhension par un humain. Je ne parle pas simplement de l'indentation du premier caractère autre que l'espace, je parle des éléments qui se répêtent d'une ligne à l'autre. Par exemple, en Perl 5, je pourrais écrire :

$longueur[$n] = $longueur[$n - 1] + $dx;
$largeur [$n] = $largeur [$n - 1] + $dy;

En Perl 6, il est interdit d'écrire :

@longueur[$n] = @longueur[$n - 1] + $dx;
@largeur [$n] = @largeur [$n - 1] + $dy;

Car il ne doit pas y avoir d'espace entre le nom du tableau et le crochet ouvrant qui donne l'indice. Il faudrait donc écrire ce code horrible :

@longueur[$n] = @longueur[$n - 1] + $dx;
@largeur[$n] = @largeur[$n - 1] + $dy;

Moi, je me suis résigné à écrire

@longueur[$n] = @longueur[$n - 1] + $dx;
@largeur[ $n] = @largeur[ $n - 1] + $dy;

Tous les éléments restent alignés verticalement, sauf les crochets ouvrants.

Il existe une fonctionnalité intéressante que vous avez aperçue au paragraphe sur la transformation schwartzienne, le unspace. S'il y a un backslash suivi par un ou plusieurs caractères blancs (espace, saut de ligne, etc), alors l'interpréteur Perl 6 considère qu'il n'y a pas de blanc. Ainsi, les deux lignes suivantes sont équivalentes :

@longueur[$n]
@longueur\       [$n]

Cela aurait-il pu me servir dans mon exemple longueur-largeur ? Non, car l'ajout du backslash pour masque le blanc dans la ligne "largeur" aurait créé un décalage d'un caractère supplémentaire. Il aurait donc fallu insérer un blanc dans la ligne "longueur", ce qui en cascade aurait nécessité d'insérer un autre backslash dans la ligne "longueur". Cela aurait donné au final :

@longueur\ [$n] = @longueur\ [$n - 1] + $dx;
@largeur\  [$n] = @largeur\  [$n - 1] + $dy;

On m'a donné d'autres suggestions pour traiter ce problème. L'une consiste à commettre une faute d'orthographe et à écrire soit

$longuer[$n] = $longuer[$n - 1] + $dx;
$largeur[$n] = $largeur[$n - 1] + $dy;

soit

$longueur[$n] = $longueur[$n - 1] + $dx;
$largeurr[$n] = $largeurr[$n - 1] + $dy;

Une autre suggestion consiste à installer un module qui change la syntaxe de Perl 6 pour admettre des espaces là où la syntaxe standard les refuse. Hélas, je n'ai pas retenu le nom du module ni celui de son auteur.

Expressions régulières

La syntaxe des regexps a été remaniée en profondeur. Reportez-vous systématiquement à la doc lors de vos premières tentatives. Un exemple où je me suis fait avoir. Pour tester qu'une chaîne de caractères ne contient que des chiffres 0, 1 et 5, ne pas écrire

if $ch ~~ /^[015]+$/

mais

if $ch ~~ /^[0|1|5]+$/

En fait, en lisant la documentation, la syntaxe est :

if $ch ~~ /^<[015]>$/

Il y a énormément à dire sur ce sujet, il est préférable que je passe à autre chose.

Typage des valeurs et des variables

Une grande nouveauté de Perl 6, c'est le fait que l'on peut associer un type à une variable et que l'interpréteur fera les vérifications de type associées. Seulement voilà, les contrôles sont parfois un peu trop stricts. Soit le bout de code suivant

class Exemple {
  has Num $.grandeur is rw;
}
my Exemple $donnée .= new;
$donnée.grandeur = 1;

Résultat :

Type check failed in assignment to $!grandeur; expected Num but got Int (1)
  in block <unit> at exemple1.p6 line 5

Cet exemple de code plante, parce que l'interpréteur attend un (Num) et qu'on lui donne un (Int). Non, il n'y a pas de conversion implicite des entiers vers les flottants. Qu'à cela ne tienne, il suffit d'écrire

$donnée.grandeur = 1.0;

et le tour est joué ! Éh bien non, ça plante encore, cette fois-ci parce que paraît-il on veut mettre un (Rat), c'est-à-dire un rationnel, dans un (Num) ! Je m'en suis tiré d'abord avec une conversion explicite :

$donnée.grandeur = 1.Num;

ce qui n'est pas élégant. Puis je me suis rappelé de certains modules Perl 5 qui devaient renvoyer un résultat « vrai » en contexte booléen avec une valeur donnant zéro en contexte numérique. Il y a les modules qui écrivent

return 0 but true;

Astucieux, mais cela ne m'aide pas. Et il y a les autres qui écrivent

return "0e0";

Beaucoup plus intéressant. J'ai donc écrit

$donnée.grandeur = 1e0;

et c'est passé !

Undef et (nil)

Un autre point qu'il faudra que je règle, c'est le typage des paramètres dans les fonctions. Si la déclaration de la fonction attribue un type à un paramètre et qu'on appelle cette fonction avec undef, le programme plante parce que l'on fournit une valeur de type (Nil) à un paramètre de type (Num) (par exemple). Dans ce cas, je me résigne pour l'instant à ne pas spécifier de type.

Références

Lorsque j'ai lu les livres qui m'ont servi à apprendre le langage, j'ai constaté certaines omissions (Désolé Laurent). L'omission la plus flagrante pour moi est concerne les références. Même si l'on écrit

@t[2]

au lieu de

$t[2]

pour accéder à un élément de tableau, la notation

$t[2]

existe toujours et concerne la variable $t qui contient une référence à une liste. L'équivalent en Perl 5 est

$t->[2]

Je ne me suis pas privé d'utiliser des références dans mes programmes, mais j'aurais bien aimé avoir l'aval des gourous sur la question.

Et pour convertir une référence de liste en liste ? Impossible d'écrire :

my $ref = <A B C D E F>;
my @liste = $ref;

car cela crée une liste avec un seul élément, la référence à une liste. Il faut en fait écrire :

my $ref = <A B C D E F>;
my @liste = $ref[*];

Opérateurs spécifiques

Une autre lacune, mais qui est restée pour l'instant théorique pour mes besoins, c'est la possibilité de créer ses propres opérateurs. Comment définit-on la priorité de ces opérateurs par rapport aux opérateurs traditionnels ?

La réponse a été donnée pendant les Journées Perl. Elle consiste à utiliser les « traits » is tigher ou is looser. C'est marqué dans la doc à condition de savoir où chercher.

POD

Je ne me suis pas du tout intéressé à POD en version Perl 6. J'ai écrit la documentation de mes programmes avec du POD tel que je le codais en Perl 5. La seule différence, c'est qu'il faut obligatoirement une balise =begin POD et une balise =end POD. Et il n'y a pas de commande perl6doc, à la place il faut utliser perl6 avec l'option --doc.

Néanmoins, si vous regardez les statistiques calculées par Github sur mon dépôt, vous verrez que parmi les langages utilisés, Perl 6 est très largement dominant. Ce qui veut dire que le présent texte, entièrement écrit en POD version Perl 5, n'a pas été pris en compte par les statistiques de Github, voire a été pris en compte mais en tant que POD version Perl 6.

MongoDB

Avertissement : hélas, je n'ai pas pris de notes lorsque j'ai installé le module MongoDB pour Perl 6. J'expose ici mes souvenirs, qui ne sont pas fiables à 100 %.

D'autre part, ce que je raconte concerne l'installation que j'ai faite le 17 mars. J'ai pu constater que l'auteur du module MongoDB pour Perl 6 s'était remis au travail fin avril. Les bugs que je signale ont peut-être disparu (et d'autres sont apparus...). Je n'ai pas voulu prendre le risque de réinstaller le module pendant que je réalisais mes programmes et que je rédigeais mon exposé. Je referai une nouvelle installation au moment où je migrerai vers xubuntu 18.04.

Installation du 17 mars 2018

Tout d'abord, un problème qui n'a rien à voir avec Perl 6. Sur ma machine, la version de MongoDB est la version 2.6, alors que le site web donne la documentation d'une version 3.x. Par exemple, le site propose une commande findAndUpdate, qui n'existe pas sur ma machine. D'accord, il y a la possibilité d'accéder à la doc 2.6 (ou par anticipation à la doc 4.0), mais je me suis contenté d'avoir une doc légèrement déphasée plutôt que de cliquer sur de nombreux liens pour trouver la doc parfaitement adaptée.

Avec l'installateur intégré zef de Rakudo Star, l'installation du module MongoDB se fait simplement par :

zef install MongoDB

Et ça plante. Normal, il faut lire le README, qui indique qu'il y a des problèmes dans les scripts de tests et qu'il faut donc ne pas bloquer sur les erreurs obtenues. Donc, faire :

zef install --/test MongoDB

Et le module est installé ! Maintenant, il reste à écrire quelques programmes. J'ai commencé par copier-coller l'exemple fourni dans la documentation du programme. Mais ça plante ! D'après ce que je comprends des messages d'erreur, cela plante à cause d'un sous-programme qui attend un paramètre (Str) et qui reçoit un (Int). Pour votre gouverne, il s'agit de trois lignes :

debug-message("command done {$command.find-key(0)}");

Je vais donc dans le fichier désigné par le message d'erreur (un fichier avec un nom horrible tiré d'un SHA1 ou d'un MD5) et je corrige en supprimant les trois lignes en cause. Les messages de débug que je ne sais pas interpréter, je me sens autorisé à les supprimer. Je reteste, cela ne change rien. Je pense que le module figure en deux exemplaires : le fichier source que j'ai modifié et un fichier de bytecode que je n'ai pas vu. Le bytecode n'ayant pas changé, le bug est toujours là.

Je clone donc le dépôt Git Hub, je supprime les trois messages de débug et je réinstalle par :

zef install --/test .

(où « . » désigne le répertoire courant, c'est à dire le dépôt Git local plutôt que le dépôt hébergé par Github). Et zef refuse car, me dit-il, le module est à jour. En cherchant dans l'aide en ligne de zef, je trouve une solution :

zef install --force --/test .

Est-ce la bonne solution ? Je ne sais pas, mais au moins, ça fonctionne. Le programme proposé dans la documentation du module fonctionne, à condition d'enlever le findAndUpdate qui n'existe pas en version 2.6.

Quand j'essaie de faire mes propres accès à MongoDB, j'ai de nouveau un problème, une histoire de promesse non tenue. Après moult tentatives, j'en viens à la conclusion que dans un find, il est indispensable de coder le paramètre :

projection => ( _id => 0, )

c'est-à-dire, prendre tous les attributs du document, sauf l'attribut _id. Pourquoi cette exclusion ? Je ne sais pas. Tout ce que je sais, c'est que si l'on exclut _id ça fonctionne presque toujours (*), si l'on inclut implicitement _id ça ne fonctionne jamais. Donc maintenant, je code cette exclusion dans tous mes find.

(*) Presque toujours, car il m'arrive de faire des erreurs sur d'autres points.

Tiens, j'ai mentionné « moult tentatives ». Le problème, c'est qu'à force de zigzaguer dans les méandres de la syntaxe, j'ai effectué plusieurs tentatives identiques. Pour éviter cette perte de temps, j'ai décidé au bout d'un certain temps d'écrire un livre de recettes, où je mémorise chaque variante testée des accès à la base de données, avec les messages d'erreur obtenus.

Par exemple, bien noter que l'instruction find dans un programme Perl 6 ne reconnaîtra pas les options sort, limit et find. Et ce n'est pas un problème de version 2.6 contre 3.x, ces trois options sont mentionnées dans le livre O'Reilly de 2013 (page 68 si vous voulez vérifier).

On trouve avec MongoDB une variante du problème évoqué dans le paragraphe sur le typage des données. MongoDB connaît les entiers (Int) et les flottants (Num), mais pas les rationnels (Rat). Le message d'erreur n'est pas très clair :

2018-05-25 05:31:06.093687 [E]  1: localhost:27017: encode() on 0.5, error: Not yet implemented
. At site#sources/9F1921D0AA42F2C1C0229F51082ED0A3E384968C (MongoDB::Wire):112
2018-05-25 05:31:06.097117 [E]  1: No server reply on query. At site#sources/C727D6F914B4AE4082E938B9310DC8D2F4CD7B53 (MongoDB::Collection):101 in find()
2018-05-25 05:31:06.098900 [E]  1: No cursor returned. At site#sources/BC4819BBBB818C799983157518CA75F0C1A6DFC7 (MongoDB::Database):73 in run-command()

Et si vous voulez le script minimal ayant donné cette erreur, le voici :

use v6;
use BSON::Document;
use MongoDB::Client;
use MongoDB::Database;
use MongoDB::Collection;

my MongoDB::Client     $client  .= new(:uri('mongodb://'));
my MongoDB::Database   $database = $client.database('exemple2');
my MongoDB::Collection $coll     = $database.collection('exemple2');

my BSON::Document $doc .= new: (
     numéro => 0.5,
     );
my BSON::Document $req .= new: (
  insert => 'exemple2',
  documents => [ $doc ],
  );

my BSON::Document $result = $database.run-command($req);
#say "Création exemple2 ok : ", $result<ok>, " nb : ", $result<n>;

Installation du 21 juin 2018

Après avoir migré ma machine vers xubuntu-18.04 64 bits et MongDB 3.6.3 et après avoir installé Rakudo Star à partir des sources, j'ai voulu installer le module MongoDB pour Perl 6. La commande

zef install MongoDB

a planté parce que mon système ne contenait pas de libssl.so. Il contient bien une libssl3.so ainsi qu'une libssl.so.1.1 et une libssl.so.1.0.0, mais cela ne semble pas avoir convenu aux tests de MongoDB. J'ai donc tlancé la deuxième variante de l'installation, celle qui ne tient pas compte des tests :

zef install --/test MongoDB

Et l'installation s'est faite. J'ai lancé quelques programmes Perl 6 utilisant MongoDB et ils ont fonctionné du premier coup. Un grand pas en avant entre mars et juin 2018 !

Remarquez que je n'ai pas cherché à voir si le problème de

projection => ( _id => 0, )

ou le problème des (Rat) conduisant au message

2018-05-25 05:31:06.093687 [E]  1: localhost:27017: encode() on 0.5, error: Not yet implemented

existent encore.

Bailador

Au début, je n'avais pas l'intention de faire du web dynamique. Puis j'ai trouvé que les fichiers HTML, avec les liens hypertextes qui conviennent, c'est plus facile à lire que le résultat d'une requête écrite dans un interpréteur de JavaScript.

Donc, j'ai installé Bailador. Que dire ? J'ai installé le module, j'ai écrit un programme basé sur Bailador, et ça tourne. Je n'ai rien de croustillant à raconter.

Il faut dire que mon utilisation de Bailador est aussi rustique que l'utilisation de Dancer2 que j'ai présentée l'an dernier. Plus rustique même, puisque je n'utilise pas de formulaires HTML. Si vous vous attendez à avoir autant de modules pour Bailador que ce dont vous disposez pour Dancer2, il va falloir attendre un peu (et, pourquoi pas, contribuer à l'effort).

Il y a juste un point à ajouter. Ce point n'est pas une conséquence de l'utilisation de Bailador, mais je l'ai découvert parce que j'ai décidé d'utiliser Bailador. Même si je ne connais rien à l'architecture MVC (Model, View, Controller), j'ai décidé de scinder mon programme Bailador en plusieurs fichiers. Le premier, site.p6 correspond, je pense, au Contrôleur. Dans un sous-répertoire, j'ai lib/acces-mongodb.pm6, correspondant au Modèle. Et j'ai d'autres fichiers lib/site-coup.pm6, lib/site-liste-parties.pm6 et lib/site-partie.pm6 correspondant aux Vues. J'arrive à appeler les fonctions du Modèle lib/acces-mongodb.pm6 depuis le Contrôleur site.p6, mais pas depuis les Vues lib/site-xxx.pm6. J'ai encore quelques notions à apprendre sur les use et les require en Perl 6.

Retour d'expérience sur l'As des As

Un point qui m'a surpris et auquel j'aurais pu penser, c'est la réaction des pilotes en page 223 en mode combat. Le but était de permettre à un avion déjà bien amoché de fuir pour survivre et pour n'accorder qu'un demi-point de victoire, au lieu de continuer à combattre avec un net désavantage et d'accorder un point de victoire entier à l'ennemi.

Ce qui se passe en réalité, c'est que le choix de la fuite donne en moyenne un résultat négatif : -0.5 PV si l'adversaire préfère continuer le combat, 0 PV si l'adversaire souhaite lui aussi fuir. En revanche, la poursuite du combat donne autant de chances de gagner le combat que de le perdre. Donc en moyenne, un résultat nul, meilleur que la moyenne des résultats de la fuite. C'est ainsi que les pilotes sont amenés à choisir la poursuite du combat.

Ce qu'il faudrait faire, c'est moduler la note des résultats antérieurs en comparant le potentiel restant de la situation actuelle et celui de ces coups antérieurs. Si un pilote est en page 223 avec tout juste 2 points de vie sur les 12 initiaux, on privilégiera les coups antérieurs avec 1, 2 ou 3 points de vie, tout en minimisant l'importance des coups antérieurs avec 8 points de vie ou plus. De la sorte, peut-être que les pilotes préféreront fuir.

À compléter.

Conclusion

J'aime beaucoup Perl 6. J'avais quelques projets pour lesquels j'envisageais d'utiliser APL, je pense que ces projets vont utiliser Perl 6 à la place. J'ai encore des lacunes à combler pour une bonne utilisation de Perl 6, mais c'est juste une question de temps.

Quant au choix de l'As des As pour un projet dans le genre Google Alpha Go Zero, il faut avoir à l'esprit que c'est un jeu beaucoup plus simple que le go ou les échecs. Plus complexe que le tic-tac-toe certes, mais largement plus simple de plusieurs ordres de grandeur que les deux jeux classiques. C'est pour cela que j'ai pu créer un « Alpha As des As Zéro » sur un datacenter constitué d'un seul PC de puissance moyenne.

LICENCE

Texte diffusé sous la licence CC-BY-NC-ND : Creative Commons avec clause de paternité, excluant l'utilisation commerciale et excluant la modification.

Certaines illustrations sont diffusées avec une licence différente. Celle-ci est mentionnée à la suite de l'illustration.

You can’t perform that action at this time.