1 pontos por GN⁺ 4 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • Em estruturas de dados, quando itens são separados por vírgulas, permitir separadores à direita faz com que adicionar, remover ou reordenar itens seja tratado como o mesmo tipo de alteração textual
  • O JSON proíbe a vírgula após o último membro, criando um caso especial em que adicionar ou remover uma chave no fim exige modificar até a linha já existente
  • Registros em Haskell, declarações de variáveis em TLA+ e regras em Prolog também tratam de forma diferente alterações na primeira linha e na última linha por causa da posição do separador ou do símbolo de término
  • Python e Go permitem vírgula à direita, mas não permitem vírgula à esquerda; já o Alloy permite vírgula à esquerda e à direita
  • Separadores à direita podem criar ambiguidades de parsing em estruturas de controle e também são usados para distinguir significado em sintaxes de dados, como na tupla de um único elemento em Python

O problema da última vírgula no JSON

  • Em objetos JSON, vírgulas entre membros são permitidas, mas a vírgula após o último membro não é permitida pela gramática
{
    "a": 1,
    "b": 2,
    "c": 3
}
  • No mesmo objeto, adicionar uma vírgula após o último membro, como em "c": 3,, torna o JSON inválido
{
    "a": 1,
    "b": 2,
    "c": 3,
}
  • Se vírgulas à direita fossem permitidas, adicionar "x" antes de "a" e "y" depois de "c" exigiria apenas o mesmo tipo de adição de linha
{
+   "x": 0,
    "a": 1,
    "b": 2,
    "c": 3,
+   "y": 4,
}
  • Na gramática atual do JSON, ao adicionar uma chave na última posição, é preciso colocar uma vírgula também na antiga última linha "c": 3, o que torna a alteração mais complexa
{
+   "x": 0,
    "a": 1,
    "b": 2,
-   "c": 3
+   "c": 3,
+   "y": 4
}
  • Ao remover elementos, também não basta apagar apenas aquela linha: é preciso verificar se não sobra uma vírgula à direita na última linha
  • Se o próprio valor do objeto for um array ou objeto multilinha, a transformação causada pela ausência de “vírgula à direita” fica ainda mais complexa

