Journal TapTempo en une ligne

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
45
19
juil.
2019

En lisant une dépêche du moment je découvre en passant le logiciel TapTempo. En lisant la description du comportement, par réflexe je me suis dit "tiens ça doit rentrer dans une ligne un algo de ce genre".

perl -ne 'BEGIN{use Time::HiRes qw/gettimeofday/} push(@t,0+gettimeofday()); shift(@t) if @t>5; printf("%3.0f bpm",60*(@t-1)/($t[-1]-$t[0])) if @t>1'

Bon ça ne marque qu'avec la touche Entrée, pas avec n'importe quelle touche, mais ça affiche bien le BPM en moyennant les 5 dernières valeurs. Et ça rentre dans un tweet (sans compter l'invocation de l'interpréteur : 138 caractères).

Je décompose le mouvement :

perl -ne : le flag -e indique que le code va être passé dans l'argument suivant; le flag -n indique que ce code sera exécuté pour chaque ligne reçu via stdin, et dans notre cas la source sera la console et la fin de ligne par défaut le retour chariot.

BEGIN{use Time::HiRes qw/gettimeofday/} : de loin la partie la plus verbeuse, mais il faut bien appeler les choses par leur nom. Le module Time::HiRes et un "Perl core module", donc si vous avez l'interpréteur vous êtes sûr que vous avez ce module (sauf sur les distros qui aiment vous embrouiller comme CentOS/RHEL). Ensuite on importe la fonction qui nous intéresse dans l'espace de nommage de notre programme pour ne pas avoir à l'invoquer avec la syntaxe java-esque Time::HiRes::gettimeofday. Notez qu'il manque l'aliasing à la Python pour renommer une fonction importée, j'aurai bien aimé renommer gettimeofday en gtod (ça existe, mais pas en core module à ma connaissance). Comme le one-liner est invoqué à chaque ligne d'input, on limite l'invocation de cet import à la compilation avec BEGIN (oui : à la compilation, pas au début de l'exécution, mais dans ce billet vous avez le droit d'ignorer ce détail).

push(@t,0+gettimeofday()) : j'utilise la liste @t sans l'intialiser, c'est MAL mais c'est un one-liner hein, tout est permis. Mais le comportement est défini, je sais que la liste est initialement vide, je gère les risques. On relève l'heure et on l'empile à la fin de la liste. Le 0+ est un hack totalement légal et courant pour forcer le résultat de la fonction à être une valeur numérique. Sinon ça renvoie un tuple, cf. ligne 12 de l'exemple sur la doc.

