Por que muitos idiomas não suportam parâmetros nomeados? [fechadas]

14

Eu estava pensando como seria mais fácil ler o código se, ao chamar uma função, você pudesse escrever:

doFunction(param1=something, param2=somethingElse);

Não consigo pensar em nenhuma desvantagem e isso tornaria o código muito mais legível. Eu sei que você poderia passar uma matriz como o único argumento e ter as chaves da matriz como os nomes dos parâmetros, no entanto, isso ainda não seria tão legível.

Existe uma desvantagem disso que estou perdendo? Caso contrário, por que muitos idiomas não permitem isso?


fonte
1
você pode dar um idioma que deveria (e pode) ter parâmetros nomeados, mas não possui?
Bryan Chen
1
Eu acho que é muito detalhado em muitos casos. Até onde eu vi, se um idioma usa ou não isso tende a depender de impactar o idioma de maneira positiva ou negativa. A maioria das linguagens em estilo C se sai bem sem ela. No caso deles, muitas vezes é bastante óbvio o que está acontecendo, e sua ausência realmente ajuda a reduzir a desordem em geral.
Panzercrisis
2
@SteveFallows: O "Nome do parâmetro nomeado" parece um nome estranho para a definição de uma API fluente (conforme nomeado por Martin Fowler) em uma classe de construtor. Você pode encontrar exemplos do mesmo padrão em um dos primeiros itens do Java efetivo de Joshua Bloch .
Jhominal #
3
Esta não é necessariamente uma pergunta baseada em opinião
Catskul
2
Acordado. "Por que muitos idiomas não suportam parâmetros nomeados?" é mais uma questão de história das línguas do que de opinião. Seria opinião se alguém perguntasse como "Os parâmetros não estão melhor nomeados?".
Andy Fleming

Respostas:

14

A especificação de nomes de parâmetros nem sempre torna o código mais legível: como exemplo, você prefere ler min(first=0, second=1)ou min(0, 1)?

Se você aceitar o argumento do parágrafo anterior, é óbvio que especificar nomes de parâmetros não deve ser obrigatório. Por que todos os idiomas não tornam a especificação de nomes de parâmetros uma opção válida?

Eu posso pensar em pelo menos duas razões:

  • Em geral, a introdução de uma segunda sintaxe para uma necessidade já atendida (aqui, passando parâmetros) é algo cujos benefícios devem ser equilibrados com o custo inerente à complexidade da linguagem introduzida (tanto na implementação da linguagem quanto no modelo mental da linguagem do programador) ;
  • Torna a alteração de nomes de parâmetros uma alteração de quebra de API, que pode não estar em conformidade com a expectativa de um desenvolvedor de que alterar um nome de parâmetro seja uma alteração trivial e ininterrupta;

Observe que eu não conheço nenhuma linguagem que implemente parâmetros nomeados sem também implementar parâmetros opcionais (que exigem parâmetros nomeados) - portanto, você deve desconfiar de superestimar seus benefícios de legibilidade, que podem ser obtidos de forma mais consistente com a definição de interfaces fluentes .

jhominal
fonte
6
Eu codigo regularmente em vários idiomas diferentes, quase sempre preciso procurar os parâmetros para uma substring simples em qualquer idioma. É substring (start, length) ou substring (start, end) e o end está incluído na string?
James Anderson
1
@ JamesAnderson - Como uma substring de parâmetro nomeado resolveria seu problema? Você ainda teria que procurar os parâmetros para saber qual é o nome do segundo parâmetro (a menos que existam ambas as funções e o recurso de polimorfismo da linguagem permita parâmetros do mesmo tipo com nomes diferentes).
Mouviciel 27/11
6
@mouviciel: Os parâmetros nomeados não são muito úteis para o escritor , mas são fantásticos para o leitor . Todos sabemos como os nomes das variáveis ​​são importantes para criar código legível, e os parâmetros nomeados permitem que o criador de uma interface forneça bons nomes de parâmetros e nomes de funções, facilitando para as pessoas que usam essa interface escreverem códigos legíveis.
Phoshi
@Phoshi - Você está correto. Acabei de esquecer a importância de ler o código quando escrevi meu comentário anterior.
Mouviciel
14

Parâmetros nomeados tornam o código mais fácil de ler e mais difícil de escrever

Quando estou lendo um pedaço de código, os parâmetros nomeados podem introduzir um contexto que facilita a compreensão do código. Considere, por exemplo, este construtor: Color(1, 102, 205, 170). O que diabos isso significa? Na verdade, Color(alpha: 1, red: 102, green: 205, blue: 170)seria muito mais fácil de ler. Mas, infelizmente, o Compilador diz "não" - ele quer Color(a: 1, r: 102, g: 205, b: 170). Ao escrever código usando parâmetros nomeados, você gasta um tempo desnecessário pesquisando os nomes exatos - é mais fácil esquecer os nomes exatos de alguns parâmetros do que esquecer sua ordem.

