Syntax Design - Por que usar parênteses quando nenhum argumento é passado?

63

Em muitos idiomas, a sintaxe function_name(arg1, arg2, ...) é usada para chamar uma função. Quando queremos chamar a função sem nenhum argumento, devemos fazer function_name() .

Acho estranho que um compilador ou interpretador de scripts exija que () detecte com êxito como uma chamada de função. Se uma variável é conhecida como chamada, por que não seria function_name; suficiente?

Por outro lado, em alguns idiomas, podemos fazer: function_name 'test'; ou mesmo function_name 'first' 'second'; para chamar uma função ou um comando.

Acho que os parênteses teriam sido melhores se fossem necessários apenas para declarar a ordem de prioridade e em outros lugares fossem opcionais. Por exemplo, fazer if expression == true function_name; deve ser tão válido quanto if (expression == true) function_name(); .

A coisa mais chata na minha opinião é fazer 'SOME_STRING'.toLowerCase() quando claramente nenhum argumento é necessário para a função protótipo. Por que os designers decidiram contra o mais simples 'SOME_STRING'.lower ?

Aviso: Não me entenda mal, eu amo a sintaxe do tipo C! ;) Estou apenas perguntando se poderia ser melhor. A exigência de () tem vantagens de desempenho ou facilita o entendimento do código? Estou muito curioso para saber exatamente o motivo.

    
por DRS David Soft 05.10.2016 / 15:49
fonte

10 respostas

250

Para idiomas que usam funções de primeira classe , é bastante comum que a sintaxe de referindo para uma função é:

a = object.functionName

enquanto o ato de chamar essa função é:

b = object.functionName()

a no exemplo acima seria referência à função acima (e você poderia chamá-la fazendo a() ), enquanto b conteria o valor de retorno da função.

Enquanto alguns idiomas podem fazer chamadas de função sem parênteses, pode ficar confuso se eles estão chamando a função, ou simplesmente referindo para a função.

    
por 05.10.2016 / 16:15
fonte
63

De fato, o Scala permite isso, embora haja uma convenção que seja seguida: se o método tiver efeitos colaterais, parênteses devem ser usados de qualquer maneira.

Como redatora de compiladores, eu consideraria a presença garantida de parênteses bastante conveniente; Eu sempre soube que é uma chamada de método, e eu não teria que construir uma bifurcação para o caso estranho.

Como programador e leitor de códigos, a presença de parênteses não deixa dúvidas de que é uma chamada de método, mesmo que nenhum parâmetro seja passado.

A passagem de parâmetros não é a única característica definidora de uma chamada de método. Por que eu trataria um método sem parâmetros diferente de um método que tivesse parâmetros?

    
por 05.10.2016 / 15:54
fonte
19

Isto é na verdade um acaso muito sutil de escolhas de sintaxe. Eu falarei com linguagens funcionais, que são baseadas no cálculo lambda tipado.

Nos ditos idiomas, cada função tem exatamente um argumento . O que frequentemente pensamos como "vários argumentos" é, na verdade, um único parâmetro do tipo de produto. Então, por exemplo, a função que compara dois inteiros:

leq : int * int -> bool
leq (a, b) = a <= b

aceita um único par de inteiros. Os parênteses não denotam os parâmetros da função; eles são usados para padronizar a correspondência do argumento. Para convencê-lo de que este é realmente um argumento, podemos usar as funções projetivas em vez da correspondência de padrões para desconstruir o par:

leq some_pair = some_pair.1 <= some_pair.2

Assim, os parênteses são realmente uma conveniência que nos permite padronizar a correspondência e economizar alguma digitação. Eles não são necessários.

Que tal uma função que ostensivamente não tem argumentos? Essa função, na verdade, tem domínio Unit . O único membro de Unit geralmente é escrito como () , e é por isso que os parênteses aparecem.

say_hi : Unit -> string
say_hi a = "Hi buddy!"

