Mon apprentissage de Erlang - Version imprimable +- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org) +-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38) +--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51) +--- Sujet : Mon apprentissage de Erlang (/showthread.php?tid=5664) |
RE: Mon apprentissage de Erlang - Sephi-Chan - 27-08-2011 En fait, c'est plus proche de la version proposée par Niahoo, avec le case. Mais au moins, ça a une bonne tronche et ce n'est pas inutilement compliqué, malgré les quelques parasites. RE: Mon apprentissage de Erlang - srm - 27-08-2011 Les parasites sont du au fait que le langage est typé RE: Mon apprentissage de Erlang - Sephi-Chan - 26-04-2012 Bon, je me remets un peu sur Erlang ces jours-ci. J'en profite pour parler du système de processus. Le processus a l'air d'être l'unité de base de Erlang : un programme est composé de multiples processus qui communiquent entre eux par des messages. Contrairement à la programmation objet, on n'invoque pas de méthode sur un processus : on lui envoie un message. La différence, c'est qu'un n'implique pas forcément un retour. Si je veux savoir si le processus en face a entendu mon message, il faut qu'il me réponde (en m'envoyant à son tour un message). Le parallèle avec la vie réelle est assez simple : chaque personne est un processus. Quand on s'adresse à quelqu'un (un processus), on lui envoie un message auquel il répond ou non (parce qu'il n'a pas entendu ou qu'il est mort entre-temps, par exemple). Quand il répond, il envoie à son tour un message. On peut créer un processus grâce à la fonction spawn .
J'utilise ici cette fonction spawn avec 3 arguments, le premier est un atom qui indique le nom du module, le second est un atom qui indique le nom de la fonction, et le troisième est une liste d'arguments à passer à la fonction.Je crée donc un processus à partir de la fonction character du module character . Je ne lui donne aucun argument. La fonction me retourne un l'identifiant du processus créé (qu'on appelle Pid, pour process identifier) que je garde de côté dans une variable Character , qu'on distingue d'un atom par la majuscule) pour pouvoir lui envoyer des messages.Voyons une implémentation possible de ce module et de cette fonction.
Ici, character est une fonction qui dispose d'une clause receive , qui va lui permettre de recevoir des messages et qui utilise le pattern matching pour distinguer les messages qu'elle reçoit et les traiter.En l'état, elle est capable de recevoir deux types de messages : un message contenant seulement l'atom introduce , et n'importe quel autre message (on stock ce message — quel qu'il soit — dans la variable Any, qui aurait pu s'appeler n'importe comment, par ailleurs).Comme on peut le voir, le premier cas de message affiche à l'écran une chaîne puis appelle la fonction character : elle est donc récursive. Si on ne fait pas ça, le bloc receive se termine et le processus n'est plus capable de recevoir de message.À l'inverse, c'est exactement le comportement qu'on demande à l'autre forme de message reçu : l'arrêt de la réception. Je teste maintenant le code dans une console :
Comme on peut le voir, quand j'envoie un message contenant l'atom endive , les messages introduce suivant ne sont pas traités. Si l'on n'avait pas créé de règle pour les messages d'une autre forme, le message endive n'aurait pas été traité, mais la réception des messages suivants n'aurait pas été interrompue pour autant (je ne suis pas encore capable d'en expliquer la raison).Notons que l'utilisation de la fonction io:format sert seulement pour voir facilement ce qui se passe.Dans le prochain poste, j'expliquerais comment permettre à notre personnage de répondre au message. RE: Mon apprentissage de Erlang - niahoo - 26-04-2012 (26-04-2012, 02:05 PM)Sephi-Chan a écrit : Comme on peut le voir, quand j'envoie un message contenant l'atom endive, les messages introduce suivant ne sont pas traités. Si l'on n'avait pas créé de règle pour les messages d'une autre forme, le message endive n'aurait pas été traité, mais la réception des messages suivants n'aurait pas été interrompue pour autant (je ne suis pas encore capable d'en expliquer la raison). Si tu n'avais pas utilisé un catch-all (je te suggère d'ailleurs d'utiliser _ au lieu de Any), le process serait resté bloqué sur receive, en attente d'autres messages, et aurait pu les traiter au fur et à mesure qu'ils arrivent, les messages non reconnus de type endive seraient simplement ignorés jusqu'au prochain appel de receive. On utilise ça qand on attend des messages avec une priorité. dans le code que je donne, on veut vérifier si la mailbox ne contient aucun message de type important, et s'il n'y en a pas, on consulte les autres messages, un truc du genre (non testé): Code : listen(prio) -> edit : la clause 'after' ne servait à rien dans la seconde fonction. ainsi c'est plus simple. RE: Mon apprentissage de Erlang - Sephi-Chan - 26-04-2012 Pour en revenir aux messages entre processus. On va modifier le programme pour que le processus envoie un message en retour après avoir reçu un message d'introduction.
Ici, on change le pattern du message : avant on attendait seulement l'atom introduce , maintenant on accepte un tuple composé de quelque chose et de introduce . Ce quelque chose, ça va être le Pid du processus qui a envoyé le message : quand on attend une réponse de quelqu'un, on s'assure de lui donner notre adresse.Ici, quand l'envoyeur va envoyer un message, il va envoyer un tuple contenant son propre Pid et l'atom introduce . Ainsi, l'autre lui répondra l'atom reply !D'ailleurs, on peut voir qu'en recevant le message introduce , il va se heurter à la seconde règle du receive et donc arrêter de recevoir des messages ! Pour régler ça, il faudrait ajouter une nouvelle règle au receive pour gérer la réception d'un message contenant l'atom reply .Comme ceci :
Ici, j'ai utilisé la fonction self qui retourne le Pid du processus courant.Pour essayer ce code :
Et voilà ! Deux personnages sont capables de discuter ensemble ! RE: Mon apprentissage de Erlang - niahoo - 26-04-2012 héhé mais là vous êtes trois à parler, tu sers d'entremetteur ! Voici un petit exemple de code pour dialoguer avec un character Code : - module(character). Code : 54> c(character). la fonction test ne compte pas, c'est juste pour s'éviter de taper dans le shell quand on debug. On peut voir un truc courant en erlang, la partie client et la partie serveur son codées dans le même module On encapsule donc tout ce qui concerne le processus, premièrement le 'spawn', ensuite l'envoi de messages, et enfin la réception des messages. ça permet d'avoir un comportement asynchrone mais en gardant une API qui ressemble à du code classique, synchrone. Enfin, ça nique un peu la lisibilité du code, mais io_lib:format/2 renvoie des listes imbriquées, la fonction lists:flatten/1 prend une liste et l'applatit comme son nom l'indique Code : 62> lists:flatten([1,2,[[4,5],6,7,[8,[9]]]]). RE: Mon apprentissage de Erlang - srm - 27-04-2012 Pour les curieux le code équivalent en Scala est disponible http://www.jeuweb.org/showthread.php?tid=6739&pid=107533#pid107533 RE: Mon apprentissage de Erlang - Sephi-Chan - 20-10-2012 J'ai fait mes premiers pas avec OTP. Le but est de créer le socle d'une application OTP qu'on peut lancer et qui lance un superviseur principal et tous les processus enfants. Pour le moment l'application ne fait rien d'autre que démarrer. Le code est sur GitHub. Derrière le terme d'application OTP se cache en fait 3 composants : un fichier qui décrit l'application, un module qui implémente le comportement application et un autre qui implémente le comportement supervisor .Le tout est organisé en 2 répertoire : un premier nommé src qui contient le code, et l'autre nommé ebin qui contient le code compilé (et le fichier de description de l'application).Ce fichier de description contient s'appelle battleship.app , où battleship est le nom de notre application. Il contient un tuple de 3 éléments.
Le fichier suivant est mon module d'application, src/battlefleet_app.erl :
Il définit donc le module battlefleet_app , qui implémente le comportement application . Il exporte deux fonctions start et stop (En Erlang, les fonctions qui ne sont pas exportées ne peuvent être appelées qu'au sein du module dans lequel elles sont définies).Comme toujours avec OTP, on fournit essentiellement des modules contenant des fonctions qu'on n'utilise pas directement : c'est OTP qui les invoquera et il faut donc respecter ses exigences en terme d'arguments acceptés et de valeur retournées (cf. celles du comportement application )La fonction start sert à appeler la fonction start_link de notre superviseur principal (le module battlefleet_sup ) avec des arguments donnés (la fameuse liste vide qu'on a renseigné dans notre fichier battleship.app ).N'oubliez pas qu'en Erlang, les mots comme normal ne sont pas des variables (qui commencent par une majuscule) mais des atomes, des valeurs qui servent juste à donner du sens au code grâce au pattern matching. Ici, si on appelle la fonction start de ce module (ce que fera OTP) avec autre chose que l'atome normal en premier argument, la fonction ne sera même pas appelée. Cela nous permet d'écrire différentes implémentation de la fonction selon ce qu'elle reçoit. En quelque sorte, ça évite d'avoir à faire une condition à l'intérieur de la fonction.En revanche, le second argument est bien transmis à la fonction dans la variable Args .La fonction stop a pour seul effet de retourner l'atome ok . Notez le underscore qui précède le nom de la variable. Il sert à indiquer au développeur (qui relit le code) que cette variable ne sera pas utilisée. C'est une convention purement sémantique. Le compilateur n'émet pas d'avertissement indiquant que la variable n'etst pas utilisée si elle est préfixée de ce underscore.Le fichier suivant est mon module d'application, src/battlefleet_sup.erl :
Le superviseur a pour rôle de… superviser. C'est un processus qui surveille un autre processus et de réagir à son cycle de vie (extinction propre, extinction brutale, etc.). C'est la base d'Erlang en matière de tolérance à l'erreur. L'un des mantra d'Erlang est "Let it crash". En effet, Erlang incite à ne pas coder de manière défensive (ce qui complique considérable le code) et de laisser un processus planter s'il rencontre une erreur (un message innatendu, par exemple) afin qu'il soit relancé dans un état sain par un superviseur. Ici, le fichier définit le module battlefleet_sup (qu'on a utilisé dans battlefleet_app !), qui implémente le comportement supervisor et exporte les fonctions start_link et init .En Erlang, les epxression comme ?MODULE sont des macros. En l'occurrence, ?MODULE est une macro native qui représente le nom du module courant. Les macros sont remplacées par leur valeur au moment de la compilation. Ici, la macro ?MODULE sera remplacée par l'atome battlefleet_sup .La fonction start_link appelle à son tour la fonction start_link du module supervisor (fourni par Erlang) en lui transmettant 3 arguments : un tuple, un nom de module et la liste d'arguments qu'elle a elle-même reçu.Le premier tuple indique qu'on va nommer localement le processus (à l'échelle d'un nœud Erlang, et pas de tout le cluster éventuel dans le cas d'une application distribuée sur plusieurs machines). On choisit d'utiliser le nom du module comme nom de processus. Le second argument indique un nom de module. OTP se chargera d'appeler la fonction init de ce module en lui transmettant les arguments Args .Et il se trouve que — comme par hasard — on définit également cette fonction init ! Celle-ci a pour but de décrire les processus qui seront supervisés par le processus superviseur. Pour cela, elle doit renvoyer un tuple de 2 éléments : l'atome ok et un autre tuple de 2 éléments pour décrire la stratégie de relancement des processus qui plantent et la liste de ces processus.Ce second tuple va d'abord indiquer les critères de relancement en cas de crash des processus supervisés. On choisit la stratégie de relancement lol (qui consiste à simplement relancer le processus qui plante) avec une On impose au superviseur des limites : le superviseur ne relancera qu'une fois un processus planté toutes les 60 secondes. Le but est bien sûr d'éviter de consommer trop de ressources à relancer un processus qui persiste à planter.Le second élément du tuple est une liste qui décrit les processus qui seront supervisés. Pour le moment il n'en supervise aucun. Voilà ! Comme vous pouvez le voir, il y a beaucoup de choses à dire sur ces 16 malheureuses lignes de code ! Ma prochaine étape est de créer un module qui va me permettre de placer des vaisseaux (chacun représenté par un processus Erlang) sur mon plateau de jeu. Je devrais également prévoir un superviseur pour tous les vaisseaux. L'étape d'après sera de pouvoir faire bouger ces vaisseaux puis de faire persister leur état à l'extinction et au redémarrage du serveur. RE: Mon apprentissage de Erlang - archANJS - 20-10-2012 Très intéressant RE: Mon apprentissage de Erlang - Sephi-Chan - 21-10-2012 Et bien ! Je ne pensais pas à avoir autant de difficulté pour simplement démarrer un nouveau superviseur (qui supervisera les vaisseaux) en tant qu'enfant du superviseur principal. https://github.com/Sephi-Chan/battlefleet/tree/c01d6ad284c57b234bd5b42e13e27342e4538af5 J'ai donc modifié le superviseur principal pour qu'il lance à son tour un superviseur de type ship_sup . L'idée étant de créer un arbre de supervision.
Ici, on peut voir que j'ajoute une définition à la liste des processus enfants du superviseur. Détaillons un peu de quoi est constitué une définition. Déjà, on peut voir que c'est un tuple de 6 éléments.
Il faut ensuite implémenter le module ship_sup .
Il s'agit donc d'un superviseur tout simple qui ne supervise rien pour le moment. Pendant 1 heure, j'ai donc eu un plantage au démarrage de l'application car je pensais que start_link était appelé avec 1 argument (une liste vide). Alors que OTP l'appelait en fait sans argument.Plus qu'à créer un module pour créer des vaisseaux, les faire persister et les faire bouger. |