1 points par GN⁺ 4 시간 전 | 1 commentaires | Partager sur WhatsApp
  • Dans les structures de données, autoriser les séparateurs terminaux quand les éléments sont séparés par des virgules permet de traiter l’ajout, la suppression et le réordonnancement d’éléments comme de simples modifications de texte du même type
  • JSON interdit la virgule après le dernier membre, ce qui crée un cas particulier où il faut aussi modifier la ligne existante lorsqu’on ajoute ou supprime une clé à la toute fin
  • Les enregistrements Haskell, les déclarations de variables TLA+ et les règles Prolog traitent aussi différemment la première ligne et la dernière ligne à cause de la position des séparateurs ou des symboles de fin
  • Python et Go autorisent les virgules terminales, mais pas les virgules initiales, tandis qu’Alloy autorise à la fois les virgules initiales et terminales
  • Les séparateurs terminaux peuvent créer des ambiguïtés de parsing dans les structures de contrôle et servent aussi parfois à distinguer le sens dans la syntaxe de données, comme avec les tuples à un seul élément en Python

Le problème de la dernière virgule en JSON

  • Dans les objets JSON, les virgules entre les membres sont autorisées, mais une virgule après le dernier membre n’est pas autorisée par la grammaire
{
    "a": 1,
    "b": 2,
    "c": 3
}
  • Dans ce même objet, si l’on ajoute une virgule après le dernier membre, comme "c": 3,, cela devient un JSON invalide
{
    "a": 1,
    "b": 2,
    "c": 3,
}
  • Si les virgules terminales étaient autorisées, l’ajout de "x" avant "a" et de "y" après "c" ne nécessiterait que des ajouts de lignes de la même forme
{
+   "x": 0,
    "a": 1,
    "b": 2,
    "c": 3,
+   "y": 4,
}
  • Avec la grammaire JSON actuelle, ajouter une clé en dernière position oblige aussi à ajouter une virgule à l’ancienne dernière ligne "c": 3, ce qui complique davantage la modification
{
+   "x": 0,
    "a": 1,
    "b": 2,
-   "c": 3
+   "c": 3,
+   "y": 4
}
  • Lorsqu’on supprime des éléments, on ne peut pas non plus se contenter d’effacer la ligne concernée : il faut vérifier qu’aucune virgule terminale ne reste sur la dernière ligne
  • Si la valeur d’un objet est elle-même un tableau ou un objet sur plusieurs lignes, les transformations deviennent encore plus complexes à cause de l’absence de virgule terminale