shift(@t) if @t>5 : je limite la taille de la liste à 5 éléments en retirant le premier élément si nécessaire (ma liste est donc utilisée en FIFO, je garde les 5 dernières mesures sur une fenêtre de temps glissante). Notez que l'opérateur de comparaison est numérique (en Perl on donc le pendant alphanumérique qui est gt) et force Perl à considérer @t comme le nombre d'éléments de la liste au lieu de la liste elle-même. Perl est un langage où le contexte intervient tout le temps et si vous dépassez vos premières impressions (c'est ambigu/magique/nimp), vous pourrez découvrir que c'est un moyen d'expressivité dont on a du mal à se passer dans les autres langages.

60*(@t-1)/($t[-1]-$t[0]) : si vous voulez faire la moyenne des deltas entre chaque mesure successive, vous constaterez facilement que le calcul se résume à faire le delta uniquement entre la première et dernière mesure (et cet algo simple m'arrangeait, même si ergonomiquement on tend à préférer des moyennes exponentiellement lissées comme par ex. la fameuse mesure du load Unix). En perl l'index -1 d'une liste permet d'obtenir le dernier élément. Au fait, si on a N échantillons, on a N-1 mesures, d'où le facteur @t-1. Pour passer des Hz obtenus naturellement à partir du chronométrage en secondes au BPM, ben on multiplie par 60.

printf("%3.0f bpm",...) if @t>1 : il faut bien afficher le résultat, on le justifie sur 3 colonnes et on demande 0 décimales. Détail amusant, on n'affiche pas le retour chariot car votre console va afficher celui que vous avez saisi, et il y a donc une race condition (mais je vous souhaite bonne chance pour appuyer plus vite sur Entrée que l'interprétation de la ligne par Perl). Il faut au moins deux échantillons pour avoir une mesure, et dans le monde merveilleux des entiers, "avoir au moins deux" peut aussi se dire "avoir strictement plus que un". J'ai pris la notation la plus courte.

Voilà. Et du coup faire un one-liner qui donne un journal de 100 lignes, c'est fatiguant en fait.

PS : merci à François Mazen de m'avoir suggéré d'en faire un journal. Il m'a rappelé qu'il existait des portages Perl de TapTempo :
* https://linuxfr.org/users/astaoth/journaux/portage-de-taptempo-en-perl
* https://linuxfr.org/users/glaeken/journaux/portage-de-taptempo-en-perl6

  • # Suggestions

    Posté par  . Évalué à 10.

    Puisque le but est (visiblement) de faire le plus court possible, pas forcément le plus lisible, je propose quelques modifications.

    perl -ne 'BEGIN{use Time::HiRes qw/gettimeofday/} push(@t,0+gettimeofday()); shift(@t) if @t>5; printf("%3.0f bpm",60*(@t-1)/($t[-1]-$t[0])) if @t>1'
    Tout d'abord on peut économiser tout le bloc BEGIN avec -M. Bon remplacer BEGIN{} par -M'', ça ne fait pas gagner énormément de caractères, mais je trouve ça beaucoup plus lisible.

    perl -M'Time::HiRes qw/gettimeofday/' -ne 'push(@t,0+gettimeofday()); shift(@t) if @t>5; printf("%3.0f bpm",60*(@t-1)/($t[-1]-$t[0])) if @t>1'
    Ensuite on peut gagner quelques caractères en supprimant le push (qui rajoute un élément en fin de liste) par l'expression $t[@t]=. Le += permet de conserver (implicitement) la notation 0+.

    En contexte scalaire, @t retourne le nombre d'élément du tableau. Comme le tableau commence à 0, écrire à l'indice @t revient à rajouter un élément.

    perl -M'Time::HiRes qw/gettimeofday/' -ne '$t[@t]+=gettimeofday(); shift(@t) if @t>5; printf("%3.0f bpm",60*(@t-1)/($t[-1]-$t[0])) if @t>1'
    On peut également remplacer @t-1 par $#t. Et remplacer @t>1 par $#t (implicitement $#t!=0, mais comme on sait que le nombre d'éléments d'un tableau ne peut être négatif, c'est équivalent à $#t>0 et donc équivalent à @t>1).

    Au passage on supprime plein de parenthèses inutiles.

    perl -M'Time::HiRes qw/gettimeofday/' -ne '$t[@t]+=gettimeofday; shift @t if @t>5; printf "%3.0f bpm",60*$#t/($t[-1]-$t[0]) if $#t'

    • [^] # Re: Suggestions

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

      Belle optim' !

      Je ne savais pas qu'avec -M on pouvait passer les args à l'importeur, du coup je l'avais zappé, mais bon sang c'est bien sûr !

      Je ne connaissais pas l'alias de push en $t[@t]=, j'adopte (pour les oneliners…). Et le += qui force un contexte scalaire ET promet une valeur indéfinie en 0 en mode non-strict, c'est bien recherché :).

      J'évite $#t qui a un facteur brainfuck très élevé (le rapport gain de compression vs. perte de lisibilité me semble très défavorable), mais évidemment c'est valide et plus court.

      • [^] # Re: Suggestions

        Posté par  . Évalué à 3. Dernière modification le 20 juillet 2019 à 23:48.

        Je ne connaissais pas l'alias de push en $t[@t]=, j'adopte (pour les oneliners…).

        Sauf si le but est d’écrire le code le plus court possible, il est sûrement plus rapide (et accessoirement plus clair) d’écrire push @t, : c’est à peine plus long et ça nécessite moins AltGr (sauf à taper en Qwerty).

        Pour le reste, je suis content aussi d’avoir appris qu’on peut passer des paramètres avec -M (je ne m’étais jamais posé la question, mais bon, ça me servira peut-être un jour).

        « Le fascisme c’est la gangrène, à Santiago comme à Paris. » — Renaud, Hexagone

  • # Et la version code golfé en brainfuck?

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

    Moué, c'est pas impressionnant tant que c'est pas en brainfuck.

  • # .

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

    l'invoquer avec la syntaxe java-esque Time::HiRes::gettimeofday

    Tu ne dois pas connaître le langage que tu trolles pour appeler ça une syntaxe Java-esque (oui je sais maintenant il y a :: pour manipuler des fonctions comme first-class citizen depuis Java 9 ou 10, mais ce n'est pas le cas d'usage ici).

    • [^] # Re: .

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

      Si je dis ruby-esque ça te va ? :)

      • [^] # Re: .

        Posté par  . Évalué à 1.

        Et personne ne s'est encore lancé pour une version en grot ou ubu ?

  • # Correction du lien cassé vers la dépêche en une ligne

    Posté par  . Évalué à 6. Dernière modification le 19 juillet 2019 à 17:14.

    echo "https://linuxfr.org/news/debian-10-buster-une-distribution-qui-a-du-chien]" | perl -pe 's/\]//'
  • # troll

    Posté par  . Évalué à -6.

    C'est malheureusement à cause de journaux comme ça que Perl s'est tapé une réputation de langage illisible et inmaintenable 😣

    Je suis tellement triste que personne n'ai voulu rénover ce langage incroyable….

Suivre le flux des commentaires

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