Usando gramática de linguagem natural em API fluente

14

Estou mexendo com uma abstração de consulta na API WebSQL / Phonegap Database, e me sinto atraído e duvidoso por definir uma API fluente que imita o uso da gramática natural da língua inglesa.

Pode ser mais fácil explicar isso por meio de exemplos. A seguir, todas as consultas válidas na minha gramática e os comentários explicam a semântica pretendida:

//find user where name equals "foo" or email starts with "foo@"
find("user").where("name").equals("foo").and("email").startsWith("foo@")

//find user where name equals "foo" or "bar"
find("user").where("name").equals("foo").or("bar");

//find user where name equals "foo" or ends with "bar"
find("user").where("name").equals("foo").or().endsWith("bar");

//find user where name equals or ends with "foo"
find("user").where("name").equals().or().endsWith("foo");

//find user where name equals "foo" and email is not like "%contoso.com"
find("user").where("name").equals("foo").and("email").is().not().like("%contoso.com");

//where name is not null
find("user").where("name").is().not().null();

//find post where author is "foo" and id is in (1,2,3)
find("post").where("author").is("foo").and("id").is().in(1, 2, 3);

//find post where id is between 1 and 100
find("post").where("id").is().between(1).and(100);

Edite com base no feedback de Quentin Pradet : Além disso, a API teria que suportar formas verbais no plural e no singular, portanto:

//a equals b
find("post").where("foo").equals(1);

//a and b (both) equal c
find("post").where("foo").and("bar").equal(2);

Por uma questão de dúvida, vamos supor que eu não tenha esgotado todas as construções possíveis aqui. Suponhamos também que eu possa cobrir as frases em inglês mais corretas - afinal, a gramática em si é limitada aos verbos e conjurações definidos pelo SQL.


Editar em relação ao agrupamento : uma "sentença" é um grupo e a precedência é como definida em SQL: da esquerda para a direita. Vários agrupamentos podem ser expressos com várias whereinstruções:

//the conjunctive "and()" between where statements is optional
find("post")
  .where("foo").and("bar").equal(2).and()
  .where("baz").isLessThan(5);

Como você pode ver, a definição de cada método depende do contexto gramatical em que está. Por exemplo, o argumento para "métodos de conjunção" or()e and()pode ser deixado de fora, ou referir-se a um nome de campo ou valor esperado.

Para mim, isso parece muito intuitivo, mas gostaria que você ouvisse seus comentários: essa é uma API boa e útil ou devo recuar para uma implementação mais direta?

Para o registro: essa biblioteca também fornecerá uma API mais convencional e não fluente, com base em objetos de configuração.

fencliff
fonte
1
O encadeamento também é o motivo pelo qual o jQuery é muito famoso. Bem direto, seqüencial e compreensível.
317 Joseph
3
Isto é interessante! Você provavelmente deveria perguntar isso aos programadores.
Benjamin Gruenbaum
2
Como você lidaria com o agrupamento? O equivalente a ... where foo = 1 or (bar = 2 and qux = 3):?
7
Na IMO, esse tipo de API fluente é horrível. Por exemplo, a falta de precedência do operador é irritante. Eu analisaria where("name").equals("foo").or("bar")como (name=="foo")or bar. Então não é claro quando uma string representa um literal, e quando apresenta um nome de coluna, ...
CodesInChaos
4
btw. se você quiser usar uma DSL para consultar um banco de dados, poderá usar a DSL já existente chamada SQL.
CodesInChaos

Respostas:

23

Eu acho que está muito errado. Eu estudo linguagem natural e é cheia de ambiguidade que só pode ser resolvida com contexto e muito conhecimento humano. O fato de as linguagens de programação não serem ambíguas é uma coisa muito boa! Eu não acho que você queira que o significado dos métodos mude de acordo com o contexto:

  • Isso adiciona mais surpresas, já que você traz ambiguidade
  • Seus usuários vão querer usar construções que você não cobriu, por exemplo. find("user").where("name").and("email").equals("foo");
  • É difícil relatar erros: com o que você pode fazer find("user").where("name").not().is().null();?

Suponhamos também que eu possa cobrir as frases em inglês mais corretas - afinal, a gramática em si é limitada aos verbos e conjurações definidos pelo SQL.

Não, você não pode cobrir as frases em inglês mais corretas. Outros já tentaram antes, e isso fica muito complicado muito rapidamente. Isso se chama entendimento da linguagem natural, mas ninguém realmente tenta isso: estamos tentando resolver problemas menores primeiro. Para sua biblioteca, você basicamente tem duas opções:

  • ou você se restringe a um subconjunto de inglês: que fornece SQL,
  • ou você tenta cobrir "inglês" e descobre que isso não é possível devido à ambiguidade, complexidade e diversidade do idioma.
