Journal chaintools, outils unix avec syntaxe pythonique

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
18
26
juin
2015

Ce qui suit est une traduction des points les plus important du README du projet que l'on peut retrouver sur https://github.com/xcombelle/chaintools. Vous pouvez jouer avec (attention, c'est en pre-0.1 donc la bibliothèque est susceptible d'évoluer

Présentation

Le but de cette bibliothèque est de créer des outils type unix avec une syntaxe pythonique. Le seul mécanisme disponible est le pipeline

Un exemple d'utilisation

chain(cat(),
      filter(lambda s: s and s[0] != '#'),
      map(float),
      sort(),
      head(n=10),
      output())

Le secret d'utilisation est d'une part la fonction chain qui fait un pipeline de générateur et les générateurs qui sont créés par des usines à générateur (que j'appelle outil). Plusieurs outils sont disponibles. Il est facile de créer sont propre outil.

Statut

Cette bibliothèque est en pre-0.1. Il y aura donc probablement des changements.

Pourquoi cette syntaxe

J'ai l'espoir d'avoir tous le pouvoir des outils unix d'une manière pythonique

Pourquoi tous les générateurs utilisent une notation de type fonction:

  • une notation uniforme: pas de question sur les quels ont besoin de parenthèse ou pas
  • les générateurs font quelque chose

Je crois que les usine à générateur ont des caractéristiques correspondant aux outils unix comme les options (en utilisant les paramètre) les code d'erreur (en utilisant les exception). La seule partie manquante est la sortie d'erreur standard.

La principale différence est qu'il n'y a pas un processus par outil (sauf quand on lance un programme externe):
* Il n'y a pas de parallélisation native des outils
* Il n'y a pas besoin de créer un processus pour chaque outil

Un grand avantage des chaintools est que les type ne sont pas limités à des
flux d'octet. Par exemple les outil join et split n'ont pas d'équivalent (à ma
connaissance) parmi les outils unix.

Qu'est-ce qu'on peut importer ?

On peut importer

from chaintools import (
    chain,
    grep,
    run,
    cat,
    output,
    split,
    sort,
    join,
    map,
    filter,
    head,
    tail,
    null,
)

