Les langages de programmation – Partie 4 : simplicité et syntaxe

Dans la suite de mes trois précédents articles consacrés à ce sujet, j’ai commencé à écrire un très long article dans lequel je décortique point par point les différentes caractéristiques des langages de programmation. L’écriture de l’article m’a obligée à structurer mes idées, et m’a aidée à réaliser un certain nombre de choses. Par contre, l’article lui-même est devenu un long truc un peu indigeste, alors j’ai décidé de le mettre à la poubelle.

Pour le moment, je vais juste reprendre une partie de ce que j’avais écrit, concernant la syntaxe des langages.

Une des caractéristiques essentielles d’un langage de programmation, c’est d’être facile à lire et à relire. On doit pouvoir lire du code source comme un linguiste peut lire un texte écrit dans une langue étrangère.
Cela passe notamment par un syntaxe légère, qui ne se mette pas en travers de la lecture, qui ne soit pas inutilement verbeuse et qui reste sans ambiguïté.

Blocs, labels, accolades et indentation

Pour illustrer mon propos, je vais vous montrer comment on code la factorielle récursive dans plusieurs langages procéduraux. Tout d’abord en Pascal :

FUNCTION factorielle (n: shortint) : integer;
BEGIN
    IF n <= 1 THEN
        BEGIN
            WRITELN('End of loop');
            factorielle := 1
        END
    ELSE
        BEGIN
            WRITELN('Loop');
            factorielle := n * factorielle (n - 1)
        END;
END;

Ensuite en BASIC (VB.net, inspiré par le site Developpez.com, © Philippe Laserre) :

Function Factorielle (ByVal N as Long) As Long
    If N=1 Then
        Console.WriteLine("End of loop")
        Return 1
    End If
    Console.WriteLine("Loop")
    Return N * Factorielle(N - 1)
End Function

Et voici le code équivalent en C :

int factorielle(int n)
{
    if (n <= 1)
    {
        printf("End of loop\n");
        return 1;
    }
    printf("Loop\n");
    return n * factorielle(n - 1);
}

Pour terminer cette démonstration, voici la même fonction factorielle en Python :

def factorielle(x):
    if x <= 1:
        print("End of loop\n")
        return 1
    print("Loop\n")
    return x * factorielle(x - 1)

Je vous laisse voir le code équivalent en Ruby, qui conjugue syntaxe légères et typage dynamique comme le Python.

Je crois que j’ai déjà cité mon prof de C en prépa, qui parlait des différences entre les langages créés par des mathématiciens suisses (cf. le Pascal) et ceux créés par des hippies californiens (cf. le C). Et encore, l’exemple serait plus criant si il fallait déclarer des variables ; en Pascal c’est vraiment marrant.

Je vous rappelle que le BASIC est né en 1963, le Pascal en 1970, le C en 1972, le Python en 1990 et le Ruby en 1995. Il y a forcément une notion de modernisme dans la simplification de la syntaxe des langages.

Je ne parle pas de la compacité d’un code par rapport à l’autre, mais bien de sa lisibilité intrinsèque. Le C utilise des accolades, là où le Pascal et le BASIC utilisent du texte (BEGIN, END, End If, End Function). Savoir où placer les point-virgules en fin de ligne n’est pas forcément naturel au premier abord en Pascal, alors qu’en C le comportement est consistant ; le BASIC et le Python se suppriment quant à eux complètement cette contrainte. Pour finir, la déclaration de la fonction avec son type de retour et son paramètre, est assez similaire entre le Pascal et le BASIC ; le C est bien moins verbeux. En Python, il n’y a pas de déclaration du type des fonctions et des variables, c’est encore plus simple à lire (mais moins rigoureux diront certains).