Cas similaires dans d’autres langages

  • Enregistrements Haskell

    • En Haskell, on peut utiliser dans les types d’enregistrements un style de « pseudo-puces » où la virgule est placée au début de chaque ligne
    data Drone = Drone
      { xPos :: Int
      , yPos :: Int
      , zPos :: Int
      }
    
    • Cette approche facilite la modification de la dernière ligne, mais rend la modification de la première plus difficile
  • TLA+

    • En TLA+, une liste de variables ou une séquence sans virgule finale est valide
    VARIABLES a, b, c
    vars ==
    
    • Dans cette même syntaxe, ajouter une virgule après le dernier élément la rend invalide
    VARIABLES a, b, c,
    vars ==
    
    • Lorsqu’on écrit une spécification TLA+, on ajoute souvent des variables de plus haut niveau au fil du temps, ce qui rend cette limitation gênante
    • Le DSL PlusCal n’a pas ce problème et permet d’énumérer les déclarations de variables avec des points-virgules
    (*--algorithm foo {
    variables a; b; c;
    
  • Prolog

    • Les langages logiques comme Prolog n’autorisent pas seulement les séparateurs terminaux : ils utilisent aussi un symbole de fin distinct
    foo(A, B, C) :-
        A = 1, % comma
        B = 2, % comma
        C = 3. % period!
    
    • On peut considérer qu’une manière de voir les choses est de mettre le point final sur une ligne séparée, un peu comme une accolade, mais ce n’est pas la syntaxe standard et cela ne donne toujours pas de séparateur terminal
    foo(A, B, C) :-
        A = 1,
        B = 2,
        C = 3
    .
    

Une meilleure approche

  • Langages qui autorisent les séparateurs terminaux

    • Go autorise une virgule après le dernier élément dans les littéraux de map
    valid := map[string]int{
            "a": 1,
            "b": 2,
            "c": 3,
        }
    
    • Python autorise lui aussi une virgule après le dernier élément dans les dictionnaires
    valid = {
      "a": 1,
      "b": 2,
      "c": 3,
    }
    
    • En Python comme en Go, la virgule peut venir après, mais pas avant ; on ne peut donc pas obtenir un style de puces complet
    invalid = {
        , "a": 1
        , "b": 2
        , "c": 3
    }
    
  • Séparateurs initiaux et Alloy

    • TLA+ autorise les conjonctions et disjonctions initiales, mais pas une forme postfixée comme (a &&)
    // Not TLA+ but the same semantics
    || && a == 1
       && b == 2
    
    || && a == 3
       && b == 4
    
    • Alloy autorise à la fois les virgules initiales et terminales
    sig Valid {
        , a: 1
        , b: 2
    }
    
    sig AlsoValid {
        a: 1,
        b: 2,
    }
    
    • Alloy autorise aussi des séparateurs vides, de sorte qu’une ligne composée de plusieurs virgules seulement reste valide
    sig StillValid {
        ,, a: 1,,
        ,,,,,,,,,
        ,, b: 2,,
    }
    
    • Cette forme est appelée par certains « stuttering »

Objection : ambiguïté de parsing

  • Séparateurs de contrôle en Prolog

    • L’un des arguments contre les séparateurs terminaux est qu’ils peuvent rendre le parsing ambigu
    • En Prolog, terminer une règle par un point rend clair que foo et bar sont deux définitions distinctes
    foo(A, B) :-
        A = 1,
        B = 2.
    
    bar(c).
    
    • Si l’on remplaçait le symbole de fin de règle par une virgule, bar(c) pourrait être interprété comme faisant partie de la définition de foo
    foo(A, B) :-
        A = 1,
        B = 2,
    
    bar(c),
    
    • Dans ce cas, foo pourrait être interprété comme vrai seulement si bar(c) l’est aussi
  • Appels de méthodes en Ruby

    • En Ruby, on peut continuer une chaîne d’appels de méthodes après un retour à la ligne, et le code ci-dessous affiche 5
    puts 3.
         succ().
         succ()
    
    • Si l’on autorisait un séparateur terminal après un appel de méthode, il ne serait plus clair si quux() est une fonction de haut niveau ou une méthode de foo
    foo.
      bar().
      baz().
    
    quux()
    
    • Les exemples de Prolog et Ruby relèvent d’ambiguïtés liées à des séparateurs de contrôle, pas à des séparateurs de données

Exception dans la syntaxe des données : les tuples Python

  • Python utilise les parenthèses à la fois pour le groupement des expressions et pour la définition des tuples
  • (2+3) est traité comme l’évaluation d’une expression et donne un int
>>> x = (2+3)
>>> type(x)

  • (2+3,) est traité comme un tuple à un seul élément à cause de la virgule terminale
>>> x = (2+3,)
>>> type(x)

  • Dans ce cas en Python, le séparateur terminal de données sert à distinguer une expression d’un tuple à un seul élément

1 commentaires

 
GN⁺ 4 시간 전
Avis sur Lobste.rs
  • La grammaire JSON indique qu’on peut mettre une virgule entre deux membres d’un objet, mais pas de virgule finale après un membre. Je ne pense pas qu’on puisse appeler ça une « erreur de conception ». Ce n’était tout simplement pas une option JSON a été conçu vers 2000–2001 comme un sous-ensemble d’ECMAScript 3, et la RFC informative 4627 a été rédigée en 2006. Le fait que JSON soit un sous-ensemble de JavaScript, donc exécutable directement dans le navigateur via eval, était à la fois son objectif et la clé de son succès, et l’API JSON native des navigateurs n’a été ajoutée qu’en 2009 La virgule finale n’a été spécifiée dans ES5 qu’en décembre 2009, donc un JSON avec virgule finale n’aurait de toute façon pas pu exister sans aller à l’encontre de son objectif initial

    • Tu sembles considérer que seules les actions positives peuvent être des erreurs, mais ne rien faire est aussi un choix, donc il est légitime d’appeler cela une erreur également
  • C’est l’un des désagréments que je rencontre le plus souvent en Prolog. Quand on travaille sur un prédicat, ajouter ,true. à la fin est pratique, car ça permet de réordonner ou de commenter les lignes du dessus sans avoir à se soucier du point final

    • De manière similaire, en SQL, notre équipe écrit les clauses WHERE sous la forme where true / and ... / and ..., où les slashs représentent des retours à la ligne Cela permet de modifier facilement n’importe quelle condition sans traitement particulier
  • Zig autorise les virgules finales, et on peut s’en servir pour piloter son formateur non configurable .{1, 2, 3,} devient :

    .{
       1,
       2,
       3,
    }
    

    Et dans un littéral formaté verticalement, retirer la virgule finale signifie qu’on demande un alignement horizontal : .{ 1, 2, 3 } Cela fonctionne aussi ici : définition de type conteneur struct { a: u32, b: u32, }, signature de fonction fn foo(a: u32, b: u32,) void {}, appel de fonction foo(1, 2,); Dans tous ces cas, la virgule finale permet de contrôler le formatage automatique J’aime tellement cette fonctionnalité que je l’ai ajoutée à mon serveur de langage HTML / auto-formateur Before:

    Foo
    
    

    After:

    Foo
    
    

    https://github.com/kristoff-it/superhtml

  • Entièrement d’accord. Une nouvelle langue sans séparateurs finaux perd pour moi quelques points. Pas au point de me faire détester sa syntaxe, mais c’est une petite irritation persistante

    • Même dans les appels de fonction ? foo(1,2,3,4,) ? bar(,1,2,3,4) ? Si foo() accepte un nombre variable de paramètres, alors le dernier argument est-il nil ? Le premier paramètre de bar() est-il nil ?
  • En Clojure et en EDN, la virgule est un espace. On l’utilise généralement par convention entre les paires clé-valeur dans les littéraux de map sur une même ligne, mais elle reste entièrement facultative

    {:a 1 :b 2}
    ;=> {:a 1, :b 2}
    {:a,1,,,,,,,,,,:b,2,} ; if you must
    ;=> {:a 1, :b 2}
    
  • Je pense qu’une grande partie de la tension ici vient du fait que lorsqu’il y a plusieurs clauses sur une même ligne, il faut bien un séparateur d’une manière ou d’une autre

    function(1, 2, 3, 4)
    

    Dans ce cas, ajouter ou supprimer un argument avec une différence ligne par ligne donne toujours quelque chose d’un peu maladroit. Même commenter un seul argument demande un peu d’attention et d’effort. En échange, on accepte la concision de mettre plusieurs éléments sur une ligne Mais dès qu’on commence à mettre une seule clause par ligne, on veut idéalement des différences plus propres et un moyen simple de désactiver des éléments via de simples commentaires de ligne La réponse évidente consiste à traiter les retours à la ligne comme séparateurs standard

    function(
      1
      2
      3
    )
    

    Beaucoup de langages font cela avec ;. Si le style encourage à ne pas mettre plusieurs instructions sur une même ligne, on finit presque par ne plus voir ;. Je ne connais pas vraiment de langage qui fasse la même chose avec les virgules, mais c’est quelque chose que j’ai déjà eu envie d’essayer dans un langage de loisir Bien sûr, Lisp contourne totalement ce problème, car les sous-expressions et les sous-clauses y sont toujours entièrement délimitées, donc il n’y a tout simplement pas de séparateur

  • C’est l’une des raisons pour lesquelles j’aime Lisp, ou plus précisément les S-expressions. Cela fait un détail de moins auquel penser