Casos parecidos em outras linguagens

  • Registros em Haskell

    • Haskell permite usar, em tipos de registro, o estilo de “quase bullet points”, colocando a vírgula no início de cada linha
    data Drone = Drone
      { xPos :: Int
      , yPos :: Int
      , zPos :: Int
      }
    
    • Esse estilo facilita alterar a última linha, mas torna mais difícil alterar a primeira
  • TLA+

    • Em TLA+, uma lista de variáveis e sequências sem vírgula final é válida
    VARIABLES a, b, c
    vars ==
    
    • Na mesma sintaxe, colocar uma vírgula após o último item torna a expressão inválida
    VARIABLES a, b, c,
    vars ==
    
    • Ao escrever especificações em TLA+, é comum continuar adicionando variáveis de nível superior, então essa limitação é incômoda
    • No DSL PlusCal, esse problema não existe, e declarações de variáveis podem ser listadas com ponto e vírgula
    (*--algorithm foo {
    variables a; b; c;
    
  • Prolog

    • Linguagens lógicas como Prolog não apenas não permitem separadores à direita, como também usam um símbolo de término separado
    foo(A, B, C) :-
        A = 1, % comma
        B = 2, % comma
        C = 3. % period!
    
    • Dá para ver isso como algo parecido com chaves colocando o ponto final em uma linha separada, mas isso não é sintaxe padrão e ainda não oferece separadores à direita
    foo(A, B, C) :-
        A = 1,
        B = 2,
        C = 3
    .
    

Uma forma melhor

  • Linguagens que permitem separadores à direita

    • Go permite vírgula após o último item em literais de mapa
    valid := map[string]int{
            "a": 1,
            "b": 2,
            "c": 3,
        }
    
    • Python também permite vírgula após o último item em dicionários
    valid = {
      "a": 1,
      "b": 2,
      "c": 3,
    }
    
    • Em Python e Go, a vírgula pode vir depois, mas não antes, então não dá para formar um estilo completo de bullet points
    invalid = {
        , "a": 1
        , "b": 2
        , "c": 3
    }
    
  • Separadores à esquerda e Alloy

    • TLA+ permite conjunção à esquerda e disjunção à esquerda, mas não permite a forma com o operador no fim, como em (a &&)
    // Not TLA+ but the same semantics
    || && a == 1
       && b == 2
    
    || && a == 3
       && b == 4
    
    • Alloy permite tanto vírgula à esquerda quanto vírgula à direita
    sig Valid {
        , a: 1
        , b: 2
    }
    
    sig AlsoValid {
        a: 1,
        b: 2,
    }
    
    • Alloy também permite separadores vazios, então até linhas com várias vírgulas são consideradas válidas
    sig StillValid {
        ,, a: 1,,
        ,,,,,,,,,
        ,, b: 2,,
    }
    
    • Esse formato é chamado por algumas pessoas de “stuttering

Contra-argumento: ambiguidade de parsing

  • Separadores de controle em Prolog

    • Um dos argumentos contra separadores à direita é que eles podem tornar o parsing ambíguo
    • Em Prolog, ao encerrar uma regra com ponto final, fica claro que foo e bar são definições separadas
    foo(A, B) :-
        A = 1,
        B = 2.
    
    bar(c).
    
    • Se o símbolo de término da regra fosse trocado por vírgula, bar(c) poderia ser interpretado como parte da definição de foo
    foo(A, B) :-
        A = 1,
        B = 2,
    
    bar(c),
    
    • Nesse caso, foo poderia ser interpretado como verdadeiro apenas quando bar(c) também for verdadeiro
  • Chamadas de método em Ruby

    • Em Ruby, é possível continuar o encadeamento de métodos após uma quebra de linha, e o código abaixo imprime 5
    puts 3.
         succ().
         succ()
    
    • Se separadores à direita fossem permitidos após chamadas de método, não ficaria claro se quux() é uma função de nível superior ou um método de foo
    foo.
      bar().
      baz().
    
    quux()
    
    • Os casos de Prolog e Ruby envolvem ambiguidades relacionadas a separadores de controle, não a separadores de dados

Exceção em sintaxe de dados: tuplas em Python

  • Python usa parênteses tanto para agrupar expressões quanto para definir tuplas
  • (2+3) é tratado como avaliação de expressão e resulta em int
>>> x = (2+3)
>>> type(x)

  • (2+3,) é tratado como uma tupla de um único elemento por causa da vírgula à direita
>>> x = (2+3,)
>>> type(x)

  • Nesse caso do Python, o separador de dados à direita serve para distinguir uma expressão de uma tupla de um único elemento

1 comentários

 
GN⁺ 4 시간 전
Opiniões no Lobste.rs
  • A gramática do JSON permite colocar vírgulas entre dois membros de um objeto, mas não permite uma vírgula final depois do membro. Não acho que dê para chamar isso de “erro de projeto”, porque não era uma opção O JSON foi criado por volta de 2000–2001 como um subconjunto do ECMAScript 3, e a RFC 4627 informativa foi escrita em 2006. Ser um subconjunto de JavaScript e funcionar diretamente no navegador com eval era justamente o objetivo do JSON e a chave do seu sucesso; a API JSON nativa dos navegadores só foi adicionada em 2009 Como as vírgulas finais só foram especificadas no ES5 em dezembro de 2009, JSON com vírgula final simplesmente não poderia existir sem contrariar o propósito original

    • Parece considerar erro apenas ações ativas, mas não fazer nada também é uma escolha, então acho válido chamar isso de erro também
  • Essa é uma das irritações que mais encontro em Prolog. Ao trabalhar em um predicado, deixar ,true. no fim torna tudo mais prático, porque você não precisa se preocupar com o ponto final ao reorganizar linhas acima ou comentar alguma delas

    • De forma parecida, em SQL nosso time usa a cláusula WHERE no formato where true / and ... / and ..., em que as barras significam quebras de linha Assim, qualquer condição pode ser editada facilmente sem tratamento especial
  • Zig permite vírgula final, e isso pode ser usado para controlar o formatador não configurável .{1, 2, 3,} vira o seguinte

    .{
       1,
       2,
       3,
    }
    

    E, em literais formatados verticalmente, remover a vírgula final passa a significar um pedido de alinhamento horizontal: .{ 1, 2, 3 } Isso também funciona em: definições de tipo de contêiner struct { a: u32, b: u32, }, assinaturas de função fn foo(a: u32, b: u32,) void {}, chamadas de função foo(1, 2,); Em todos esses casos, a vírgula final pode controlar a formatação automática Gostei tanto desse recurso que também o adicionei ao meu servidor de linguagem/autoformatador de HTML Before:

    Foo
    
    

    After:

    Foo
    
    

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

  • Concordo 100%. Pessoalmente, uma linguagem nova sem separador final já perde alguns pontos comigo. Não a ponto de eu odiar a gramática da linguagem, mas é uma pequena irritação, como um machucado leve

    • Isso vale também para chamadas de função? foo(1,2,3,4,)? bar(,1,2,3,4)? Se foo() permitir um número variável de parâmetros, o último argumento seria nil? O primeiro parâmetro de bar() seria nil?
  • Em Clojure e EDN, a vírgula é espaço em branco. Ela costuma ser usada por convenção entre pares chave-valor em literais de mapa na mesma linha, mas é totalmente opcional

    {:a 1 :b 2}
    ;=> {:a 1, :b 2}
    {:a,1,,,,,,,,,,:b,2,} ; if you must
    ;=> {:a 1, :b 2}
    
  • Acho que boa parte da tensão aqui vem do fato de que, quando há várias cláusulas na mesma linha, é preciso algum tipo de separador

    function(1, 2, 3, 4)
    

    Nesses casos, adicionar ou remover argumentos por diferença de linhas sempre parece estranho. Até comentar apenas um argumento exige um pouco de cuidado e esforço. Em troca, você aceita a concisão de colocar vários itens na mesma linha Mas, quando você passa a colocar só uma cláusula por linha, idealmente começa a querer diferenças mais limpas e uma forma fácil de desativar coisas com comentários de linha simples A resposta óbvia seria tratar a quebra de linha como separador padrão

    function(
      1
      2
      3
    )
    

    Muitas linguagens fazem isso com ;. Se o estilo incentiva não colocar várias instruções na mesma linha, você quase não vê ;. Não conheço linguagens que façam exatamente o mesmo com vírgulas, mas já tive vontade de experimentar isso em uma linguagem hobby Claro, Lisp contorna esse problema porque subexpressões e subcláusulas são sempre completamente delimitadas, então não há separador nenhum

  • Esse é um dos motivos pelos quais gosto de Lisp — mais precisamente de S-expressions. É um detalhe a menos em que pensar