Enfiler des process comme des pâtes dans un collier de nouilles

Rappel de l'épisode précédent

Mon article précédent sur Elixir documentait le passage d'une modélisation de classe en C++ en une modélisation "objet" composée par un module et animé processus GenServer.

Pour créer des logiciels complexes, il s'agit maintenant d'assembler ces "objets" entre eux. De façon ultime, ils s'agit de pouvoir utiliser les patrons de conceptions (design patterns en bon français) en Elixir. Avant d'en arriver là, étudions les relations de bases entre "objets" et la façon de les implémenter en Elixir.

Les relations de bases

La composition

La composition d'un objet avec un ou plusieurs autres objets consiste à inclure les objets composés comme membres de l'objet chapeau et d'utiliser les fonctions des sous-objets pour contribuer à l'implémentation des fonctions de l'objet.

On crée l'objet composeur et dans la fonction init du module en question, on créé les sous-objets. Cette création des sous-objets fils se fait à l'aide de

GenServer.start_link()         

ce qui permet de garder un lien entre le processus courant (le processus qui anime l'objet) et les processus fils. Il suffit ensuite de stocker les pid des processus fils dans la map d'état (contexte) du GenServer.

Si un processus fils s'arrête, le processus père reçoit un signal d'arrêt et si ce signal n'est pas traité, il est arrêté. De même, si le processus père qui anime l'objet principal s'arrête, les fils reçoivent un signal d'arrêt et par défaut libèrent leur ressource et s'arrêtent.

On a bien là les caractéristique d'une composition d'objet.

La référence

Un objet peut également utiliser un autre objet par référence. L'équivalent en Elixir n'est pas évident. Bien entendu, un processus Elixir peut stocker dans une variable d'état les PID d'autres processus mais il n'a pas de garantie que ce PID corresponde à un processus actif.

On peut vérifier cela en utilisant Kernel.alive?() mais ce n'est pas la meilleur option. Il est préférable d'enregistrer les processus à référencer avec des noms

Extrait de la doc de GenServer.start_link()


Name registration

Both start_link/3 and start/3 support the GenServer to register a name on start via the :name option. Registered names are also automatically cleaned up on termination. The supported values are:

  • an atom - the GenServer is registered locally (to the current node) with the given name using Process.register/2.
  • {:global, term} - the GenServer is registered globally with the given term using the functions in the :global module.
  • {:via, module, term} - the GenServer is registered with the given mechanism and name. The :via option expects a module that exports register_name/2, unregister_name/1, whereis_name/1 and send/2. One such example is the :global module which uses these functions for keeping the list of names of processes and their associated PIDs that are available globally for a network of Elixir nodes. Elixir also ships with a local, decentralized and scalable registry called Registry for locally storing names that are generated dynamically.


On peut alors utiliser alors les noms enregistrés ou une registry pour interagir avec le processus et s'assurer qu'il existe bien.

L'héritage

C'est la m ... en Elixir. Le langage n'est pas vraiment fait pour faire de héritage. On va redécouper ce besoin en deux :

  • Définir une interface et implémenter plusieurs version de cette dernière
  • Etendre un objet existant.

La notion d'interface ou d'objet présentant une série de fonctions précises à implémenter est supporté par les behaviours. On peut définir un modèle de module comme ceci

defmodule Animal do
@doc "Definit le comportement d'un animal"

@callback nom(animal :: map() ) :: { atom(), binary() }

@callback mange(animal :: map(), nourriture :: atom ) :: { :ok, map() } | :boude
 
end        

On utilise ensuite le modèle comme suit :

defmodule Chat do
@behaviour Animal

defp viande?(nourriture) do
   nourriture in [ :volaille, :boeuf, :cochon ]
end

@impl
def nom(animal) when is_map(animal) and animal.type == :chat do
   { :chat, animal.nom }
endif

@impl
def mange(animal, nourriture) do
   if viande?(nourriture) do
      { :ok, Map.put(animal, :etat, :repus) }
   else
      :boude
   end
end

end        

Ce n'est pas très utilisable parce qu'il faut connaitre le module à utiliser pour appliquer la bonne fonction. Si l'on veut manipuler des objets sans savoir de quel type ils sont alors il faut complexifier notre code.

On crée un wrapper qui interroge un genserver

defmodule Animal do
@doc "Definit le comportement d'un animal"

def nom(animal) when is_pid(animal) do
   GenServer.call(animal, :nom)
end

def mange(animal, nourriture) when is_pid(animal) do
   GenServer.call(animal, { :mange, nourriture } )
end        

  • On implémente chaque animal comme un GenServer qui répond aux appels :mange et :nom

Ca n'est pas très élégant mais c'est la meilleure façon que j'ai trouvé de manipuler des objets sans exposer leur implémentation.

Extension d'un objet

On touche ici les limites de la transposition de la POO à Elixir. Il n'y a pas vraiment de mécanismes propre. On peut rassembler le code de la classe de base dans un module puis définir les fonction de la classe dérivée dans un autre module en emballant les appels du module de base dans le module dérivé. C'est moche.



Identifiez-vous pour afficher ou ajouter un commentaire

Plus d’articles de Emmanuel Buu

  • Quelques clés de lecture pour le second tour de la législative partielle en Isère

    Quelques clés de lecture pour le second tour de la législative partielle en Isère

    Pas de consigne de vote pour le second tour Fidèle à sa politique lors des entre-deux tours, la candidate Gaelle…

    10 commentaires
  • La bonne année 2024 de Sam Altman

    La bonne année 2024 de Sam Altman

    Dans un article de son blog personnel, au style élégant et minimaliste, Sam Altman, le patron d'OpenAI nous livre de…

  • Passer de C++ à Elixir

    Passer de C++ à Elixir

    Un peu de contexte En avril dernier, je suis devenu technico commercial donc j'ai logiquement arrêter de coder dans le…

  • Sobriété numérique ou green IT ?

    Sobriété numérique ou green IT ?

    Préambule Je me réinteresse à un langage de niche : Elixir un langage qui fonctionne sur la machine virtuelle d'Erlang.…

  • Autoformation à l'IA

    Autoformation à l'IA

    Cela faisait longtemps que je voulais en apprendre plus sur les réseaux de neurone et l'intelligence artificielle. Une…

    3 commentaires
  • Tik Tok, la chine, la neutralité du Net et la sobriété numérique

    Tik Tok, la chine, la neutralité du Net et la sobriété numérique

    La neutralité du Net est un principe de gestion de réseau qui veut que le réseau Internet soit aveugle au contenu de ce…

    3 commentaires
  • 5G - un autre déploiement est possible

    5G - un autre déploiement est possible

    J'ai pu publier plusieurs posts qui donnent à penser que je suis radicalement anti-5G. Ca n'est pas le cas.

    3 commentaires
  • Sobriété numérique - la série

    Sobriété numérique - la série

    Le numérique a longtemps été vu comme un domaine technique vertueux en terme d'environnement. Puis est apparu la notion…

    1 commentaire
  • Les controverses de la 5G

    Les controverses de la 5G

    Avant propos Après avoir découvert ce rapport très complet sur la 5G écrit et diffusé le 1er avril 2020 par Gauthier…

    14 commentaires
  • La 5G et la consommation électrique

    La 5G et la consommation électrique

    Pour référence future voici un extrait pertinent du livre blanc de Huawei sur la consommation électrique d'un réseau…

    2 commentaires

Autres pages consultées

Explorer les sujets