Sur un exemple comme celui-ci, mon avis est assez évident. Le fait d’utiliser des termes textuels peut sembler plus facile à appréhender pour les débutants, mais j’y apporte deux objections : Premièrement, même si on ne vous explique pas le rôle des accolades, le code en C ci-dessus est très facilement compréhensible, et on devine sans peine à quoi elles servent. Deuxièmement, les éléments de syntaxe basique sont intégrés très rapidement par les codeurs débutants ; obliger les développeurs à affronter la pollution visuelle des BEGIN/END pendant tout le reste de leur vie me semble être un mauvais calcul.

Je ne veux pas tant montrer que je préfère la syntaxe du C que de pointer le genre de détails qui font qu’un langage peut − ou non − mettre des bâtons dans les roues des développeurs qui l’utilisent.

On peut remarquer que le Python va encore plus loin en supprimant même les accolades. C’est l’indentation qui détermine les blocs de code. Les experts du langage trouvent ça très positif : le code est plus simple car il ne nécessite plus aucune indication de début et fin de bloc, et cela unifie le formatage du code. Même si je suis sensible à ces arguments, j’y vois personnellement 3 inconvénients : le mélange d’espaces et de tabulations peut générer des bugs indémerdables, à plus forte raison quand plusieurs développeurs interviennent en utilisant des éditeurs de texte différents ; il n’y a rien pour “fermer” les blocs, on a l’impression que les fonctions flottent dans le vide (c’est une question d’habitude, je sais). Mais surtout, dans certains cas, on se retrouve à quand même utiliser des symboles de début et fin de bloc, et à ce moment-là l’indentation arrête d’être significative ; par exemple, quand on construit une liste, le code suivant est fonctionnel, complètement logique, mais brise l’indentation habituelle en Python :

ma_liste = [
       "aaa",
                     "bbb",
  "ccc"
]

Dollars et points-virgules

Les deux autres notions basiques sont l’utilisation du caractère dollar ($) devant les noms de variables et les points-virgules en fin d’expression.

Historiquement, le dollar était inutile dans les langages compilés (Pascal, C), mais traditionnel dans les langages de script (shell, Perl). C’est assez amusant, quand je code en C je me passe très bien des dollars, alors qu’en PHP je trouve ça naturel. C’est clairement une question d’habitude. J’ai tendance à trouver les dollars pratiques pour différencier d’un seul coup d’œil les variables des autres constructions du langage, comme les namespaces, ce qui peut s’avérer utile au fur et à mesure de l’ajout de fonctionnalités dans les langages modernes.

Les points-virgules en fin d’expression m’ont toujours semblés tout aussi naturels. Ils permettent de savoir où se termine l’expression. La plupart du temps il n’y en a pas besoin, car les expressions sont courtes et faciles à lire. Mais parfois − pas si rarement que ça, en fait − on écrit des expressions sur plusieurs lignes, et mon œil cherche instinctivement le point-virgule pour savoir où est la fin.
On peut remarquer que le Go (nouveau langage de Google) n’a pas besoin de point-virgule en fin de ligne ; mais, pour éviter les ambiguïtés, il impose que les accolades ouvrantes soient sur la même ligne que l’instruction if qui précède (cf. documentation). On en arrive donc à une situation très étrange, où pour simplifier l’écriture on se retrouve à la contraindre…

Un dernier point de détail concernant la syntaxe : L’utilisation des parenthèses.
Plusieurs langages n’obligent pas de mettre des parenthèses autour des paramètres d’une fonction. Moi, ça me gêne. Depuis le collège, on est habitué à écrire des fonctions mathématiques ; quand on écrit f(x), on ne se pose aucune question, on sait que f est une fonction et qu’elle prend un paramètre nommé x.

Parenthèses

Idem pour les parenthèses autour de la condition d’un if ou d’un while. Je trouve que ça donne une délimitation visuelle claire. Si on regarde l’exemple suivant (tiré de la documentation du Go) :

for i:= 0; i < flag.NArg(); i++ {
    if i > 0 {
        s+= space
    }
    s+= flag.Arg(i)
}

Je le trouve moins lisible que s’il s’écrivait :