Para chamar essa função, teríamos que aplicá-la a um valor do tipo Unit , que deve ser () , então acabamos escrevendo say_hi () .

Então, realmente não existe uma lista de argumentos!

    
por 05.10.2016 / 16:45
fonte
14

Em Javascript, por exemplo, usar um nome de método sem () retorna a própria função sem executá-la. Desta forma você pode, por exemplo, passar a função como um argumento para outro método.

Em Java, foi feita a escolha de que um identificador seguido por () ou (...) significa uma chamada de método, enquanto um identificador sem () refere-se a uma variável de membro. Isso pode melhorar a legibilidade, pois você não tem dúvidas se está lidando com um método ou com uma variável de membro. De fato, o mesmo nome pode ser usado tanto para um método quanto para uma variável de membro, ambos estarão acessíveis com suas respectivas sintaxes.

    
por 05.10.2016 / 16:10
fonte
10

A sintaxe segue a semântica, então vamos começar da semântica:

What are the ways of using a function or method?

Existem, na verdade, várias maneiras:

  • uma função pode ser chamada, com ou sem argumentos
  • uma função pode ser tratada como um valor
  • uma função pode ser parcialmente aplicada (criando um encerramento, tendo pelo menos um argumento a menos e fechando os argumentos passados)

Ter uma sintaxe semelhante para usos diferentes é a melhor maneira de criar uma linguagem ambígua ou, no mínimo, confusa (e temos o suficiente delas).

Em linguagens C e C:

  • chamar uma função é feito usando parênteses para colocar os argumentos (pode não haver nenhum) como em func()
  • tratar uma função como um valor pode ser feito simplesmente usando seu nome como em &func (C criando um ponteiro de função)
  • em alguns idiomas, você tem sintaxes curtas para aplicar parcialmente; Java permite someVariable::someMethod por exemplo (limitado ao receptor do método, mas ainda é útil)

Note como cada uso apresenta uma sintaxe diferente, permitindo diferenciá-los facilmente.

    
por 05.10.2016 / 16:51
fonte
8

Em uma linguagem com efeitos colaterais, a OMI é realmente útil para diferenciar (em teoria, sem efeitos colaterais) a leitura de uma variável

variable

e chamando uma função, que pode gerar efeitos colaterais

launch_nukes()

OTOH, se não houver efeitos colaterais (além daqueles codificados no sistema de tipos), então não há nenhum ponto para diferenciar entre ler uma variável e chamar uma função sem argumentos.

    
por 05.10.2016 / 16:25
fonte
7
Nenhuma das outras respostas tentou abordar a questão: quanta redundância deveria haver no design de uma linguagem? Porque mesmo que você possa projetar uma linguagem para que x = sqrt y defina x para a raiz quadrada de y, isso não significa necessariamente que você deveria.

Em uma linguagem sem redundância, cada sequência de caracteres significa algo, o que significa que, se você cometer um único erro, não receberá uma mensagem de erro, seu programa fará a coisa errada, o que pode ser algo muito diferente do que você pretendia e muito difícil de depurar. (Como qualquer pessoa que tenha trabalhado com expressões regulares saberá.) Um pouco de redundância é bom porque permite que muitos dos seus erros sejam detectados, e quanto mais redundância houver, maior a probabilidade de que o diagnóstico seja preciso. Agora, é claro, você pode levar isso longe demais (nenhum de nós quer escrever em COBOL atualmente), mas há um equilíbrio que é certo.

A redundância também ajuda na legibilidade, porque há mais pistas. Inglês sem redundância seria muito difícil de ler, e o mesmo é válido para linguagens de programação.

    
por 06.10.2016 / 23:23
fonte
5

Na maioria dos casos, são escolhas sintáticas da gramática da linguagem. É útil para a gramática que os vários constructos individuais sejam (relativamente) não ambíguos quando tomados todos juntos. (Se houver ambigüidades, como em algumas declarações C ++, tem que haver regras específicas para resolução.) O compilador não tem a latitude para adivinhar; é necessário seguir a especificação da linguagem.

