Em muitos idiomas, a sintaxe function_name(arg1, arg2, ...)
é usada para chamar uma função. Quando queremos chamar a função sem argumentos, devemos fazê-lo function_name()
.
Acho estranho que um compilador ou intérprete de script exigiria ()
detectá-lo com êxito como uma chamada de função. Se uma variável é conhecida como chamadora, por que não function_name;
seria suficiente?
Por outro lado, em alguns idiomas, podemos fazer: function_name 'test';
ou mesmo function_name 'first' 'second';
chamar uma função ou comando.
Acho que os parênteses teriam sido melhores se fossem necessários apenas para declarar a ordem de prioridade e, em outros lugares, eram opcionais. Por exemplo, fazer if expression == true function_name;
deve ser tão válido quanto if (expression == true) function_name();
.
A coisa mais irritante na minha opinião é fazer 'SOME_STRING'.toLowerCase()
quando claramente nenhum argumento é necessário para a função prototype. Por que os designers decidiram contra o mais simples 'SOME_STRING'.lower
?
Isenção de responsabilidade: não me interpretem mal, eu amo a sintaxe do tipo C! ;) Só estou perguntando se poderia ser melhor. A exigência ()
tem vantagens de desempenho ou facilita a compreensão do código? Estou realmente curioso para saber exatamente qual é o motivo.
fonte
()
, mas o que se destaca em seu post é aif (expression == true)
afirmação. Você se preocupa com supérfluos()
's, no entanto, em seguida, usar um supérfluo== true
:)Respostas:
Para idiomas que usam funções de primeira classe , é bastante comum que a sintaxe de referência a uma função seja:
enquanto o ato de chamar essa função é:
a
no exemplo acima, seria referência à função acima (e você poderia chamá-la fazendo issoa()
), enquantob
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 se referindo à função.
fonte
make_list
que agrupe seus argumentos em uma lista, há uma grande diferença entref(make_list)
passar uma função paraf
ou passar uma lista vazia.SATInstance.solve
ou alguma outra função pura incrivelmente cara para outra função sem imediatamente tentar executá-la. Isso também torna as coisas realmente estranhas, sesomeFunction
algo é diferente, dependendo sesomeFunction
é declarado puro. Por exemplo, se eu tiver (pseudocódigo)func f() {return g}
,func g() {doSomethingImpure}
efunc apply(x) {x()}
, em seguida,apply(f)
chamaf
. Se eu declaro que issof
é puro, repentinamenteapply(f)
passag
paraapply
,apply
chamag
e faz algo impuro.apply(f)
, seg
é impuro (eapply
também?), Tudo é impuro e quebra, é claro.foo.bar()
não é uma operação "chamar métodobar
de objetofoo
", mas duas operações "desreferenciar o campobar
do objetofoo
e depois chamar o objeto retornado desse campo". Portanto,.
é o operador seletor de campo e()
é o operador de chamada e eles são independentes.De fato, Scala permite isso, embora exista uma convenção a seguir: se o método tiver efeitos colaterais, parênteses devem ser usados de qualquer maneira.
Como escritor de compilador, consideraria conveniente a presença garantida de parênteses; Eu sempre saberia que é uma chamada de método e não precisaria criar uma bifurcação para o caso estranho.
Como programador e leitor de código, 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âmetro diferente de um método que possui parâmetros?
fonte
def foo()
é um método com uma lista de parâmetros vazia edef foo
é um método sem lista de parâmetros e os dois são coisas diferentes ! Em geral, um método sem lista de parâmetros precisa ser chamado sem lista de argumentos e um método com uma lista de parâmetros vazia precisa ser chamado com uma lista de argumentos vazia. Chamar um método sem uma lista de parâmetros com um argumento vazio é realmente…apply
método do objeto retornado pela chamada de método com uma lista de argumentos vazia, enquanto chamar um método com uma lista de parâmetros vazia sem uma lista de argumentos pode, dependendo do contexto, ser interpretado de várias formas como chamar o método com uma lista de argumentos vazia, expansão de η para um método parcialmente aplicado (ou seja, "referência de método") ou talvez não seja compilado. Além disso, Scala segue o Princípio de acesso uniforme, não distinguindo (sintaticamente) entre chamar um método sem uma lista de argumentos e referenciar um campo.Este é realmente um acaso bastante sutil de escolhas de sintaxe. Vou falar com linguagens funcionais, que são baseadas no cálculo lambda digitado.
Nas referidas linguagens, toda função tem exatamente um argumento . O que geralmente consideramos "múltiplos argumentos" é na verdade um único parâmetro do tipo de produto. Por exemplo, a função que compara dois números inteiros:
leva um único par de números inteiros. Os parênteses não indicam os parâmetros da função; eles são usados para combinar o padrão com o argumento. Para convencê-lo de que esse é realmente um argumento, podemos usar as funções projetivas em vez da correspondência de padrões para desconstruir o par:
Assim, os parênteses são realmente uma conveniência que permite padronizar a correspondência e salvar algumas digitações. Eles não são necessários.
E uma função que ostensivamente não tem argumentos? Essa função realmente tem domínio
Unit
. O único membro deUnit
geralmente é escrito como()
, é por isso que os parênteses aparecem.Para chamar essa função, teríamos que aplicá-la a um valor do tipo
Unit
, que deve ser()
, para que acabemos escrevendosay_hi ()
.Portanto, não existe realmente uma lista de argumentos!
fonte
()
assemelham a colheres, menos o cabo.leq
função que usa<
e não<=
é bastante confusa!()
para representar a unidade. Eu não ficaria surpreso se pelo menos parte do motivo fosse parecido com uma "função sem parâmetros". No entanto, há outra explicação possível para a sintaxe. Na álgebra de tipos,Unit
é de fato a identidade dos tipos de produtos. Um produto binário é escrito como(a,b)
, poderíamos escrever um produto unário como(a)
, portanto, uma extensão lógica seria escrever o produto nulo da maneira mais simples()
.Em Javascript, por exemplo, usar um nome de método sem () retorna a própria função sem executá-la. Dessa forma, você pode, por exemplo, passar a função como argumento para outro método.
Em Java, optou-se por um identificador seguido por () ou (...) significa uma chamada de método, enquanto um identificador sem () se refere 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 uma variável de membro. De fato, o mesmo nome pode ser usado para um método e uma variável membro, ambos estarão acessíveis com sua respectiva sintaxe.
fonte
Perl Best Practices
fazObject::toString
identifica um método.A sintaxe segue a semântica, então vamos começar pela semântica:
Na verdade, existem várias maneiras:
Ter uma sintaxe semelhante para diferentes usos é a melhor maneira de criar uma linguagem ambígua ou, pelo menos, confusa (e temos o suficiente).
Nos idiomas C e C:
func()
&func
(C criando um ponteiro de função)someVariable::someMethod
por exemplo (limitado ao receptor do método, mas ainda útil)Observe como cada uso apresenta uma sintaxe diferente, permitindo diferenciá-los facilmente.
fonte
::
operador em Java é um operador de aplicativo parcial, não um operador de currying. Aplicação parcial é uma operação que pega uma função e um ou mais valores e retorna uma função que aceita menos valores. O curry opera apenas em funções, não em valores.Em uma linguagem com efeitos colaterais, é muito útil para a OMI diferenciar entre (em teoria, sem efeito colateral) a leitura de uma variável
e chamar uma função, que pode resultar em efeitos colaterais
OTOH, se não houver efeitos colaterais (além dos codificados no sistema de tipos), não há sentido em diferenciar entre ler uma variável e chamar uma função sem argumentos.
fonte
noun.verb
A sintaxe pode ter um significado tão claro quantonoun.verb()
e um pouco menos confuso. Da mesma forma, uma funçãomember_
retornar uma referência esquerda pode oferecer uma sintaxe amigável do que uma função setter típico:obj.member_ = new_value
vs.obj.set_member(new_value)
(o_
postfix é uma dica dizendo "exposto" uma reminiscência de_
prefixos para as funções "ocultas").noun.verb
significa invocação de função, como você a diferencia do mero acesso de membro?Nenhuma das outras respostas tentou abordar a questão: quanta redundância deve haver no design de um idioma? Porque, mesmo que você possa projetar uma linguagem para
x = sqrt y
definir x como raiz quadrada de y, isso não significa que você deva necessariamente.Em um idioma 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ê pretendido e muito difícil de depurar. (Como qualquer pessoa que tenha trabalhado com expressões regulares saberá.) Um pouco de redundância é uma coisa boa, pois permite que muitos de seus erros sejam detectados, e quanto mais redundância houver, maior a probabilidade de o diagnóstico ser preciso. Agora, é claro, você pode levar isso muito longe (nenhum de nós quer escrever no COBOL hoje em dia), mas existe um equilíbrio certo.
A redundância também ajuda na legibilidade, porque há mais pistas. O inglês sem redundância seria muito difícil de ler, e o mesmo se aplica às linguagens de programação.
fonte
:=
e comparação==
, sendo=
apenas utilizável para inicialização em tempo de compilação, a probabilidade de erros de digitação transformando comparações em atribuições seria muito reduzida.()
a invocação de uma função de argumento zero, mas também exigiria um token para "pegar o endereço de" uma função. A ação anterior é muito mais comum, portanto, salvar um token no caso menos comum não é tão útil.Na maioria dos casos, essas são escolhas sintáticas da gramática do idioma. É útil para a gramática que as várias construções individuais sejam (relativamente) inequívocas quando tomadas em conjunto. (Se houver ambiguidades, como em algumas declarações de C ++, deve haver regras específicas para a resolução.) O compilador não tem latitude para adivinhar; é necessário seguir a especificação do idioma.
O Visual Basic, de 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 exigem parênteses, apenas argumentos separados por vírgula, se houver. As funções devem ser chamadas como parte de uma expressão e requerem os parênteses.
É uma distinção relativamente desnecessária que torna a refatoração manual entre as duas formas mais dolorosa do que precisa ser.
(Por outro lado Visual Basic usa os mesmos parênteses
()
para referências matriz como para chamadas de função, de modo que as referências de matriz olhar como chamadas de função. E isso facilita refatoração manual de um array em uma chamada de função! Então, poderíamos ponderar outras línguas usam[]
's para referências de matriz, mas discordo ...)Nas linguagens C e C ++, o conteúdo das variáveis é acessado automaticamente usando seu nome e, se você quiser se referir à própria variável em vez de seu conteúdo, aplica o
&
operador unário .Esse tipo de mecanismo também pode ser aplicado aos nomes das funções. O nome da função bruta pode implicar uma chamada de função, enquanto um
&
operador unário seria usado para se referir à função (ela mesma) como dados. Pessoalmente, gosto da ideia de acessar funções sem argumento sem efeitos colaterais com a mesma sintaxe que as variáveis.Isso é perfeitamente plausível (assim como outras opções sintáticas para isso).
fonte
call <procedure name> (<arguments>)
? Certeza de que era uma forma válida de utilização de procedimentos de volta quando eu fiz programação QuickBASIC em outra vida ...Call
para um método em que quase nunca preciso no código de produção "normal".Consistência e legibilidade.
Se eu descobrir que você chamar a função
X
como esta:X(arg1, arg2, ...)
, então eu espero que ele funcione da mesma forma para sem argumentos:X()
.Agora, ao mesmo tempo, aprendi que você pode definir a variável como algum símbolo e usá-la assim:
Agora, o que eu pensaria quando encontrar isso?
Seu palpite é tão bom quanto o meu. Em circunstâncias normais,
X
é uma variável. Mas se seguíssemos o seu caminho, também poderia ser uma função! Devo lembrar se qual símbolo é mapeado para qual grupo (variáveis / funções)?Poderíamos impor restrições artificiais, por exemplo, "Funções começam com letra maiúscula. Variáveis começam com letra minúscula", mas isso não é necessário e torna as coisas mais complicadas, embora às vezes possa ajudar alguns dos objetivos do projeto.
Nota lateral 1: Outras respostas ignoram completamente mais uma coisa: a linguagem pode permitir que você use o mesmo nome para a função e a variável e faça a distinção por contexto. Veja
Common Lisp
como um exemplo. Funçãox
e variávelx
coexistem perfeitamente.Side-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 umobject
. Sejaobject
um objeto, uma classe ou um espaço para nome, não importa muito, mas ele me diz que pertence a algum lugar . Isso significa que você deve adicionar sintaxe artificialobject.
para cada função global ou criar algumasobject
para armazenar todas as funções globais.De qualquer forma, você perde a capacidade de ter espaços de nomes separados para funções e variáveis.
fonte
Para adicionar às outras respostas, pegue este exemplo C:
Se o operador de chamada fosse opcional, não haveria maneira de distinguir entre a atribuição de função e a chamada de 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.
fonte
function cross(a, b) { return a + b; }