Quentin Pradet
fonte
Obrigado pela sua contribuição. Editou minha pergunta para cobrir o primeiro caso que você listou e adicionou algumas advertências. Concordo com a sua posição sobre ambiguidade - esse é o cerne da minha pergunta. É aceitável que as linguagens de programação sejam ambiciosas em contextos bem definidos?
fencliff
se for ambíguo, não está bem definido. Se for ambíguo, há mais de um resultado potencial e algo precisa escolher um deles. A escolha será bem definida ou o analisador de idiomas escolherá aleatoriamente. Portanto, a ambiguidade em uma linguagem de programação é aceitável se você deseja uma linguagem estocástica (1 + 1 pode ser igual a 2 e, às vezes, igual a 1 ou 3) em oposição a determinística (1 + 1 sempre é igual a 2).
22313 Michael Michaelukukis
Você está falando sobre a proposta dele ou o inglês? Na maioria das vezes, a ambiguidade pode ser resolvida usando o contexto e / ou conhecimento (parte do qual é senso comum), algo que não está disponível para um computador.
Quentin Pradet
+1 Boa percepção da sensibilidade ao contexto de sua implementação específica de uma API fluente. Gostei da ideia de sua API fluente à primeira vista, mas não olhei tão de perto para ver isso. Definitivamente enorme problema.
Jimmy Hoffa
se ele torna a gramática inequívoca e livre de contexto, qual é o problema? com certeza eles podem querer remover versos plurais formas singulares de verbos e coisas assim, mas isso não muda o âmago do que eles estavam tentando fazer. Você não teria is()ou equal()somente equals(). Não encontrou seu problema ao relatar erros após isso. null()também se tornaria um literal para comparar, em vez de uma função de sintaxe. find("user").where("name").is().not().null();tornafind("user").where("name").not().equals(null);
whn
3

Costumo concordar um pouco com os posts de outras pessoas que esse não é um ótimo design. No entanto, acredito que tenho razões diferentes.

Você está apresentando o que eu vejo como uma sintaxe concreta para consultas SQL. Eu acredito firmemente que a sintaxe concreta nunca pode ajudar uma linguagem, apenas machuca se for ruim.

No entanto, a sintaxe abstrata é uma história diferente. A sintaxe abstrata define a estrutura do seu idioma e como as frases podem ser combinadas para criar frases maiores. Eu sinto que o sucesso de uma linguagem depende fortemente da qualidade de sua definição abstrata de sintaxe.

Meu problema com a API fluente não é ambíguo, claro ou não expressivo - oculta a linguagem real e sua estrutura e, ao fazê-lo, acaba tornando as coisas muito mais complicadas do que precisam ser ( introduzindo ambiguidades, erros de sintaxe não óbvios etc.).

Como você mencionou que também fornecerá uma "API mais convencional", parece que você já sabe tudo isso. Para isso eu digo "Bom!" Mas isso não significa que você também não pode desenvolver sua API fluente em paralelo! Uma única definição de sintaxe abstrata pode suportar várias sintaxes concretas. Embora você tenha em mente que a sintaxe abstrata é o negócio real, uma sintaxe concreta também pode ser muito útil.


fonte
2

Além dos pontos muito bons de Quentin Pradet, duvido dos alegados benefícios desse idioma.

Presumivelmente, o objetivo de uma gramática próxima à linguagem natural é torná-la acessível. Mas o SQL já está bem próximo da linguagem natural. Um deles é realmente mais próximo do inglês que o outro?

find("user").where("name").equals("foo")

select user from table where name = 'foo'

Realmente não vejo o benefício da sua gramática, do ponto de vista da intuitividade ou legibilidade. De fato, a versão SQL parece mais legível (e é mais fácil de digitar) devido ao seu espaço em branco.


fonte
2

Há várias decisões de design ruins que não são ideais que parecem ter sido tomadas ao considerar esta API.

A primeira é a questão da utilidade - a que finalidade ela serve? Isso parece estar criando uma estrutura de dados que será compilada em um dialeto do SQL. Aliás, a gramática parece ser um conjunto limitado de SQL. A questão de "que vantagem isso serve ao usar apenas SQL?" torna-se chave. Se for mais complicado escrever usando a interface fluente do que apenas escrever uma string com a interpolação apropriada, não será possível escrever usando essa API.

Inglês é ambíguo. Tentar modelar uma interface fluente em inglês é uma má escolha (é melhor usar o latim ). Quando existem várias análises possivelmente válidas do mesmo conjunto de ligações, isso gera confusão e surpresa . Nenhuma dessas coisas é boa em uma API.

Há mais partes no SQL do que esta API está expondo. As junções (em qualquer uma de suas inúmeras formas) estão notavelmente ausentes no conjunto de exemplos. Subconsultas ( foo in (select id from bar)), uniões e grupos de são algumas das coisas que são frequentemente usadas. Agrupamentos complexos de lógica não parecem estar presentes de maneira intuitiva.

Se alguém estivesse escrevendo usando essa API e descobrisse que ela não é capaz de expressar a consulta desejada, um tempo significativo será perdido. É uma má escolha usar estilos mistos para fazer consultas em um aplicativo (consultas simples nesta API, complexas em sql bruto) - e, finalmente, o que for mais expressivo será usado.

Enquanto a programação é generalizada, a fluência em inglês não é. Mesmo com uma limitação do idioma para "como o SQL", há nuances de como um falante nativo leria algo e alguém que tem o inglês como segundo ou terceiro idioma.

Há redundância desnecessária na API por causa do inglês. Em particular equal()vs equals()fazendo a mesma coisa. Embora eu não tenha certeza disso, acredito que esse is()é um acréscimo no-op por causa de um inglês mais próximo. Congratulo-me com qualquer um que ouça meu discurso sobre redundância de métodos em ruby ​​no chat - não cometa o mesmo erro.

Sente-se e escreva um conjunto abrangente de exemplos das consultas que você deseja poder usar. Determine com quem você manipulará todos esses exemplos de maneira não ambígua e menos trabalhosa do que as próprias consultas. Se não puder, considere se vale a pena seguir o caminho de escrever a API. O SQL é onde está hoje (não é perfeito, mas não encontrei nada melhor) ao longo de décadas de aprimoramento.

RFC 1925 - As doze verdades da rede

(12) No projeto do protocolo, a perfeição foi alcançada não quando não há mais nada a acrescentar, mas quando não há mais nada a ser retirado.


fonte