Outils pour utilisateurs

Outils du site


lua

Problèmes de syntaxe

Opérateur ~=

Lorsque tous les langages ou presque utilisent l'opérateur != ou <> pour tester la non égalité, Lua impose l'opérateur ~=, qui est tout à fait inattendu, et donc contraire au principe de moindre surprise.

Pas d'opérateur ternaire

Lua n'a pas d'opérateur ternaire, mais la documentation officielle propose l'utilisation des opérateurs and et or à la place. Le résultat est peu intuitif et une personne non habituée à ce type de contournement butera sur ce genre d'utilisation.

foo = bar and baz or 42 -- En C cela s'écrirait foo = bar ? baz : 42; sauf dans le cas bar == 0

Pas de continue

Le langage possède les mécanismes d'interruption de code break et return, mais bizarrement continue est absent.

Les tables

Le langage se veut doté d’un système de table très puissant, mais bien qu'effectivement pratique dans les cas simples, il est en réalité très limité. Les tables peuvent être indexées par des clés ou par des entiers. La bibliothèque standard propose une fonction permettant de compter les éléments d’une table, mais elle se contente de renvoyer le plus grand entier consécutif. Elle ne fonctionne donc pas dans le cas d'une table indexée par des clés, ou dans le cas où un élément est nil. Si l’on veut une fonction sérieuse pour connaître cette information, on doit donc l’écrire.