for (i = 0; i < flag.NArg(); i++) {
    if (i > 0)
        s += space;
    s += flag.Arg(i);
}

Mais c’est sûrement encore une question d’habitude.

Conclusion

Bon… Encore un article qui ne sert pas à grand-chose…
Tout ça pour dire que je suis habitué à un style de syntaxe auquel tout le monde est habitué, qui constitue les bases du C, du C++, du Perl, du Javascript, du PHP, …

J’ai un peu l’impression qu’après une période durant laquelle l’informatique s’est rapidement complexifiée avec l’arrivée des premiers gros ordinateurs, la syntaxe des langages a été simplifiée (merci Dennis Ritchie). Sur un cycle de re-complexification, on a abouti au C++, qui est quand même un monument à ce niveau. On a re-simplifié par la suite de différentes manières, plus ou moins réussies (Python, Lua, Java, Ruby, PHP, …).

Les être humains répétant régulièrement leurs erreurs, je pense que nous sommes actuellement dans un nouveau cycle de complexification. Il parait important d’ajouter des fonctionnalités aux langages, alors que ces mêmes fonctionnalités pourraient rester dans des bibliothèques externes ; il semble nécessaire de farcir les frameworks de capacités supplémentaires, alors que les systèmes de plugins qui sont là pour ça. On ajoute plein de choses dans les systèmes d’exploitation, pour finalement leur donner la simplicité des OS mobiles