Isso me incomodou ao usar uma DateTimeAPI que tinha duas classes de irmãos por pontos e durações com interfaces quase idênticas. Enquanto DateTime->new(...)aceitou um second => 30argumento, o DateTime::Duration->new(...)procurado seconds => 30e semelhante para outras unidades. Sim, faz absolutamente sentido, mas isso me mostrou que os parâmetros nomeados são fáceis de usar.

Nomes ruins nem facilitam a leitura

Outro exemplo de como os parâmetros nomeados podem ser ruins é provavelmente a linguagem R. Este pedaço de código cria um gráfico de dados:

plot(plotdata$n, plotdata$mu, type="p", pch=17,  lty=1, bty="n", ann=FALSE, axes=FALSE)

Você vê dois argumentos posicionais para o x e y linhas de dados, e em seguida uma lista de parâmetros nomeados. Existem muito mais opções com padrões, e apenas aquelas listadas cujos padrões eu queria alterar ou especificar explicitamente. Depois que ignoramos que esse código usa números mágicos e que podemos nos beneficiar do uso de enumerações (se R tiver algum!), O problema é que muitos desses nomes de parâmetros são indecifráveis.

  • pché realmente o caractere da plotagem, o glifo que será desenhado para cada ponto de dados. 17é um círculo vazio, ou algo assim.
  • ltyé o tipo de linha. Aqui 1está uma linha sólida.
  • btyé o tipo de caixa. A configuração para "n"evitar que uma caixa seja desenhada ao redor da plotagem.
  • ann controla a aparência das anotações do eixo.

Para alguém que não sabe o que significa cada abreviação, essas opções são bastante confusas. Isso também revela por que o R usa esses rótulos: não como código de auto-documentação, mas (sendo uma linguagem de tipo dinâmico) como chaves para mapear os valores para suas variáveis ​​corretas.

Propriedades de parâmetros e assinaturas

As assinaturas de funções podem ter as seguintes propriedades:

  • Os argumentos podem ser ordenados ou não ordenados,
  • nomeado ou sem nome,
  • obrigatório ou opcional.
  • As assinaturas também podem ser sobrecarregadas por tamanho ou tipo,
  • e pode ter um tamanho não especificado com varargs.

Idiomas diferentes chegam a coordenadas diferentes deste sistema. Em C, os argumentos são ordenados, sem nome, sempre necessários e podem ser varargs. Em Java, a situação é semelhante, exceto que as assinaturas podem ser sobrecarregadas. No Objetivo C, as assinaturas são ordenadas, nomeadas, necessárias e não podem ser sobrecarregadas porque é apenas açúcar sintático em torno de C.

Linguagens digitadas dinamicamente com varargs (interfaces de linha de comando, Perl,…) podem emular parâmetros nomeados opcionais. Os idiomas com sobrecarga de tamanho de assinatura têm algo como parâmetros opcionais posicionais.

Como não implementar parâmetros nomeados

Ao pensar em parâmetros nomeados, geralmente assumimos parâmetros nomeados, opcionais e não ordenados. Implementar isso é difícil.

Parâmetros opcionais podem ter valores padrão. Eles devem ser especificados pela função chamada e não devem ser compilados no código de chamada. Caso contrário, os padrões não poderão ser atualizados sem recompilar todo o código dependente.

Agora, uma pergunta importante é como os argumentos são realmente passados ​​para a função. Com parâmetros ordenados, os argumentos podem ser passados ​​em um registro ou em sua ordem inerente na pilha. Quando excluímos os registros por um momento, o problema é como colocar argumentos opcionais não ordenados na pilha.

Para isso, precisamos de alguma ordem sobre os argumentos opcionais. E se o código da declaração for alterado? Como a ordem é irrelevante, uma nova ordem na declaração da função não deve alterar a posição dos valores na pilha. Também devemos considerar se é possível adicionar um novo parâmetro opcional. Da perspectiva dos usuários, parece que sim, porque o código que não usava esse parâmetro anteriormente ainda deve funcionar com o novo parâmetro. Portanto, isso exclui pedidos como usar o pedido na declaração ou usar o pedido alfabético.

Considere isso também à luz da subtipagem e do Princípio de Substituição de Liskov - na saída compilada, as mesmas instruções devem poder invocar o método em um subtipo com possíveis parâmetros nomeados e em um supertipo.

Possíveis implementações