O Visual Basic, em várias formas, diferencia entre procedimentos que não retornam um valor e funções.

Os procedimentos devem ser chamados como uma declaração e não requerem parênteses, apenas argumentos separados por vírgulas, se houver. As funções devem ser chamadas como parte de uma expressão e exigem os parênteses.

É uma distinção relativamente desnecessária que torna a refatoração manual entre as duas formas mais dolorosa do que tem de ser.

(Por outro lado, o Visual Basic usa o mesmo parens () para referências de array como para chamadas de função, então referências de array parecem chamadas de função. E isso facilita a refatoração manual de um array em uma chamada de função! ponderar outras linguagens use [] para referências de matriz, mas eu divago ...)

Nas linguagens C e C ++, o conteúdo das variáveis é acessado automaticamente usando seu nome, e se você quiser se referir à variável em vez de seu conteúdo, você aplica o operador & unário.

Esse tipo de mecanismo também pode ser aplicado a nomes de função. O nome da função bruta poderia implicar uma chamada de função, enquanto um operador & unário seria usado para se referir à função (propriamente dita) como dados. Pessoalmente, eu gosto da idéia de acessar funções sem argumentos sem efeitos colaterais com a mesma sintaxe das variáveis.

Isso é perfeitamente plausível (assim como outras escolhas sintáticas para isso).

    
por 05.10.2016 / 16:58
fonte
4

Para adicionar as outras respostas, use este exemplo C:

void *func_factory(void)
{
        return 0;
}

void *(*ff)(void);

void example()
{
        ff = func_factory;
        ff = func_factory();
}

Se o operador de invocação fosse opcional, não haveria como distinguir entre a atribuição da função e a chamada da função.

Isso é ainda mais problemático em idiomas que não possuem um sistema de tipos, por exemplo JavaScript, onde a inferência de tipos não pode ser usada para descobrir o que é e o que não é uma função.

    
por 05.10.2016 / 23:22
fonte
4

Consistência e legibilidade.

Se eu souber que você chama a função X assim: X(arg1, arg2, ...) , então espero que funcione da mesma maneira para nenhum argumento: X() .

Agora, ao mesmo tempo, aprendo que você pode definir a variável como algum símbolo e usá-la assim:

a = 5
b = a
...

Agora, o que eu pensaria quando encontrasse isso?

c = X

Seu palpite é tão bom quanto o meu. Em circunstâncias normais, o X é uma variável. Mas se fôssemos seguir o seu caminho, também poderia ser uma função! Devo lembrar se o símbolo mapeia a qual grupo (variáveis / funções)?

Poderíamos impor restrições artificiais, por ex. "As funções começam com letra maiúscula. As variáveis começam com letra minúscula", mas isso é desnecessário e torna as coisas mais complicadas, embora às vezes possa ajudar alguns dos objetivos do projeto.

Side-note 1: Outras respostas ignoram completamente mais uma coisa: o idioma pode permitir que você use o mesmo nome para a função e a variável e diferencie por contexto. Veja Common Lisp como um exemplo. A função x e variável x coexistem perfeitamente bem.

Nota 2: A resposta aceita nos mostra a sintaxe: object.functionname . Em primeiro lugar, não é universal para idiomas com funções de primeira classe. Em segundo lugar, como programador, eu trataria isso como uma informação adicional: functionname pertence a um object . Se object é um objeto, uma classe ou um namespace não importa muito, mas me diz que ele pertence a algum lugar . Isso significa que você precisa adicionar a sintaxe artificial object. para cada função global ou criar alguns object para manter todas as funções globais.

E de qualquer forma, você perde a capacidade de ter namespaces separados para funções e variáveis.

    
por 06.10.2016 / 11:01
fonte