attention, cet import remplace les builtins map et filter.

  • # remarques sur filter et sed

    Posté par  . Évalué à 3.

    Intéressant.
    Je ne sais pas si j'en aurai l'utilité, mais je fais tout de même 2 remarques.

    D'une part un commande de type "sed" serait utile je pense. "sed" est globalement très utilisée.

    D'autre part, noyée au milieu d'autres noms de commandes unix, "filter" est trompeur car il ne correspond pas à la commande unix "filter".

    man filter

    filter - cups file conversion filter interface

    • [^] # Re: remarques sur filter et sed

      Posté par  (site web personnel) . Évalué à 4.

      Merci du commentaire

      Concernant le nom des outils, c'est compliqué:
      - j'ai essayé qu'ils soient le plus clair possible pour des programmeurs python
      - j'ai essayé de limiter le remplacement des builtins (j'y suis pas arrivé pour map et filter)

      Pour la commande sed, je vais faire une commande basée sur re.sub c'est suffisant à votre avis?

      En règle générale, si vous voyez des outils utiles dites le moi.

      Toute remarque est la bienvenue.

  • # Autres outils

    Posté par  (site web personnel) . Évalué à 3. Dernière modification le 27 juin 2015 à 15:38.

    Je ne veux pas casser ton enthousiasme, mais il y a déjà des outils qui font ce genre de chose, et avec un syntaxe plus «shell-compliant»:

    Ceci dit, ça peut être source d'inspiration ou de collaboration.

    A+

    Python 3 - Apprendre à programmer dans l'écosystème Python → https://www.dunod.com/EAN/9782100809141

    • [^] # Re: Autres outils

      Posté par  (site web personnel) . Évalué à 2.

      Je connaissais pas tous ces outils, mais leur point commun est en gros d'appeler les outils shell.

      Ma bibliothèque permet:
      1. de faire un pipeline d'outil en pur python
      2. de faciliter le développement d'outil en pur python.

      Le gros avantage des outils en pur python est qu'ils sont beaucoup plus simple à développer que leur équivalent en C.

      Le gros inconvénient c'est qu'il faut redévelopper les outil et qu'on ne peut pas réutiliser ceux existant. (Ceci étant minoré par le fait qu'ils sont plus simple)

      • [^] # Re: Autres outils

        Posté par  . Évalué à 5.

        de faire un pipeline d'outil en pur python

        Je ne vois pas trop l'intérêt, étant donné qu'ici c'est presque simplement de la composition de fonctions (ici de générateurs).

        De plus, en python la composition est assez « peu naturelle », car tu as du code comme (ce qui se voit sur l'exemple que tu as écrit) :

        ma_fonction_qui_compose (fonction1, fonction2 .....)

        En revanche, un code plus « pythonique » serait d'utiliser chaque appel comme une instruction indépendante :

        a = fonction1
        ... faire des choses 
        b = fonction2 (a)
        ... faire des choses

        Cela permet :

        1. De pouvoir simplement faire autre chose au milieu d'une liste d'appels sans avoir à créer un « outil » spécifique
        2. D'avoir accès à un état global (au moins en lecture) puisque chaque appel peut utiliser les résultats de tous les précédents sans alourdir la syntaxe (alors que si on devait passer l'intégralité des résultats à chaque fonction, ce serait lourd !).

        Alors, bien sûr, il est utile de composer directement : on peut supprimer les structures intermédiaires de données. Mais du coup, essayer de faire une notation infixe serait pas mal si tu veux inciter l'utilisation de ta fonction chain (même si c'est un peu un hack) :

        class Composable:
            def __rshift__ (self, next):
                return compose (next,self)
        
        a = ma_premiere_fonction ()
        b = cat () >> grep () >> sed ()
        c = zip (a,b)
        ...
        • [^] # Re: Autres outils

          Posté par  (site web personnel) . Évalué à 2. Dernière modification le 27 juin 2015 à 15:38.

          La solution "d'utiliser chaque appel comme une instruction indépendante" est la solution que j'avais déjà vu.
          Elle a deux défauts à mon avis:

          • Elle nécessite de créer des variables intermédiaires même quand on en a pas besoin qui se répètent violant le principe DRY
          • Elle inverse le sens de lecture régulièrement (je lis plus facilement de gauche à droite)

          La solution d'utiliser une notation infixe est quasi équivalent, mais son coté hack me fait préférer une notation explicite.

          • [^] # Re: Autres outils

            Posté par  . Évalué à 4.

            coté hack me fait préférer une notation explicite.

            Le côté « hack » c'est juste utiliser un symbole, comme un autre commentaire l'a montré, on peut utiliser un nom de fonction … par exemple chain,
            ce qui donne (mais qui est « moins joli »)

            cat ().chain (sed ()). chain ( etc ...) . chain ( ... )

            Après, il y a d'autres avantages à encapsuler dans un objet le calcul: tu peux contrôler les actions ! Ainsi, tu peux permettre des trucs utiles dans le genre:

            • lancer en mode debug
            • définir des « variables environnement » pour le programme
            • construire d'autres manière de combiner les actions :
              • pour chaque ligne de résultat lancer une nouvelle chaine de commande et fusionner le tout (en parallèle ou non). Cette fonction serait en gros un « concatMap » sur des générateurs
              • exécuter une partie de code si et seulement si la première a échoué
              • etc …

            Quand on y regarde de plus près, on se rend compte qu'une action (cat, sed etc …) c'est simplement une fonction de type action A : () -> [A].
            On peut donc construire tout un tas de combinateurs :

            • chain : action A -> ([A] -> action B) -> action B qui correspond à ta version
            • chainEach : action A -> (A -> action B) -> action B qui lance un appel sur chaque ligne et concatène le résultat
            • alter : action A -> action A -> action A qui essaie la première, et si elle échoue, lance la deuxième (alternative)
            • fmap : (A -> B) -> action A -> action B, applique une fonction « normale » au résultat d'une action
            • apply : action (A -> B) -> action A -> action B (l'utilisation de celle-ci est plus anecdotique, mais on peut en trouver).

            Ensuite, on peut écrire du code comme celui-ci:

            # 1) sélectionne un fichier qui contient une liste de fichiers
            alter (cat ("fichier1"), cat ("fichier2"), erreur ("pas de fichiers"))
            . chainEach (readFile) # lis les fichiers un par un et concatène 
            . chain ( sed (" ... ") ) # applique un filtre (sur la totalité)
            . fmap ( int ) # transforme chaque ligne du résultat en entier 
            . run () # lance le véritable calcul 
        • [^] # Re: Autres outils

          Posté par  (site web personnel) . Évalué à 2. Dernière modification le 27 juin 2015 à 15:38.

          Ce problème de composition a été soulevé par Pandas récemment

          cf

          http://pandas.pydata.org/pandas-docs/stable/basics.html#tablewise-function-application

              # f, g, and h are functions taking and returning ``DataFrames``
              >>> f(g(h(df), arg1=1), arg2=2, arg3=3)

          with the equivalent

              >>> (df.pipe(h)
                     .pipe(g, arg1=1)
                     .pipe(f, arg2=2, arg3=3)
                  )

          L'idée pourrait être reprise

        • [^] # Re: Autres outils

          Posté par  (site web personnel) . Évalué à 3.

          Je ne vois pas trop l'intérêt, étant donné qu'ici c'est presque simplement de la composition de fonctions (ici de générateurs)

          Un intérêt est d'avoir des IO rapides à la jonction des processus puisque la communication est directement prise en charge par l'OS au lieu de tout remonter dans le langage, couper ligne à ligne, écrire immédiatement.

          Sur des applications très intenses au niveau des IO, c'est une différence importante.

          • [^] # Re: Autres outils

            Posté par  . Évalué à 1.

            Un intérêt est d'avoir des IO rapides à la jonction des processus puisque la communication est directement prise en charge par l'OS au lieu de tout remonter dans le langage, couper ligne à ligne, écrire immédiatement.

            Un intérêt pour quel partie ? Faire le calcul entièrement en python ? Faire une librairie pour gérer la composition de fonctions ?

            C'est la deuxième problématique que je trouvais discutable, et je n'ai pas vu/compris comment l'outil proposé améliorait l'IO en python … mis à part l'utilisation de générateurs (qui peut se faire de manière indépendante).

      • [^] # Re: Autres outils

        Posté par  (site web personnel) . Évalué à 3.

        Plummbum, chut et python-sh (cité dans un autre post) appellent en effet les outils du shell par derrière.

        Par contre pyxshell, est un wrapper qui permet de construire des pipes en série avec la syntaxe | et des générateurs, le genre de truc dans lequel tu pourrais insérer tes fonctions shell-like.

        Python 3 - Apprendre à programmer dans l'écosystème Python → https://www.dunod.com/EAN/9782100809141

    • [^] # Re: Autres outils

      Posté par  (site web personnel) . Évalué à 3.

      Encore un autre: python-sh

  • # Gestion des erreurs

    Posté par  (site web personnel) . Évalué à 7.

    Je programme énormément en shell pour écrire des programmes que je considère comme des prototypes et comme je programme aussi en OCaml j'ai commencé à écrire Rashell une bibliothèque un peu comme celle que tu viens de commencer, mais pour OCaml.

    De mon expérience de programmeur shell ce qui me semble important à considérer pour ta bibliothèque pour pouvoir écrire des programmes plus fiables que ce que permet le shell c'est:

    1. La gestion des erreurs – comment sont signalées les erreurs dans une pipeline ?
    2. Le déboguage – comment sont affichées ou transmises les informations importantes aux déboguage lorsque un processus rate?
    3. Comment est implémentée l'option pour réessayer?

    J'ai commencé Rashell comme projet de programmation exploratrice, sans avoir un plan très clair a priori mais il me semble que l'idéal pour résoudre 1.2.3 serait de passer par des monades comme SuccessMonad qui fournissent un environnement d'évaluation aux commandes complexes.

    J'ai vaguement regardé les projets qu'ont cité les autres et je n'ai pas vu comment ils traitaient 1.2.3. C'est d'ailleurs un peu une blague de citer touts ces projets pêle-mêle sans aucune hiérarchisation, certains sont beaucoup plus aboutis que d'autres. Si tu t'intéresses à la programmation système, tous ces projets sont des sources d'inspiration, Tu as peut-être des objectifs différents que ces projets là, ce qui te ferait de la place. Si au contraire il y a déjà un projet qui correspond bien à ton besoin, c'est une super nouvelle car tu as trouvé un projet que tu peux aider! :)

    • [^] # Re: Gestion des erreurs

      Posté par  . Évalué à 1.

      Juste une petite remarque : j'apprécie tes interventions sur le shell, mais pourrais-tu ne pas mettre en gras les noms de programme (shell, sed, etc.) ? Ça casse le rythme de lecture et je pense que nous devinons tous qu'il s'agit de noms de logiciel.

    • [^] # Re: Gestion des erreurs

      Posté par  (site web personnel) . Évalué à 2. Dernière modification le 28 juin 2015 à 16:38.

      • La gestion des erreurs – comment sont signalées les erreurs dans une pipeline ?

      Par des exceptions

      • Le déboguage – comment sont affichées ou transmises les informations importantes aux déboguage lorsque un processus rate?

      Qu'appelles tu informations importantes au déboguage ? Peux tu donner des exemples ?

      • Comment est implémentée l'option pour réessayer?

      J'ai vraiment pas compris ce que tu veux dire. D'habitude quand un script plante le plus simple c'est de le réparer et le relancer non ?

      • [^] # Re: Gestion des erreurs

        Posté par  (site web personnel) . Évalué à 3.

        La problématique du déboguage est la suivante: j'ai un utilitaire qui plante au milieu de la pipeline et je veux comprendre pourquoi. Je veux donc accéder à:

        1. La ligne d'appel de l'utilitaire (argument vector)
        2. L'environnement du processus
        3. Son dossier d'exécution
        4. Le code de retour
        5. Le contenu de stdout
        6. Le contenu de stderr.

        Si je lance la commande rm ou cp je n'ai peut-être pas besoin d'avoir accès à tous ces détails pour comprendre ce qui s'est mal passé, mais dans les cas de programmes complexes, comme par exemple un compilateur, TeX ou METAPOST, accéder à ces informations est primordial.

        Pour la gestion des erreurs, imaginons que j'ai un traitement complexe à effectuer, et qu'au milieu j'ai une erreur. Par exemple j'exécute un script par SSH sur 50 serveurs et le script plante sur deux serveurs. Je peux vouloir que mon programme supervisant l'exécution des scripts me donne la chance de voir ce qui ne va pas sur les deux serveurs puis de réessayer les scripts, et si tout s'est bien passé de continuer le pipeline. Si tout ce que me propose mon programme de supervision c'est d'arrêter le traitement à la première erreur, de nettoyer à la main le bazar qu'il a laissé derrière lui et de tout reprendre depuis zéro… et bien j'aime autant programmer directement en shell.

    • [^] # Re: Gestion des erreurs

      Posté par  . Évalué à 2.

      On est pas vendredi, mais remplacer du shell par du caml … mis à part des améliorations purement techniques de performances, je ne comprend pas l'intérêt syntaxique (voir l'exemple qui est donné …)

      Après, pour la monade, oui, c'est une solution élégante, mais en OCaml, on ne peut pas garantir la pureté des fonctions, du coup, des méthodes de type « retry » et autres pourraient avoir des comportements étranges … (si on ne fait pas attention)

      Quitte à essayer de construire une bibliothèque qui le fait bien, autant coder directement en Haskell, qui possède tout ce qu'il faut pour combiner des fonctions, et qui permet (via l'évaluation paresseuse) de remplacer les générateurs de python par de simples listes … et ce sans avoir une syntaxe immonde

      cat :: String -> IO [String]
      cat f = lines <$> readFile f
      
      -- En traduisant « mot à mot » l'exemple du journal 
      main :: IO ()
      main = cat "fichier" >>= return . filter (\s -> length s > 0 && (s !! 0) /= '#') 
                           >>= return . map toFloat
                           >>= return . sort
                           >>= return . take 10
                           >>= output
      
      -- En ré-écrivant pour utiliser >>= uniquement 
      -- quand nécessaire (préférant (<$>) qui correspond à 
      -- fmap en notation infixe)
      main :: IO ()
      main = (take 10 <$> 
              sort <$> 
              map toFloat <$> 
              filter (\s -> length s > 0 && (s !! 0) /= '#') <$> 
              cat "fichier") >>= output 
      
      -- Enfin, en utilisant fmap f . fmap g = fmap (f . g)
      main :: IO ()
      main = (fmap traitement (cat "fichier")) >>= output
          where 
              traitement = take 10 . sort . map toFloat . filter ()

      Pour résumer, oui l'approche monadique (ici avec IO) a beaucoup d'avantages :

      1. Tu peux automatiquement promouvoir n'importe quelle fonction « normale » en une fonction qui agit sur un résultat de « programme » avec fmap ou <$>, ce qui permet d'utiliser toute fonction déjà écrite dans le langage que tu utilises
      2. Tu peux raisonner sur des axiomes simples pour simplifier le code
      3. Le résultat est encore lisible, plus facile à « comprendre » et surtout à modifier (on voit la structure du programme de manière apparente, et pas seulement au travers de commentaires à chaque étape …)

      Le seul problème est l'intégration du code monadique dans le langage, et en python c'est pas toujours « esthétique », le problème en caml se pose moins puisqu'il est possible de définir des opérateurs infixes.

      Remarque : c'est le genre de syntaxe que j'ai proposé d'utiliser en mettant en place une classe python dans un autre commentaire.

      • [^] # Re: Gestion des erreurs

        Posté par  (site web personnel) . Évalué à 4. Dernière modification le 28 juin 2015 à 18:14.

        Ça doit être mon manque de connaissance en Haskell, le caractère lisible et facile à comprendre ne me saute pas aux yeux. (NB il manque le filter dans le dernier exemple mais c'est tout sauf grave)

        Prenons le dernier exemple qui me parait le plus simple (si on omet l'explication de la fonction cat):

        1. fmap je suppose que ça veut dire function map
        2. >>= Ça doit être un truc avec l'approche monadique
        3. Le . doit être pour la composition de fonction
        4. mais qu'est-ce que le () à la fin ?

        Les point 2 et 4 me paraissent tout à fait inexplicable.

        Ce que je veux dire c'est que votre familiarité avec un langage fait croire que ce langage est clair, mais qu'en fait ce n'est le cas que pour ceux qui sont familiarisés avec ses idiomes.

        PS1: Si quelqu'un veut bien m'expliquer les points mystérieux il est le bien venu

        PS2: J'ai toujours voulu apprendre le Haskell sans jamais y arriver, quelqu'un peut-il m'indiquer un ou des points de départs ?

        • [^] # Re: Gestion des erreurs

          Posté par  (site web personnel) . Évalué à 2.

          Ce que je veux dire c'est que votre familiarité avec un langage fait croire que ce langage est clair, mais qu'en fait ce n'est le cas que pour ceux qui sont familiarisés avec ses idiomes.

          Voilà un exemple de programme pas très clair: je veux afficher Hello, world! et je dois écrire:

          #include <stdio.h>
          #include <stdlib.h>
          
          int main(int argc, char **argv)
          {
              printf("Hello, world!\n");
              return EXIT_SUCCESS;
          }
          

          Par rapport à mon but il y a plein de bruit que je ne comprends pas. Je vois bien que le biscuit c'est le printf(…) mais quel est le rapport entre mon but et le main et les #include, argc, argv ? Ce n'est pas clair parceque beaucoup d'éléments sont sans lien apparent avec mon but. Quand tu lis

          main :: IO ()
          main = (take 10 <$> 
                  sort <$> 
                  map toFloat <$> 
                  filter (\s -> length s > 0 && (s !! 0) /= '#') <$> 
                  cat "fichier") >>= output 
          

          on retrouve un peu de bruit main et output mais la plupart des éléments peuvent facilement être rattachés à la description de la tâche. Comme dans ton exemple

          chain(cat(),
                filter(lambda s: s and s[0] != '#'),
                map(float),
                sort(),
                head(n=10),
                output())
          

          Il y a un petit peu de bruit, le chain mais les autres éléments sont facilement rattachables à la description de la tâche à effectuer.

        • [^] # Re: Gestion des erreurs

          Posté par  . Évalué à 2.

          mais qu'est-ce que le () à la fin ?

          Euh, c'est moi qui ai oublié de le remplacer par (\s -> length s > 0 && (s !! 0) /= '#'), du coup c'est normal que cela n'ai pas de sens …

          fmap je suppose que ça veut dire function map

          En fait, c'est « un peu plus » que cela. Imaginons que tu te donnes un type A quelconque, et que tu puisse manipuler des types [A] (ici c'est liste, mais en fait, ça peut être « programme », IO …). Tu te dis :

          • J'ai tout un tas de fonctions super cool sur le type A
          • J'ai tout un tas de fonctions super cool sur le type [] (indépendantes de A, par exemple head, tail etc …)
          • J'aimerais avoir un moyen de transformer une fonction de A -> B en une fonction de [A] -> [B] : c'est fmap qui le fait

          Alors, pour les listes, fmap = map. Mais en fait c'est beaucoup plus général : du moment que tu as un type paramétré, tu peux espérer trouver une fonction fmap, et qui a le bon goût de vérifier quelques axiomes (fmap id = id ou fmap f . fmap g = fmap (f . g) etc …)

          Donc en fait fmap, c'est « functor map », et c'est un moyen de transformer une fonction « normale » sur une fonction d'un type paramétré.

          Dans le cas présent, c'est IO a qui est un type paramétré, et qui représente un calcul effectué dans un état global.

          >>= Ça doit être un truc avec l'approche monadique

          Exactement, et cela vient très naturellement. Bon, c'est cool de pouvoir appliquer les fonctions normales sur ton type, mais tu as construit un type, c'est pas pour rien : il faut pouvoir faire plus de choses. En plus, tu as souvent des fonctions comme : a -> F b (j'ai remplacé [] par F) et le problème … c'est qu'elles ne se composent pas ! C'est tout ce que >>= fait : donner un moyen de composer ce nouveau type de fonctions.

          • f : a -> F b : prend un type normal, retourne un type complexe
          • g : b -> F c : idem
          • f >=> g : a -> F c : prend un type normal, retourne un type complexe (en effectuant f puis g)

          En fait, pour des raisons pratiques, on utilise plus souvent >>= que >=>, avec a >>= g === (\_ -> a) >>= g.

          Exemple pratique, tu récupères la liste des arguments de la ligne de commande, c'est une action, de type Action [String], tu veux afficher cette liste, c'est une action, mais elle attend la chose à afficher en argument :

          getArg :: Action [String]
          print  :: String -> Action ()
          
          printArgs :: Action ()
          printArgs = getArg >>= (print . concat)

          Donc en réalité, tout le code qui est écrit en exemple est soit

          1. De la composition de fonction
          2. De la transformation de fonction (en fonction sur les IO)

          En espérant que mes explications sont claires :-)

        • [^] # Re: Gestion des erreurs

          Posté par  . Évalué à 2.

          Perso j'ai commencé avec le bouquin "Programming in Haskell" de Graham Hutton. J'ai aussi fait le tutorial Learn yourself a Haskell for great good. Traduction en français ici.
          Sinon il y a le livre "Real World Haskell", je l'ai depuis plusieurs années, il prend un peu la poussière malheureusement. Bien qu'il ait l'air intéressant. (j'ai juste lu le début)

          Au début, on est tenté de faire seulement des petits trucs, parce que sans boucles et sans effets de bord on est un peu perdu. Moi aussi j'ai tenté puis abandonné, puis re-tenté, puis re abandonné, et là je suis en train d'essayer de coder un gros truc avec, je sens que ça vient.

      • [^] # Re: Gestion des erreurs

        Posté par  (site web personnel) . Évalué à 3.

        On est pas vendredi, mais remplacer du shell par du caml … mis à part des améliorations purement techniques de performances, je ne comprend pas l'intérêt syntaxique

        Le but est d'aboutir à une meilleur gestion des erreurs. Le shell est très efficace pour écrire des prototypes. Dans mon poste actuel, j'ai codé un environnement de test unitaires, des outils d'intégration continue et de déploiement, des outils de maintenance de serveur, de configuration de serveur, … en quelques semaines à peine, en shell.

        Mais ces programmes ne sont que des prototypes, qui mériteraient des versions plus robustes.

        Pour l'approche monadique je pensais plutôt à l'autre côté: la description de la tâche et sa supervision plutôt que l'IO. Ce que je vais essayer dans les prochains jours c'est de programmer une monade dans laquelle on peut définir une commande complexe et faire tout le pipe-fitting qu'on veut, définir les options de supervision, etc.

        Après, pour la monade, oui, c'est une solution élégante, mais en OCaml, on ne peut pas garantir la pureté des fonctions, du coup, des méthodes de type « retry » et autres pourraient avoir des comportements étranges … (si on ne fait pas attention)

        Quand on crée et efface des fichiers, qu'on compile des programmes ou qu'on installe des paquets sur un serveur, on est intéressé essentiellement par les effets de bord, et aucun langage ne va magiquement rendre le retry facile à implémenter!

      • [^] # Re: Gestion des erreurs

        Posté par  (site web personnel) . Évalué à 4.

        Quitte à essayer de construire une bibliothèque qui le fait bien, autant coder directement en Haskell

        On n'est pas vendredi, mais remplacer du caml par du haskell ;)

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.