Se não podemos ter uma ordem definitiva, precisamos de alguma estrutura de dados não ordenada.

  • A implementação mais simples é simplesmente passar o nome dos parâmetros junto com os valores. É assim que os parâmetros nomeados são emulados no Perl ou com as ferramentas de linha de comando. Isso resolve todos os problemas de extensão mencionados acima, mas pode ser um enorme desperdício de espaço - não uma opção no código crítico de desempenho. Além disso, o processamento desses parâmetros nomeados agora é muito mais complicado do que simplesmente retirar valores de uma pilha.

    Na verdade, os requisitos de espaço podem ser reduzidos usando o pool de strings, o que pode reduzir comparações posteriores de strings a comparações de ponteiros (exceto quando não é possível garantir que as strings estáticas sejam realmente agrupadas, nesse caso, as duas strings terão que ser comparadas em detalhe).

  • Em vez disso, também poderíamos passar uma estrutura de dados inteligente que funciona como um dicionário de argumentos nomeados. Isso é barato para o chamador, porque o conjunto de teclas é estaticamente conhecido no local da chamada. Isso permitiria criar uma função de hash perfeita ou pré-calcular um trie. O receptor ainda terá que testar a existência de todos os nomes de parâmetros possíveis, o que é um pouco caro. Algo assim é usado pelo Python.

Portanto, é muito caro na maioria dos casos

Se uma função com parâmetros nomeados deve ser adequadamente extensível, uma ordem definitiva não pode ser assumida. Portanto, existem apenas duas soluções:

  • Torne a ordem dos parâmetros nomeados parte da assinatura e não permita alterações posteriores. Isso é útil para código de auto-documentação, mas não ajuda com argumentos opcionais.
  • Passe uma estrutura de dados de valor-chave para o receptor, que precisará extrair informações úteis. Isso é muito caro em comparação e geralmente é visto apenas em linguagens de script sem ênfase no desempenho.

Outras armadilhas

Os nomes de variáveis ​​em uma declaração de função geralmente têm algum significado interno e não fazem parte da interface - mesmo que muitas ferramentas de documentação ainda os mostrem. Em muitos casos, você deseja nomes diferentes para uma variável interna e o argumento nomeado correspondente. Os idiomas que não permitem escolher os nomes visíveis externamente de um parâmetro nomeado não os ganham muito se o nome da variável não for usado com o contexto de chamada em mente.

Um problema com emulações de argumentos nomeados é a falta de verificação estática no lado do chamador. Isso é especialmente fácil de esquecer ao passar um dicionário de argumentos (olhando para você, Python). Isto é importante porque a passagem de um dicionário é uma solução comum, por exemplo, em JavaScript: foo({bar: "baz", qux: 42}). Aqui, nem os tipos dos valores nem a existência ou ausência de determinados nomes podem ser verificados estaticamente.

Emulando parâmetros nomeados (em idiomas estaticamente tipados)

O simples uso de strings como chaves e qualquer objeto como valor não é muito útil na presença de um verificador de tipo estático. No entanto, argumentos nomeados podem ser emulados com estruturas ou literais de objeto:

// Java

static abstract class Arguments {
  public String bar = "default";
  public int    qux = 0;
}

void foo(Arguments args) {
  ...
}

/* using an initializer block */
foo(new Arguments(){{ bar = "baz"; qux = 42; }});
amon
fonte
3
" it is easier to forget the exact names of some parameters than it is to forget their order" ... essa é uma afirmação interessante.
Catskul
1
O primeiro contra-exemplo não é um problema com parâmetros nomeados e mais uma falha na maneira como os plurais em inglês são mapeados para nomes de campos em interfaces. O segundo contra-exemplo também é um problema com os desenvolvedores fazendo escolhas ruins de nomes. (Nenhuma escolha da interface de passagem de parâmetros será, por si só, uma condição suficiente para facilitar a leitura - a questão é se é ou não uma condição necessária ou ajuda em alguns casos).
Mtraceur # 20/18
1
+1 para os pontos gerais sobre custos de implementação e trocas para a execução de parâmetros nomeados. Eu só acho que o começo vai perder pessoas.
Mtraceur # 20/18
@mtraceur Você tem razão. Agora, cinco anos depois, eu provavelmente escreveria a resposta de maneira bem diferente. Se você quiser, pode editar minha resposta para remover coisas menos úteis. (Normalmente, essas edições seriam rejeitadas por contradizerem a intenção do autor, mas aqui estou bem com isso). Por exemplo, agora acredito que muitos problemas de parâmetros nomeados são apenas restrições de linguagens dinâmicas e devem receber apenas um sistema de tipos. Por exemplo, o JavaScript possui Flow e TypeScript, o Python possui o MyPy. Com integração IDE adequada, que elimina a maioria dos problemas de usabilidade. Ainda estou esperando por algo em Perl.
amon
7

