1 puntos por GN⁺ 4 시간 전 | 1 comentarios | Compartir por WhatsApp
  • En las estructuras de datos, si se permiten los separadores finales, agregar, eliminar o reordenar elementos se puede manejar con el mismo tipo de cambio de texto
  • JSON prohíbe la coma después del último miembro, así que al agregar o eliminar una clave al final aparece un caso especial que obliga a modificar también la línea existente
  • Los registros de Haskell, las declaraciones de variables en TLA+ y las reglas de Prolog también hacen que los cambios en la primera línea y en la última se manejen de forma distinta por la posición del separador o el símbolo de terminación
  • Python y Go permiten comas finales, pero no comas iniciales, mientras que Alloy permite tanto comas iniciales como finales
  • Los separadores finales pueden crear ambigüedad de parsing en estructuras de control y también se usan para distinguir significado en sintaxis de datos, como en las tuplas de un solo elemento en Python

El problema de la coma final en JSON

  • En los objetos JSON se permiten comas entre miembros, pero la coma después del último miembro no está permitida por la gramática
{
    "a": 1,
    "b": 2,
    "c": 3
}
  • En ese mismo objeto, si se agrega una coma después del último miembro, como en "c": 3,, el JSON deja de ser válido
{
    "a": 1,
    "b": 2,
    "c": 3,
}
  • Si se permitieran comas finales, al agregar "x" antes de "a" y "y" después de "c" solo haría falta añadir líneas con la misma forma
{
+   "x": 0,
    "a": 1,
    "b": 2,
    "c": 3,
+   "y": 4,
}
  • Con la gramática actual de JSON, al agregar una clave en la última posición también hay que poner una coma en la línea final existente "c": 3, lo que hace el cambio más complejo
{
+   "x": 0,
    "a": 1,
    "b": 2,
-   "c": 3
+   "c": 3,
+   "y": 4
}
  • Al eliminar elementos tampoco basta con borrar esa línea: hay que comprobar que no quede una coma final en la última línea
  • Si el valor del objeto es en sí un arreglo u objeto de varias líneas, la transformación se vuelve más compleja por la ausencia de coma final

