A maioria das linguagens de programação (linguagens de tipo dinâmico e estaticamente) possui palavras-chave e / ou sintaxe especiais que parecem muito diferentes do que declarar variáveis para declarar funções. Vejo funções como apenas declarar outra entidade nomeada:
Por exemplo, em Python:
x = 2
y = addOne(x)
def addOne(number):
return number + 1
Por que não:
x = 2
y = addOne(x)
addOne = (number) =>
return number + 1
Da mesma forma, em uma linguagem como Java:
int x = 2;
int y = addOne(x);
int addOne(int x) {
return x + 1;
}
Por que não:
int x = 2;
int y = addOne(x);
(int => int) addOne = (x) => {
return x + 1;
}
Essa sintaxe parece uma maneira mais natural de declarar algo (seja uma função ou uma variável) e uma palavra-chave a menos como def
ou function
em alguns idiomas. E, na IMO, é mais consistente (eu procuro o mesmo local para entender o tipo de uma variável ou função) e provavelmente torna o analisador / gramática um pouco mais simples de escrever.
Eu sei que muito poucas linguagens usam essa ideia (CoffeeScript, Haskell), mas as linguagens mais comuns têm sintaxe especial para funções (Java, C ++, Python, JavaScript, C #, PHP, Ruby).
Mesmo em Scala, que suporta os dois lados (e tem inferência de tipo), é mais comum escrever:
def addOne(x: Int) = x + 1
Ao invés de:
val addOne = (x: Int) => x + 1
Na IMO, pelo menos em Scala, essa é provavelmente a versão mais facilmente compreensível, mas esse idioma raramente é seguido:
val x: Int = 1
val y: Int = addOne(x)
val addOne: (Int => Int) = x => x + 1
Estou trabalhando na minha própria linguagem de brinquedos e estou me perguntando se há alguma armadilha se eu projetar minha linguagem dessa maneira e se há alguma razão histórica ou técnica, esse padrão não é amplamente seguido.
(int => int) addOne = (x) => {
é muito mais "especial" e "complexo" do queint addOne(int) {
...Respostas:
Penso que a razão é que a maioria das linguagens populares provém ou foram influenciadas pela família C de línguas, em oposição às linguagens funcionais e sua raiz, o cálculo lambda.
E nessas linguagens, funções não são apenas outro valor:
const
,readonly
oufinal
para proibir mutação), mas funções não podem ser realocados.De uma perspectiva mais técnica, código (que é composto de funções) e dados são separados. Eles normalmente ocupam partes diferentes da memória e são acessados de maneira diferente: o código é carregado uma vez e depois executado apenas (mas não é lido ou gravado), enquanto os dados são frequentemente constantemente alocados e desalocados e estão sendo gravados e lidos, mas nunca executados.
E como C era para ser "próximo ao metal", faz sentido espelhar essa distinção também na sintaxe da linguagem.
A abordagem "função é apenas um valor", que forma a base da programação funcional, ganhou força nas linguagens comuns apenas recentemente, como evidenciado pela introdução tardia de lambdas em C ++, C # e Java (2011, 2007, 2014).
fonte
É porque é importante que os humanos reconheçam que funções não são apenas "outra entidade nomeada". Às vezes, faz sentido manipulá-los como tal, mas eles ainda podem ser reconhecidos rapidamente.
Realmente não importa o que o computador pense sobre a sintaxe, pois uma bolha incompreensível de caracteres é boa para uma máquina interpretar, mas isso seria quase impossível para os humanos entenderem e manterem.
É realmente a mesma razão pela qual temos tempo e para loops, switch e se mais, etc, mesmo que todos eles acabem se resumindo a uma instrução de comparação e salto. O motivo é que existe para o benefício dos humanos em manter e entender o código.
Ter suas funções como "outra entidade nomeada" da maneira que você está propondo tornará seu código mais difícil de ver e, portanto, mais difícil de entender.
fonte
whatsisname
foi abordar mais o primeiro ponto (e alertar sobre algum perigo de remover essas falhas), enquanto o seu comentário está mais relacionado à segunda parte da pergunta. É possível, de fato, alterar essa sintaxe (e, como você descreveu, isso já foi feito muitas vezes ...), mas não serve para todos (já que oop também não serve para todos.Você pode estar interessado em aprender que, em tempos pré-históricos, uma linguagem chamada ALGOL 68 usava uma sintaxe próxima à proposta. Reconhecendo que os identificadores de função estão vinculados a valores, assim como outros identificadores, você pode, nesse idioma, declarar uma função (constante) usando a sintaxe
Concretamente, seu exemplo seria
Reconhecendo a redundância em que o tipo inicial pode ser lido no RHS da declaração e, sendo um tipo de função sempre iniciado
PROC
, isso pode (e geralmente seria) ser contratado paramas observe que o
=
item ainda vem antes da lista de parâmetros. Observe também que, se você quiser uma variável de função (à qual outro valor do mesmo tipo de função possa ser atribuído posteriormente), ele=
deverá ser substituído por:=
, fornecendo um dosNo entanto, neste caso, ambas as formas são de fato abreviações; como o identificador
func var
designa uma referência a uma função gerada localmente, o formulário totalmente expandido seriaÉ fácil se acostumar com essa forma sintática específica, mas claramente não tinha muitos seguidores em outras linguagens de programação. Mesmo linguagens de programação funcionais como Haskell preferem o estilo
f n = n+1
com=
a seguir a lista de parâmetros. Eu acho que o motivo é principalmente psicológico; afinal, mesmo os matemáticos nem sempre preferem, como eu, f = n ⟼ n + 1 sobre f ( n ) = n + 1.A propósito, a discussão acima destaca uma diferença importante entre variáveis e funções: as definições de funções geralmente vinculam um nome a um valor específico da função, que não pode ser alterado posteriormente, enquanto as definições de variáveis geralmente introduzem um identificador com um valor inicial , mas que pode mudar mais tarde. (Não é uma regra absoluta; variáveis de função e constantes não funcionais ocorrem na maioria dos idiomas.) Além disso, em idiomas compilados, o valor vinculado em uma definição de função geralmente é uma constante em tempo de compilação, para que as chamadas para a função possam ser compilado usando um endereço fixo no código. Em C / C ++, isso é mesmo um requisito; o equivalente do ALGOL 68
não pode ser escrito em C ++ sem introduzir um ponteiro de função. Esse tipo de limitação específica justifica o uso de uma sintaxe diferente para definições de função. Mas eles dependem da semântica da linguagem e a justificativa não se aplica a todas as línguas.
fonte
Você mencionou Java e Scala como exemplos. No entanto, você ignorou um fato importante: essas não são funções, são métodos. Métodos e funções são fundamentalmente diferentes. Funções são objetos, métodos pertencem a objetos.
No Scala, que possui funções e métodos, existem as seguintes diferenças entre métodos e funções:
Portanto, sua substituição proposta simplesmente não funciona, pelo menos nesses casos.
fonte
As razões pelas quais consigo pensar são:
fonte
Mudando a questão, se alguém não está interessado em tentar editar o código-fonte em uma máquina com muita restrição de RAM ou minimizar o tempo para lê-lo em um disquete, o que há de errado em usar palavras-chave?
Certamente é melhor ler do
x=y+z
questore the value of y plus z into x
, mas isso não significa que os caracteres de pontuação sejam inerentemente "melhores" que as palavras-chave. Se as variáveisi
,j
ek
sãoInteger
, ex
éReal
, considere as seguintes linhas em Pascal:A primeira linha executará uma divisão de número inteiro truncada, enquanto a segunda executará uma divisão de número real. A distinção pode ser feita muito bem porque Pascal usa
div
como seu operador de divisão de número inteiro truncado, em vez de tentar usar um sinal de pontuação que já tem outro objetivo (divisão em número real).Embora existam alguns contextos nos quais pode ser útil tornar concisa a definição de uma função (por exemplo, um lambda que é usado como parte de outra expressão), geralmente as funções se destacam e são facilmente reconhecíveis visualmente como funções. Embora seja possível tornar a distinção muito mais sutil e usar apenas caracteres de pontuação, qual seria o objetivo? Dizer
Function Foo(A,B: Integer; C: Real): String
deixa claro qual é o nome da função, quais parâmetros ela espera e o que ela retorna. Talvez alguém possa encurtá-lo em seis ou sete caracteres substituindo-oFunction
por alguns caracteres de pontuação, mas o que seria ganho?Outro aspecto a ser observado é que, na maioria das estruturas, existe uma diferença fundamental entre uma declaração que sempre associa um nome a um método específico ou a uma ligação virtual específica, e que cria uma variável que identifica inicialmente um método ou ligação específica, mas pode ser alterado em tempo de execução para identificar outro. Como esses conceitos são semanticamente muito diferentes na maioria das estruturas procedurais, faz sentido que eles tenham sintaxe diferente.
fonte
void f() {}
é realmente mais curto que o equivalente lambda em C ++ (auto f = [](){};
), C # (Action f = () => {};
) e Java (Runnable f = () -> {};
). A concisão das lambdas vem da inferência e omissão de tiposreturn
, mas não acho que isso esteja relacionado ao que essas perguntas fazem.Bem, o motivo pode ser que esses idiomas não sejam funcionais o suficiente, por assim dizer. Em outras palavras, você raramente define funções. Portanto, o uso de uma palavra-chave extra é aceitável.
Nos idiomas da herança ML ou Miranda, OTOH, você define funções na maioria das vezes. Veja um código Haskell, por exemplo. É literalmente principalmente uma sequência de definições de funções, muitas delas possuem funções locais e funções locais dessas funções locais. Portanto, uma palavra-chave divertida em Haskell seria um erro tão grande quanto exigir uma declaração de afirmação em uma linguagem imperativa para começar com a atribuição . A atribuição de causas é provavelmente de longe a declaração mais frequente.
fonte
Pessoalmente, não vejo falha fatal em sua ideia; você pode achar que é mais complicado do que o esperado expressar algumas coisas usando sua nova sintaxe e / ou pode precisar revê-la (adicionando vários casos especiais e outros recursos, etc.), mas duvido que você se encontre precisando abandonar completamente a ideia.
A sintaxe que você propôs parece mais ou menos uma variante de alguns dos estilos de notação às vezes usados para expressar funções ou tipos de funções em matemática. Isso significa que, como todas as gramáticas, provavelmente atrairá mais alguns programadores do que outros. (Como matemático, eu gosto disso.)
No entanto, você deve observar que, na maioria dos idiomas, a
def
sintaxe -style (ou seja, a sintaxe tradicional) se comporta de maneira diferente de uma atribuição de variável padrão.C
eC++
, as funções geralmente não são tratadas como "objetos", ou seja, pedaços de dados digitados a serem copiados e colocados na pilha e outros enfeites. (Sim, você pode ter ponteiros de função, mas eles ainda apontam para código executável, não para "dados" no sentido típico.)self
(que, a propósito, não é realmente uma palavra-chave; você pode tornar qualquer identificador válido o primeiro argumento de um método).Você precisa considerar se sua nova sintaxe precisa (e esperamos intuitivamente) representar o que o compilador ou intérprete está realmente fazendo. Pode ajudar a ler, digamos, a diferença entre lambdas e métodos no Ruby; isso lhe dará uma idéia de como seu paradigma de funções são apenas dados difere do paradigma OO / procedimento típico.
fonte
Para alguns idiomas, funções não são valores. Em tal linguagem para dizer que
é uma definição de função, enquanto
declara uma constante, é confuso porque você está usando uma sintaxe para significar duas coisas.
Outros idiomas, como ML, Haskell e Scheme, tratam funções como valores de primeira classe, mas fornecem ao usuário uma sintaxe especial para declarar constantes com valor de função. * Eles estão aplicando a regra de que "o uso reduz a forma". Ou seja, se uma construção é comum e detalhada, você deve fornecer uma abreviação ao usuário. É deselegante fornecer ao usuário duas sintaxes diferentes que significam exatamente a mesma coisa; às vezes a elegância deve ser sacrificada à utilidade.
Se, em seu idioma, as funções são de 1ª classe, por que não tentar encontrar uma sintaxe concisa o suficiente para que você não fique tentado a encontrar um açúcar sintático?
- Editar -
Outra questão que ninguém levantou (ainda) é a recursão. Se você permitir
e você permite
segue-se que você deve permitir
Em um idioma preguiçoso (como Haskell), não há problema aqui. Em um idioma com praticamente nenhuma verificação estática (como LISP), não há problema aqui. Mas em uma linguagem ansiosa verificada estaticamente, você deve ter cuidado com a definição das regras de verificação estática, se desejar permitir as duas primeiras e proibir a última.
- Fim da edição -
* Pode-se argumentar que Haskell não pertence a esta lista. Ele fornece duas maneiras de declarar uma função, mas ambas são, em certo sentido, generalizações da sintaxe para declarar constantes de outros tipos
fonte
Isso pode ser útil em linguagens dinâmicas em que o tipo não é tão importante, mas não é legível em linguagens estáticas de tipo, onde você sempre deseja saber o tipo de sua variável. Além disso, nas linguagens orientadas a objetos, é muito importante conhecer o tipo de sua variável, para saber quais operações ela suporta.
No seu caso, uma função com 4 variáveis seria:
Quando olho para o cabeçalho da função e vejo (x, y, z, s), mas não conheço os tipos dessas variáveis. Se eu quiser saber o tipo de
z
qual é o terceiro parâmetro, terei que examinar o início da função e começar a contar 1, 2, 3 e depois ver que o tipo édouble
. No primeiro modo, olho diretamente e vejodouble z
.fonte
var addOne = (int x, long y, double z, String s) => { x + 1 }
uma linguagem estaticamente não-imbecil de sua escolha (exemplos: C #, C ++, Scala). Mesmo a inferência de tipo local muito limitada usada pelo C # é completamente suficiente para isso. Portanto, esta resposta está apenas criticando uma sintaxe específica duvidosa em primeiro lugar, e que na verdade não é usada em nenhum lugar (embora a sintaxe de Haskell tenha um problema muito semelhante).Há uma razão muito simples para se fazer essa distinção na maioria dos idiomas: é necessário distinguir avaliação e declaração . Seu exemplo é bom: por que não gostar de variáveis? Bem, expressões de variáveis são avaliadas imediatamente.
Haskell tem um modelo especial em que não há distinção entre avaliação e declaração, razão pela qual não há necessidade de uma palavra-chave especial.
fonte
As funções são declaradas de maneira diferente de literais, objetos etc. na maioria dos idiomas porque são usadas de maneira diferente, depuradas de maneira diferente e apresentam diferentes fontes potenciais de erro.
Se uma referência de objeto dinâmico ou um objeto mutável for passado para uma função, a função poderá alterar o valor do objeto enquanto ele é executado. Esse tipo de efeito colateral pode dificultar o acompanhamento de uma função se estiver aninhada em uma expressão complexa, e esse é um problema comum em linguagens como C ++ e Java.
Considere a depuração de algum tipo de módulo do kernel em Java, onde cada objeto possui uma operação toString (). Embora seja esperado que o método toString () restaure o objeto, pode ser necessário desmontar e remontar o objeto para converter seu valor em um objeto String. Se você estiver tentando depurar os métodos que toString () chamará (em um cenário de gancho e modelo) para fazer seu trabalho e destacar acidentalmente o objeto na janela de variáveis da maioria dos IDEs, ele poderá travar o depurador. Isso ocorre porque o IDE tentará toString () o objeto que chama o próprio código que você está no processo de depuração. Nenhum valor primitivo é um lixo assim, porque o significado semântico dos valores primitivos é definido pela linguagem, não pelo programador.
fonte