9 réponses sur “Les langages de programmation – Partie 4 : simplicité et syntaxe”

  1. Quel est alors ton avis au sujet du « syntaxic sugar » qui a le vent en poupe en ce moment dans PHP malgré une opposition relativement forte d’une partie de la communauté qui n’en voit pas l’intérêt et pense que cela peut, utiliser à mauvais escient, rendre le code illisible ?
    Remarque que je te demande ton avis, mais que je pense que c’est un sujet trop « personnel » pour pouvoir en débattre sereinement, voir même qu’il n’est pas possible d’en débattre.
    La syntaxe permet de formaliser la volonté du développeur, et à ce titre n’est qu’un outil comme un autre.
    Sa bonne utilisation dépend donc à la fois de son expérience, de sa rigueur et de son style (je passerais sur l’effet des modes camelCase/CamelCase/_, etc).
    La preuve, avec une même grammaire syntaxique, il est possible d’écrire à la fois un code très lisible et un code parfaitement incompréhensible.
    On retrouve la même distinction que chez les écrivains, ou un livre de Flaubert est plus ardu à lire qu’un livre de Jules Vernes, alors qu’ils sont tous deux en français.
    Je suis bien plus dérangé par des aberrations dans la grammaire de la syntaxe, comme par exemple en PHP le fait qu’il ne soit pas possible de définir une classe portant le nom « class » ou « namespace », alors qu’il est possible d’écrire « $var->class », ou qu’il ne soit pas possible d’utiliser $this dans une fermeture lexicale (même si je comprend les impératifs techniques sous-jacent).

  2. Hum, bonne question, à laquelle il est impossible d’apporter une réponse unique. Tout dépend du sucre syntaxique dont il est question, de ce qu’il apporte en rapidité d’écriture et/ou en facilité de lecture du code, par rapport à ce qu’il peut éventuellement causer comme fonctionnement “non-orthodoxe”.

    Par exemple, je suis un défenseur de l’écriture de tableaux à la JSON/Perl :
    [1, 2, 3]
    {'aa' => 'bb', 'cc' => 'dd'}

    Par contre, je suis un farouche opposant aux annotations dans les commentaires (sauf pour le PHPDoc, évidemment, mais ça n’a pas d’impact direct sur le développement).

    Sinon, il y a des choses que je ne considère pas comme étant du sucre syntaxique, mais plutôt des oublis :
    fonc()[3]

    Et je suis assez d’accord avec toi au sujet des closures de PHP, qui n’ont malheureusement pas le même comportement que les closures en Javascript ou en Lua, qui héritent pour leur part de l’intégralité de l’environnement dans lequel elles ont été déclarées.

    Mon article portait globalement sur les fondements de la syntaxe d’un langage. Le fait d’avoir des marqueurs textuels (blocs se terminant par « end ») en Ruby et en Lua me semble être une régression qui nuit à la compréhension rapide du code.
    Pour reprendre ton exemple des écrivains, si les romans étaient truffés de “Début du paragraphe” et “Fin du paragraphe”, la lecture en serait clairement ralentie. Les accolades me font le même effet que les étoiles qui séparent parfois des groupes de paragraphes dans un roman : on comprends à quoi ça correspond, mais ça n’accroche pas l’œil à la lecture.

  3. Pour revenir sur le sucre syntaxique, c’est vrai qu’il s’agit d’une vision purement personnelle des choses. Mais un bon sucre syntaxique est un sucre complètement optionnel.

    Par exemple, en Go, on peut découper très facilement un tableau avec une écriture de ce genre :
    b = a[12:25]
    Alors qu’en PHP il faudrait forcément écrire :
    $b = array_slice($a, 12, 13);

    Le Lua offre beaucoup de sucre syntaxique. Mais c’est nécessaire puisqu’à la base il n’offre pas grand chose de plus que des tables et des méta-tables. Sans ce sucre, le langage serait presque inexploitable pour faire de la programmation orientée objet. Il doit y avoir des développeurs Lua qui ne savent pas que lorsqu’ils écrivent :
    obj:method(12)
    C’est du sucre syntaxique pour leur éviter d’écrire :
    obj.method(obj, 12)

    Mais si on compare ça avec la syntaxe des langages nativement orientés objets, c’est difficile de lui trouver un goût très sucré 😉

  4. @Mageekguy,
    Je rebondis sur ton message, ce que tu mentionnes encore, pour les termes réservés je trouve cela compréhensible, pour le $this dans les fermetures, c’est une questionerreur de conception plus que de syntaxe.
    D’ailleurs mettre un $this dans une fermeture est très bien accepté par le parseur. C’est au moment de l’exécution que ça coince.

    Ceci-dit je suis bien d’accord avec ce que tu avances mais je trouve encore plus irritant en PHP son inconsistance dans la qualité de sa grammaire.

    Quand on voit les discussions au niveau des patchs pour le déréférencement des tableaux ou la possibilité d’écrire (new class)->method() ou le fait de pouvoir éxécuter une fermeture directement depuis l’attribut d’un objet…

    Ce genre de syntaxe devrait être accepté naturellement par l’analyseur syntaxique. Et plutôt que de perdre son temps sur les différents patchs qui ajoute une rustine à l’analyseur syntaxique, la dev team devrait sérieusement se pencher sur la réécriture du parseur car ici encore plus que dans ton exemple c’est la grammaire elle même tels qu’elle est définie qui n’est pas respectée.

    Php est le seul langage ou je me demande continuellement si je peux ou pas chainer certain élément. Par exemple :

    $class->attr[‘method’]()[‘object’]->item = (new $array[‘class’]())->closure();

    Pourtant même si c’est peu probable d’avoir besoin d’écrire ceci, cela reste passablement banale. Et même quand on essaie de ne pas rentrer dans l’exotique on tombe toujours sur des PARSE ERROR sortie de nul part.

  5. Hello,

    juste pour présenter aux gens erlang, un langage à la fois très expressif et doté d’une syntaxe horripilante.

    La syntaxe et très simple à lire et à écrire mais très ennuyante à modifier à cause des « ant turds ».

    voici la factorielle (non optimisée):

    factorial(0) -> 1;
    factorial(N) -> N * factorial(N-1).

  6. Je ne connaissais pas le terme « ant turd »… on en apprend tous les jours 🙂 (merci !)

    C’est vrai que parfois les langages usent trop de la ponctuation, surtout quand elles est superflue. Comme on dit : « less is more ».
    Python à choisi de remplacer un maximum de ponctuation par des mots réservés, j’ai encore du mal à savoir si c’est une bonne ou mauvaise idée. Plus agréable à l’oeil (avec la coloration syntaxique) mais beaucoup moins instinctif je trouve.

    D’une manière générale c’est la ponctuation (ou son équivalent textuel) qui porte le sens d’une expression et chercher à la retirer
    aboutie souvent à des grammaire ambigue et il faut alors contraindre le langage pour continuer à exploiter cette grammaire (je pense aux appels de méthodes non parenthèses du ruby par exemple).

  7. Allons-y pour d’autres langages qui n’utilisent pas les parentheses comme on le fait dans les langages derives d’Algol (C/C++/Java/blah). Par exemple :

    [code de reference en C d’abord]

    unsigned fact(unsigned n, unsigned acc) {
        return n < 2 ? acc : fact(n - 1, n * acc);
    }
    unsigned factorial(unsigned n) {
        return fact(n, 1);
    }

    [Common LISP]

    (defun fact (n acc)
        (cond ( (<= 'n 0) acc )
            (t (fact (- n 1) (* n acc)))))
    (defun factorial (n) (fact n 1))

    [OCaml ou F# ]

    let rec fact n acc = match n with
        | 0 | 1 -> acc
        | _ -> fact (n - 1) (n * acc)

    let factorial n = fact n 1

    … et pas une seule parenthese la ou on s’y attend en langages imperatifs… 🙂

  8. Ah tiens, 2000 ans après la bataille, j’ai une remarque à faire :

    À propos des parenthèses, Amaury écrivait :
    « Depuis le collège, on est habitué à écrire des fonctions mathématiques ; quand on écrit f(x) on ne se pose aucune question, on sait que f est une fonction et qu’elle prend un paramètre nommé x. »

    Ça, c’est à la fois vrai et faux. Lorsqu’on t’apprend les maths, on te fait passer par plusieurs étapes en fonction de ton niveau scolaire:

    y = a.x + b

    Puis, on t’apprend qu’il y a un domaine d’application et l’application elle-même de la fonction (une sorte « d’instance de fonction » si tu veux) :


    D : [-Inf,+Inf] → [-Inf,+Inf]
    f : x→ a.x + b

    Pas de parenthèses, et on parle bien de maths! La notation
    f(x) = a.x + b
    est généralement apprise en même temps, car la notation f(x) correspond à l’utilisation de la fonction f dans un contexte donné. Par exemple:

    D : [0, +Inf] → [1, +Inf]
    fib: x → { 1 si x < 2,
                 { fib(x-1) + fib(x-2) sinon

    En fait au contraire, plus on regarde les langages « modernes », plus ils s’épurent et se rapprochent du langages mathématique. Par exemple, la notation
    class a < b est exactement la même notation que celle utilisée en théorie des types (donc en gros de l’informatique théorique, donc en gros des maths) pour signifier « la classe a hérite de la classe b ».

    Pour reprendre mon exemple F#/OCaml avec une notation moins « idiomatique » :

    (* Version naïve *)
    let factorial n =
         if n < 2 then
            acc
         else
              n + fact (n - 1)

    (* Version récursive terminale *)
    let factorial_rt n =
         let rec fact n acc =
         if n < 2 then
              acc
         else
              fact (n - 1) (n * acc)

         fact n 1 (* Il faut bien noter que fact est une fonction auxiliaire de factorial_rt *)

    Le code F# est en pratique très proche de la notation mathématique (bien plus que la version C).

  9. (à noter que j’ai fauté dans mon précédent message : il fallait bien entendu écrire ]-Inf,+Inf[, et let rec factorial pour le code OCaml)

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Notifiez-moi des commentaires à venir via email. Vous pouvez aussi vous abonner sans commenter.