Casos similares en otros lenguajes

  • Registros de Haskell

    • Haskell permite usar en los tipos de registro un estilo de “viñetas parciales” donde la coma va al inicio de cada línea
    data Drone = Drone
      { xPos :: Int
      , yPos :: Int
      , zPos :: Int
      }
    
    • Este enfoque facilita cambiar la última línea, pero hace más difícil cambiar la primera
  • TLA+

    • En TLA+, una lista de variables o una secuencia sin coma al final es válida
    VARIABLES a, b, c
    vars ==
    
    • En esa misma sintaxis, si se agrega una coma después del último elemento, deja de ser válida
    VARIABLES a, b, c,
    vars ==
    
    • Al escribir especificaciones en TLA+, es común seguir agregando variables de nivel superior, así que esta restricción resulta incómoda
    • En el DSL PlusCal no existe el mismo problema, y las declaraciones de variables se pueden listar con punto y coma
    (*--algorithm foo {
    variables a; b; c;
    
  • Prolog

    • Los lenguajes lógicos como Prolog no solo no permiten separadores finales, sino que además usan un símbolo de terminación distinto
    foo(A, B, C) :-
        A = 1, % comma
        B = 2, % comma
        C = 3. % period!
    
    • Se podría ver el estilo de poner el punto final en una línea aparte como algo parecido a las llaves, pero no es la sintaxis estándar y tampoco resuelve el tema de los separadores finales
    foo(A, B, C) :-
        A = 1,
        B = 2,
        C = 3
    .
    

Una mejor forma

  • Lenguajes que permiten separadores finales

    • Go permite una coma después del último elemento en los literales de mapa
    valid := map[string]int{
            "a": 1,
            "b": 2,
            "c": 3,
        }
    
    • Python también permite una coma después del último elemento en los diccionarios
    valid = {
      "a": 1,
      "b": 2,
      "c": 3,
    }
    
    • En Python y Go la coma puede ir después, pero no antes, así que no se puede lograr un estilo completo de viñetas
    invalid = {
        , "a": 1
        , "b": 2
        , "c": 3
    }
    
  • Separadores iniciales y Alloy

    • TLA+ permite conjunción con prefijo y disyunción con prefijo, pero no permite la forma posfija como (a &&)
    // Not TLA+ but the same semantics
    || && a == 1
       && b == 2
    
    || && a == 3
       && b == 4
    
    • Alloy permite tanto comas iniciales como comas finales
    sig Valid {
        , a: 1
        , b: 2
    }
    
    sig AlsoValid {
        a: 1,
        b: 2,
    }
    
    • Alloy también permite separadores vacíos, por lo que incluso líneas con solo varias comas siguen siendo válidas
    sig StillValid {
        ,, a: 1,,
        ,,,,,,,,,
        ,, b: 2,,
    }
    
    • A este tipo de forma algunas personas la llaman “stuttering

Contraargumento: ambigüedad de parsing

  • Separadores de control en Prolog

    • Uno de los argumentos contra los separadores finales es que pueden volver ambiguo el parsing
    • En Prolog, si una regla termina con un punto, queda claro que foo y bar son definiciones separadas
    foo(A, B) :-
        A = 1,
        B = 2.
    
    bar(c).
    
    • Si el símbolo de cierre de la regla se cambiara por una coma, bar(c) podría interpretarse como parte de la definición de foo
    foo(A, B) :-
        A = 1,
        B = 2,
    
    bar(c),
    
    • En ese caso, foo podría interpretarse como verdadero solo cuando bar(c) también sea verdadero
  • Llamadas a métodos en Ruby

    • En Ruby se puede seguir una cadena de métodos incluso después de un salto de línea, y el siguiente código imprime 5
    puts 3.
         succ().
         succ()
    
    • Si se permitieran separadores finales después de llamadas a métodos, no quedaría claro si quux() es una función de nivel superior o un método de foo
    foo.
      bar().
      baz().
    
    quux()
    
    • Los casos de Prolog y Ruby se relacionan con ambigüedades en separadores de control, no con separadores de datos

Excepción en sintaxis de datos: tuplas de Python

  • Python usa paréntesis tanto para agrupar expresiones como para definir tuplas
  • (2+3) se trata como evaluación de una expresión y produce un int
>>> x = (2+3)
>>> type(x)

  • (2+3,) se trata como una tupla de un solo elemento por la coma final
>>> x = (2+3,)
>>> type(x)

  • En este caso de Python, el separador final de datos sirve para distinguir entre una expresión y una tupla de un solo elemento

1 comentarios

 
GN⁺ 4 시간 전
Comentarios en Lobste.rs
  • La gramática de JSON dice que puede haber comas entre dos miembros de un objeto, pero no una coma final después de un miembro. No creo que se le pueda llamar un “error de diseño”, porque no era una opción JSON se creó alrededor de 2000–2001 como un subconjunto de ECMAScript 3, y el RFC 4627 informativo se escribió en 2006. Como era un subconjunto de JavaScript, el objetivo de JSON —y una clave de su éxito— era que funcionara directamente en el navegador con eval; la API nativa de JSON en navegadores recién se añadió en 2009 La ES5 recién especificó las comas finales en diciembre de 2009, así que un JSON con comas finales simplemente no podía existir sin contradecir su propósito original

    • Parece que solo se consideran errores las acciones activas, pero no hacer nada también es una elección, así que me parece válido llamarlo error
  • En Prolog, esta es una de las molestias que más seguido me encuentro. Cuando trabajo con un predicado, me resulta cómodo dejar ,true. al final, así no tengo que preocuparme por el punto final al reordenar o comentar líneas de arriba

    • De forma parecida, en SQL nuestro equipo usa la cláusula WHERE como where true / and ... / and .... Aquí la barra significa salto de línea Así cualquier condición se puede editar fácilmente sin darle un trato especial
  • Zig permite la coma final, y se puede usar para controlar un formateador no configurable .{1, 2, 3,} se convierte en esto

    .{
       1,
       2,
       3,
    }
    

    Y si quitas la coma final en un literal formateado en vertical, pasa a significar que pides alineación horizontal: .{ 1, 2, 3 } También funciona aquí: definiciones de tipos contenedor struct { a: u32, b: u32, }, firmas de función fn foo(a: u32, b: u32,) void {}, llamadas a función foo(1, 2,); En todos esos casos puedes controlar el autoformato con la coma final Me gustó tanto esta función que la añadí también a mi servidor de lenguaje/formateador automático de HTML Before:

    Foo
    
    

    After:

    Foo
    
    

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

  • Coincido 100%. Personalmente, un lenguaje nuevo sin separadores finales me baja un poco la nota. No al punto de odiar su gramática, pero sí se siente como una pequeña molestia persistente

    • ¿También en llamadas de función? foo(1,2,3,4,)? bar(,1,2,3,4)? Si foo() permite una cantidad variable de parámetros, ¿el último argumento es nil? ¿El primer parámetro de bar() es nil?
  • En Clojure y EDN, la coma es espacio en blanco. Normalmente se usa por convención entre pares clave-valor en literales de mapa en una misma línea, pero es totalmente opcional

    {:a 1 :b 2}
    ;=> {:a 1, :b 2}
    {:a,1,,,,,,,,,,:b,2,} ; if you must
    ;=> {:a 1, :b 2}
    
  • Creo que una gran parte de la tensión aquí viene de que cuando hay varias cláusulas en una misma línea, de algún modo hace falta un separador

    function(1, 2, 3, 4)
    

    En esos casos, agregar o quitar argumentos según diferencias por línea casi siempre se ve incómodo. Incluso comentar un solo argumento requiere algo de cuidado y esfuerzo. A cambio aceptas la concisión de poner varios elementos en una misma línea Pero cuando empiezas a dejar una sola cláusula por línea, idealmente quieres diferencias más limpias y una manera sencilla de desactivar cosas con comentarios de línea simples La respuesta obvia es tratar los saltos de línea como separadores estándar

    function(
      1
      2
      3
    )
    

    Muchos lenguajes hacen esto con ;. Si tu estilo desalienta poner varias sentencias en una sola línea, casi no llegas a ver ;. No conozco lenguajes que hagan exactamente lo mismo con comas, pero sí es algo que alguna vez quise probar en un lenguaje de hobby Claro, Lisp evita este problema porque las subexpresiones y subcláusulas siempre están completamente delimitadas, así que no hay separadores en absoluto

  • Esta es una de las razones por las que me gusta Lisp, o más precisamente las S-expresiones. Es un detalle menos en el que pensar