foo = 42
baz = "baz"
test = {foo, bar, baz} -- bar est nil
print(#test) -- Affiche 3
test[5]="test"
print(#test) -- Affiche 1

Lua propose également un mécanisme d'itérateur pour parcourir les éléments d'une table, avec les fonctions pair et ipairs, selon que les éléments sont indexés par clés ou par entiers. Mais rien n'est prévu pour pouvoir parcourir une liste à l'envers. D'ailleurs, le parcours à l'endroit pose déhjà problème. Si la notion de sens n'a pas vraiment sa place dans le cas d'une table de hash, elle est tout à fait justifiée dans le cas d'une table indexée. Or s'il manque des éléments à une telle table, ipairs s'arrêtera avant la fin. Écrire une fonction permettant effectivement de parcourir une table utilisée comme un tableau s’avère donc particulièrement laborieux.

foo = 42
baz = "baz"
test = {foo, bar, baz} -- bar est nil
test[5]="test"
test[10]="test"
print("ipairs :")
for i, v in ipairs( test ) do -- La boucle n'affiche que le premier élément
    print(" - "..i.." : "..tostring(v))
end
print("pairs :")
for k, v in pairs( test ) do -- La boucle affiche les éléments dans le désordre
    print(" - "..k.." : "..tostring(v))
end

Les fonctions à nombre d'arguments variable

En conséquence directe du point précédent, les fonctions à nombre d'arguments variable sont susceptible de manquer des arguments dans le cas où l'un d'entre eux est nil, comme le montre l'exemple suivant, directement tiré de la documentation.

  function test(...)
      local printResult = ""
      for i,v in ipairs(arg) do
        printResult = printResult .. tostring(v) .. "\t"
      end
      printResult = printResult .. "\n"
      print(printResult)
  end
 
  test("foo", nil, "baz") -- N'affiche que "foo"

Les chaînes de caractères

Lua ne permet pas d'itérer dans des chaînes contrairement à la plupart des langages. Vouloir traiter une chaîne caractère par caractère nécessite de faire de lourdes opérations.

En contrepartie il se veut doté d’une gestion de chaînes puissantes. Pourtant la documentation explique que les expressions rationnelles ne sont pas POSIX pour des raisons de quantité de code nécessaire. Au final filtrer des motifs devient vite pénible dès que l’on sort des cas d’école, et l’ajout d’une nouvelle syntaxe maison vient perturber le principe de moindre surprise.

Les fonctions proposées par la bibliothèque standard sont de plus nommées de façon peu homogène :

  • string.find renvoie les indices de début et fin d'une sous-chaîne dans une chaîne
  • string.sub renvoie une sous-chaîne spécifiée par des indices
  • string.gfind renvoie les sous-chaînes correspondant à un motif
  • string.gsub remplace dans une chaîne un motif par une autre sous chaîne

Comme on le voit, le format de retour de string.find et string.gfind est différent, lorsque seul les arguments devraient l'être, et string.gsub n'a tout simplement plus rien à voir.

Problèmes d'existence des données

Portée globale par défaut

Par défaut, tout élément est global. Si l'on souhaite limiter la portée au contexte dans lequel on se trouve, il faut le demander explicitement avec le mot clé local. Même un langage permissif comme le PHP considère que l'on souhaite une portée locale par défaut, et requiert une demande explicite pour avoir une portée globale.

Création de variable implicite

La création d'une variable est implicite, y compris pour une évaluation. Ainsi le code suivant est parfaitement valide :

bar = "bar"
foo = baz -- baz n'existe pas, et l'évaluation renvoie nil

En pratique cela signifie qu'une faute de frappe dans le nom d'une variable ne sera détectée qu'à l'exécution de la branche de code concernée.

Problèmes de typage

Typage dynamique

Lua est typé dynamiquement, et une variable peut parfaitement recevoir successivement des valeurs de types différents.

On ne peut poser aucune contrainte sur le type d'un élément, par exemple dans le cas des arguments d'une fonction, mais juste le vérifier à l'exécution, ce qui coûte une comparaison de chaînes de caractères.

Pas d'opérateur booléen

Lua dispose dans sa syntaxe d'opérateurs and et or, mais ceux-ci ne sont certainement pas booléens. Il acceptent en effet toute valeur (comme expliqué plus haut, on ne peut pas imposer un type attendu), et renvoient celle des deux qui est déterminante, et ont donc un type de retour dépendant des arguments.

Pas de const

Lua ne permet pas de définir des constantes, et plus généralement il n'existe aucune notion de protection des données : n'importe qu'elle partie du code peut faire n'importe quoi avec les données de n'importe quelle autre partie.

Du faux objet

Lua ne propose pas le paradigme objet nativement. Certains traits du langage permettent de donner l'illusion d'une programmation objet, et plusieurs bibliothèques se veulent capables d'apporter cette fonctionnalité au langage. Mais en pratique, la bibliothèque Yaci par exemple, qui est pourtant la plus aboutie, souffre de gros défauts :

  • Le constructeur d'une super classe doit être appelé explicitement.
  • L'espace de nom est différent entre une classe mère et sa classe fille, et si la classe fille a accès aux éléments de la classe mère, l'inverse n'est pas vrai. Cela pourrait sembler naturel, mais a en pratique a pour conséquence que si l'on n'initialise pas tous les membres de la classe mère dans son constructeur, ils vont être créés implicitement à la première affectation dans une méthode, et donc dans l'espace de nom de la classe depuis laquelle la méthode aura été appelée. S'il s'agissait d'une classe fille, la classe mère les verra non initialisés et ne pourra pas les manipuler. Pire : si, dans le cas courant d'une méthode surchargée par exemple, on appelle explicitement une méthode de la classe mère et que cette dernière affecte un membre, deux membres vont coexister, l'un dans la classe mère, l'autre dans la classe fille.
  • Encore une fois il n'y a pas de notion de protection des données en Lua, et donc toute distinction entre donnée publique et privée est impossible. Au mieux on peut s'interdire de manipuler des attributs depuis l'extérieur d'une classe, mais en pratique rien ne l'empêche, et surtout pas une faute de frappe.

Ces différents points ont pour conséquence que l'héritage est finalement absent de cette tentative d'objet. Il est donc possible d'avoir un code plus organisé et plus propre qu'une écriture purement impérative, mais il ne faut pas croire un seul instant qu'il s'agit là d'objet.

lua.txt · Dernière modification: 2013/05/06 14:28 (modification externe)