Enquanto estava curioso na página principal do site de uma linguagem de programação de script, encontrei esta passagem:
Quando um sistema fica grande demais para ficar na sua cabeça, você pode adicionar tipos estáticos.
Isso me fez lembrar que em muitas guerras religiosas entre linguagens estáticas compiladas (como Java) e linguagens dinâmicas interpretadas (principalmente Python porque é mais usada, mas é um "problema" compartilhado entre a maioria das linguagens de script), uma das queixas de estática os fãs das linguagens digitadas em relação às linguagens dinamicamente digitadas é que eles não se adaptam bem a projetos maiores porque "um dia você esquecerá o tipo de retorno de uma função e precisará procurar, enquanto nas linguagens tipicamente estáticas tudo é declarado explicitamente ".
Eu nunca entendi declarações como esta. Para ser sincero, mesmo se você declarar o tipo de retorno de uma função, poderá e irá esquecê-lo depois de escrever muitas linhas de código e ainda precisará retornar à linha na qual é declarada usando a função de pesquisa de seu editor de texto para verificá-lo.
Além disso, como as funções são declaradas com type funcname()...
, sem saber que type
você terá que pesquisar sobre cada linha na qual a função é chamada, porque você só sabe funcname
, enquanto em Python e similares você poderia procurar apenas def funcname
ou o function funcname
que acontece apenas uma vez, em a declaração.
Além disso, com REPLs é trivial testar uma função para seu tipo de retorno com entradas diferentes, enquanto que com linguagens estáticas, você precisará adicionar algumas linhas de código e recompilar tudo apenas para saber o tipo declarado.
Portanto, além de conhecer o tipo de retorno de uma função que claramente não é um ponto forte das linguagens estáticas, como a digitação estática é realmente útil em projetos maiores?
fonte
Respostas:
Não é trivial. Não é trivial em tudo . É apenas trivial fazer isso para funções triviais.
Por exemplo, você pode definir trivialmente uma função em que o tipo de retorno depende inteiramente do tipo de entrada.
Nesse caso,
getAnswer
não possui realmente um único tipo de retorno. Não há nenhum teste que você possa escrever que chame isso com uma entrada de amostra para saber qual é o tipo de retorno. Ele vai sempre depender do argumento real. Em tempo de execução.E isso nem inclui funções que, por exemplo, executam pesquisas no banco de dados. Ou faça as coisas com base na entrada do usuário. Ou procure variáveis globais, que são obviamente de um tipo dinâmico. Ou altere seu tipo de retorno em casos aleatórios. Sem mencionar a necessidade de testar todas as funções individuais manualmente todas as vezes.
Fundamentalmente, provar o tipo de retorno da função no caso geral é literalmente matematicamente impossível (Problema de Parada). A única maneira de garantir o tipo de retorno é restringir a entrada para que a resposta a essa pergunta não se enquadre no domínio do Problema da Parada, impedindo programas que não são prováveis, e é isso que a digitação estática faz.
Linguagens de tipo estático têm coisas chamadas "ferramentas". São programas que ajudam você a fazer coisas com o seu código-fonte. Nesse caso, eu simplesmente clicaria com o botão direito e em Ir para a definição, graças ao Resharper. Ou use o atalho do teclado. Ou apenas passe o mouse e ele me dirá quais são os tipos envolvidos. Eu não me importo nem um pouco com arquivos grepping. Um editor de texto por si só é uma ferramenta patética para editar o código-fonte do programa.
A partir da memória,
def funcname
não seria suficiente no Python, pois a função poderia ser redesignada arbitrariamente. Ou pode ser declarado repetidamente em vários módulos. Ou nas aulas. Etc.Procurar arquivos pelo nome da função é uma operação primitiva terrível que nunca deve ser necessária. Isso representa uma falha fundamental do seu ambiente e ferramentas. O fato de você considerar a necessidade de uma pesquisa de texto no Python é um ponto importante contra o Python.
fonte
Pense em um projeto com muitos programadores que mudou ao longo dos anos. Você tem que manter isso. Há uma função
O que diabos isso faz? O que é
v
? De onde vem o elementoanswer
?Agora temos mais algumas informações -; precisa de um tipo de
AnswerBot
.Se formos para um idioma baseado em classe, podemos dizer
Agora podemos ter uma variável do tipo
AnswerBot
e chamar o métodogetAnswer
e todo mundo sabe o que faz. Quaisquer alterações são capturadas pelo compilador antes de qualquer teste de tempo de execução. Existem muitos outros exemplos, mas talvez isso lhe dê a idéia?fonte
Você parece ter alguns conceitos errados sobre como trabalhar com grandes projetos estáticos que podem estar atrapalhando seu julgamento. Aqui estão algumas dicas:
A maioria das pessoas que trabalha com linguagens de tipo estaticamente usa um IDE para a linguagem ou um editor inteligente (como vim ou emacs) que possui integração com ferramentas específicas da linguagem. Geralmente, existe uma maneira rápida de encontrar o tipo de uma função nessas ferramentas. Por exemplo, com o Eclipse em um projeto Java, há duas maneiras pelas quais você normalmente encontra o tipo de um método:
someVariable.
) e o Eclipse procurará o tiposomeVariable
e fornecerá uma lista suspensa de todos os métodos definidos nesse tipo; enquanto eu desço a lista, o tipo e a documentação de cada um são exibidos enquanto são selecionados. Observe que isso é muito difícil de obter com uma linguagem dinâmica, porque é difícil (ou, em alguns casos, impossível) para o editor determinar qual é o tiposomeVariable
, portanto, não é possível gerar a lista correta facilmente. Se eu quiser usar um método,this
basta pressionar ctrl + space para obter a mesma lista (embora, neste caso, não seja tão difícil de obter para idiomas dinâmicos).Como você pode ver, isso é um pouco melhor do que as ferramentas típicas disponíveis para linguagens dinâmicas (não que isso seja impossível em linguagens dinâmicas, pois algumas possuem uma funcionalidade IDE muito boa - o smalltalk é algo que chama a atenção - mas é mais difícil para uma linguagem dinâmica e, portanto, menos provável de estar disponível).
As ferramentas de linguagem estática normalmente fornecem recursos de pesquisa semântica, ou seja, eles podem encontrar a definição e referências a símbolos específicos com precisão, sem a necessidade de realizar uma pesquisa de texto. Por exemplo, usando o Eclipse para um projeto Java, posso destacar um símbolo no editor de texto e clicar com o botão direito do mouse e escolher 'ir para a definição' ou 'localizar referências' para executar uma dessas operações. Você não precisa procurar o texto de uma definição de função, porque seu editor já sabe exatamente onde está.
No entanto, o inverso é que procurar por uma definição de método por texto realmente não funciona tão bem em um grande projeto dinâmico como você sugere, pois pode haver facilmente vários métodos com o mesmo nome em um projeto e você provavelmente não tem. ferramentas prontamente disponíveis para desambiguar qual delas você está invocando (porque essas ferramentas são difíceis de escrever na melhor das hipóteses, ou impossíveis no caso geral), então você terá que fazer isso manualmente.
Não é impossível ter um REPL para uma linguagem de tipo estaticamente. Haskell é o exemplo que vem à mente, mas também existem REPLs para outras linguagens de tipo estaticamente. Mas o ponto é que você não precisa executar o código para encontrar o tipo de retorno de uma função em uma linguagem estática - ela pode ser determinada pelo exame sem a necessidade de executar nada.
As chances são de que, mesmo que você precisasse fazer isso, não precisaria recompilar tudo . A maioria das linguagens estáticas modernas possui compiladores incrementais que compilarão apenas a pequena parte do seu código que foi alterada, para que você possa obter feedback quase instantâneo para erros de tipo se você fizer um. O Eclipse / Java, por exemplo, destacará os erros de digitação enquanto você ainda os digita .
fonte
You seem to have a few misconceptions about working with large static projects that may be clouding your judgement.
Bem, eu tenho apenas 14 anos e programa apenas a menos de um ano no Android, então é possível, eu acho.Compare com digamos, javascript, Ruby ou Smalltalk, onde os desenvolvedores redefinem a funcionalidade da linguagem principal em tempo de execução. Isso dificulta a compreensão do grande projeto.
Projetos maiores não têm apenas mais pessoas, eles têm mais tempo. Tempo suficiente para todos esquecerem ou seguirem em frente.
Curiosamente, um conhecido meu tem uma programação segura "Job For Life" no Lisp. Ninguém, exceto a equipe, pode entender a base de código.
fonte
Anecdotally, an acquaintance of mine has a secure "Job For Life" programming in Lisp. Nobody except the team can understand the code-base.
É tão ruim assim? A personalização que eles adicionaram não os ajuda a serem mais produtivos?Não se trata de você esquecer o tipo de retorno - isso sempre vai acontecer. É sobre a ferramenta poder informar que você esqueceu o tipo de retorno.
Esta é uma questão de sintaxe, que não tem nenhuma relação com a digitação estática.
A sintaxe da família C é realmente hostil quando você deseja procurar uma declaração sem ter ferramentas especializadas à sua disposição. Outros idiomas não têm esse problema. Veja a sintaxe da declaração de Rust:
Qualquer idioma pode ser interpretado e qualquer idioma pode ter um REPL.
Eu responderei de uma maneira abstrata.
Um programa consiste em várias operações e essas operações são definidas da maneira que são devido a algumas suposições feitas pelo desenvolvedor.
Algumas suposições estão implícitas e outras são explícitas. Algumas suposições dizem respeito a uma operação próxima a elas, outras dizem respeito a uma operação fora delas. Uma suposição é mais fácil de identificar quando é expressa explicitamente e o mais próximo possível dos locais onde seu valor de verdade é importante.
Um bug é a manifestação de uma suposição que existe no programa, mas não é válida em alguns casos. Para rastrear um bug, precisamos identificar a suposição errônea. Para remover o bug, precisamos remover essa suposição do programa ou alterar algo para que a suposição realmente se mantenha.
Eu gostaria de categorizar suposições em dois tipos.
O primeiro tipo são as suposições que podem ou não ser mantidas, dependendo das entradas do programa. Para identificar uma suposição errônea desse tipo, precisamos procurar no espaço todas as entradas possíveis do programa. Usando suposições educadas e pensamento racional, podemos diminuir o problema e procurar em um espaço muito menor. Mas, ainda assim, à medida que um programa cresce um pouco, seu espaço de entrada inicial cresce a uma taxa enorme - até o ponto em que pode ser considerado infinito para todos os fins práticos.
O segundo tipo são as suposições que definitivamente valem para todas as entradas, ou são definitivamente erradas para todas as entradas. Quando identificamos uma suposição desse tipo como errônea, nem precisamos executar o programa ou testar nenhuma entrada. Quando identificamos uma suposição desse tipo como correta, temos menos um suspeito para nos preocupar quando rastreamos um bug ( qualquer bug). Portanto, há valor em ter o maior número possível de suposições pertencer a esse tipo.
Para colocar uma suposição na segunda categoria (sempre verdadeira ou sempre falsa, independente das entradas), precisamos de uma quantidade mínima de informações para estar disponível no local em que a suposição é feita. No código-fonte de um programa, as informações ficam obsoletas rapidamente (por exemplo, muitos compiladores não fazem análises interprocedurais, o que torna qualquer chamada um limite rígido para a maioria das informações). Precisamos de uma maneira de manter as informações necessárias atualizadas (válidas e próximas).
Uma maneira é ter a fonte dessas informações o mais próximo possível do local onde elas serão consumidas, mas isso pode ser impraticável para a maioria dos casos de uso. Outra maneira é repetir as informações com frequência, renovando sua relevância no código-fonte.
Como você já pode imaginar, os tipos estáticos são exatamente isso - faróis de informações de tipo espalhados pelo código-fonte. Essas informações podem ser usadas para colocar a maioria das suposições sobre correção de tipo na segunda categoria, o que significa que quase qualquer operação pode ser classificada como sempre correta ou sempre incorreta com relação à compatibilidade de tipo.
Quando nossos tipos estão incorretos, a análise economiza tempo, chamando a atenção do erro mais cedo do que tarde. Quando nossos tipos estão corretos, a análise economiza tempo, garantindo que, quando ocorrer um erro, possamos excluir imediatamente erros de tipo.
fonte
Você se lembra do velho ditado "lixo dentro, lixo fora", bem, é isso que a digitação estática ajuda a evitar. Não é uma panacéia universal, mas o rigor sobre o tipo de dados que uma rotina aceita e retorna significa que você tem alguma garantia de que está trabalhando corretamente com ela.
Portanto, uma rotina getAnswer que retorna um número inteiro não será útil quando você tentar usá-la em uma chamada baseada em string. A digitação estática já está lhe dizendo para tomar cuidado, que você provavelmente está cometendo um erro. (e, com certeza, você poderá substituí-lo, mas precisará saber exatamente o que está fazendo e especificá-lo no código usando uma conversão. Geralmente, porém, você não deseja fazer isso - hackear um peg redondo em um buraco quadrado nunca funciona bem no final)
Agora você pode ir mais longe usando tipos complexos, criando uma classe que tem funcionalidade de minério. Você pode começar a repassá-los e, de repente, obterá muito mais estrutura em seu programa. Programas estruturados são aqueles que são muito mais fáceis de fazer funcionar corretamente e também mantêm.
fonte