Pela mesma razão que a notação húngara não é mais amplamente usada; Intellisense (ou seu equivalente moral em IDE não Microsoft). A maioria dos IDE modernos dirá tudo o que você precisa saber sobre um parâmetro, apenas passando o mouse sobre a referência do parâmetro.

Dito isto, várias linguagens suportam parâmetros nomeados, incluindo C # e Delphi. No C #, é opcional; portanto, você não precisa usá-lo se não quiser, e há outras maneiras de declarar membros especificamente, como a inicialização de objetos.

Parâmetros nomeados são úteis principalmente quando você deseja especificar apenas um subconjunto de parâmetros opcionais, e não todos. No C #, isso é muito útil porque você não precisa mais de vários métodos sobrecarregados para fornecer ao programador essa flexibilidade.

Robert Harvey
fonte
4
O Python usa parâmetros nomeados e é um recurso maravilhoso da linguagem. Eu gostaria que mais idiomas o usassem. Isso facilita os argumentos padrão, já que você não é mais forçado, como no C ++, a especificar explicitamente quaisquer argumentos "antes" dos padrões. (Como você mencionou em seu último parágrafo, é como o python se livra de não sobrecarregar ... argumentos padrão e argumentos de nome permitem que você faça as mesmas coisas.) Argumentos nomeados também tornam o código mais legível. Quando você tem algo parecido sin(radians=2), não precisa do "Intellisense".
Gort the Robot
2

O Bash certamente suporta esse estilo de programação, e o Perl e o Ruby suportam listas de parâmetros de nome / valor (como qualquer idioma com suporte nativo a hash / mapa). Nada impede que você escolha definir uma estrutura / registro ou hash / mapa que anexa nomes aos parâmetros.

Para idiomas que incluem armazenamentos de hash / map / keyvalue nativamente, você pode obter a funcionalidade desejada, simplesmente adotando o idioma para passar hashes de chave / valor para suas funções / métodos. Depois de experimentar em um projeto, você terá uma ideia melhor sobre se ganhou alguma coisa, seja pelo aumento da produtividade derivado da facilidade de uso ou pela melhoria da qualidade devido a defeitos reduzidos.

Observe que programadores experientes se acostumaram a passar parâmetros por pedido / slot. Você também pode achar que esse idioma só é valioso quando você tiver mais do que alguns argumentos (digamos> 5/6). E como um grande número de argumentos geralmente é indicativo de métodos (excessivamente) complexos, você pode achar que esse idioma é apenas benéfico para os métodos mais complexos.

ChuckCottrill
fonte
2

Eu acho que é a mesma razão pela qual c # é mais popular que o VB.net. Enquanto o VB.NET é mais "legível", por exemplo, você digita "end if" em vez de um colchete de fechamento, mas acaba sendo mais um material que preenche o código e acaba dificultando a compreensão.

Acontece que o que torna o código mais fácil de entender é a concisão. Quanto menos, melhor. Os nomes dos parâmetros de função são normalmente bastante óbvios e não ajudam muito a entender o código.

Rocklan
fonte
1
Eu discordo veementemente que quanto "menos, melhor". Levados ao extremo lógico, os vencedores do código de golfe seriam os "melhores" programas.
Riley Maior
2

Os parâmetros nomeados são uma solução para um problema na refatoração do código-fonte e não se destinam a tornar o código-fonte mais legível . Parâmetros nomeados são usados ​​para auxiliar o compilador / analisador na resolução de valores padrão para chamadas de função. Se você deseja tornar o código mais legível, adicione comentários significativos.

Os idiomas difíceis de refatorar geralmente oferecem suporte a parâmetros nomeados, porque serão foo(1)interrompidos quando a assinatura das foo()alterações, mas foo(name:1)são menos propensos a serem quebrados, exigindo menos esforço do programador para fazer alterações foo.

O que você faz quando precisa introduzir um novo parâmetro para a função a seguir e existem centenas ou milhares de linhas de código chamando essa função?

foo(int weight, int height, int age = 0);

A maioria dos programadores fará o seguinte.

foo(int weight, int height, int age = 0, string gender = null);

Agora, nenhuma refatoração é necessária e a função pode ser executada no modo legado quando genderfor nulo.

Para especificar genderum valor agora é HARDCODED na chamada age. Como este exemplo:

 foo(10,10,0,"female");

O programador olhou para a definição da função, viu que agetinha um valor padrão 0e usou esse valor.

Agora, o valor padrão para agea definição da função é completamente inútil.

Parâmetros nomeados permitem evitar esse problema.

foo(weight: 10, height: 10, gender: "female");

Novos parâmetros podem ser adicionados fooe você não precisa se preocupar com a ordem em que foram adicionados e pode alterar os valores padrão sabendo que o padrão definido é realmente o valor padrão verdadeiro.

Reactgular
fonte