Eu estava pensando por que existem (em todas as linguagens de programação que aprendi, como C ++, Java, Python) bibliotecas padrão como stdlib, em vez de ter "funções" semelhantes, sendo uma primitiva da própria linguagem.
Eu estava pensando por que existem (em todas as linguagens de programação que aprendi, como C ++, Java, Python) bibliotecas padrão como stdlib, em vez de ter "funções" semelhantes, sendo uma primitiva da própria linguagem.
Respostas:
Permitam-me expandir um pouco a boa resposta de @ Vincent (+1) :
Por que o compilador não pode simplesmente converter uma chamada de função em um conjunto de instruções?
Pode e faz isso por pelo menos dois mecanismos:
inline uma chamada de função - durante a tradução, o compilador pode substituir uma chamada de código fonte por sua implementação diretamente inline, em vez de fazer uma chamada real para a função. Ainda assim, a função precisa ter uma implementação definida em algum lugar e que possa estar na biblioteca padrão.
função intrínseca - intrínseca são funções das quais o compilador foi informado sem necessariamente encontrar a função em uma biblioteca. Eles geralmente são reservados para recursos de hardware que não são praticamente acessíveis de nenhuma outra maneira, sendo tão simples que até a sobrecarga de uma chamada para a função de biblioteca de linguagem assembly é considerada alta. (O compilador geralmente só pode incorporar automaticamente o código-fonte embutido em seu idioma, mas não as funções de assembly, que é onde entra o mecanismo intrínseco.)
Ainda assim, algumas vezes, a melhor opção é o compilador converter uma chamada de função no idioma de origem em uma chamada de função no código da máquina. Recursão, métodos virtuais e tamanho absoluto são alguns dos motivos pelos quais o embutimento nem sempre é possível / prático. (Outro motivo é a intenção da compilação, como compilação separada (módulos de objeto), unidades de carregamento separadas (por exemplo, DLLs)).
Também não há nenhuma vantagem real em tornar a maioria das funções de biblioteca padrão intrísicas (que codificariam muito mais conhecimento no compilador sem vantagem real); portanto, uma chamada de código de máquina novamente é frequentemente mais apropriada.
C é uma linguagem notável que possivelmente omitiu outras declarações de linguagem explícita em favor das funções padrão da biblioteca. Embora as bibliotecas existissem, essa linguagem mudou para fazer mais trabalho a partir das funções padrão da biblioteca e menos como declarações explícitas na gramática da linguagem. O IO em outros idiomas, por exemplo, recebeu frequentemente sua própria sintaxe na forma de várias instruções, enquanto a gramática C não define nenhuma instrução de IO, simplesmente adiando para sua biblioteca padrão para fornecer isso, tudo acessível por meio de chamadas de função, que o compilador já sabe como fazer.
print
: No 2.x, era uma declaração , com sua própria gramática especial, mas no 3.x, tornou-se apenas mais uma chamada de função. Veja PEP 3105 para a explicação oficial.
Isso é simplesmente para manter o idioma em si o mais simples possível. Você precisa distinguir entre um recurso da linguagem, como um tipo de loop ou maneiras de passar parâmetros para funções e assim por diante, e a funcionalidade comum que a maioria dos aplicativos precisa.
Bibliotecas são funções que podem ser úteis para muitos programadores e, portanto, são criadas como códigos reutilizáveis que podem ser compartilhados. As bibliotecas padrão são projetadas para serem funções muito comuns que os programadores normalmente precisam. Dessa forma, a linguagem de programação é imediatamente útil para uma ampla gama de programadores. As bibliotecas podem ser atualizadas e estendidas sem alterar os principais recursos do próprio idioma.
PHP
como exemplo, dificilmente faz diferença entre suas vastas funções linguísticas e a própria linguagem.
include
, require
e require_once
, se / for / while (programação estruturada), exceções, um sistema separado de 'valores de erro', complicado regras de digitação fracos, complicado regras de precedência, e assim por diante . Compare isso com a simplicidade de, digamos, Smalltalk, Scheme, Prolog, Forth, etc.;)
Além do que as outras respostas já disseram, colocar funções padrão em uma biblioteca é uma separação de preocupações :
O trabalho do compilador é analisar o idioma e gerar código para ele. Não é tarefa do compilador conter qualquer coisa que já possa ser escrita nesse idioma e fornecida como uma biblioteca.
É o trabalho da biblioteca padrão (a que está sempre disponível implicitamente) para fornecer as principais funcionalidades necessárias a praticamente todos os programas. Não é tarefa da biblioteca padrão conter todas as funções que podem ser úteis.
É o trabalho das bibliotecas padrão opcionais fornecer funcionalidades auxiliares que muitos programas podem dispensar, mas que ainda são bastante básicas e também essenciais para muitos aplicativos que justificam o envio com ambientes padrão. Não é tarefa dessas bibliotecas opcionais conter todo o código reutilizável que já foi escrito.
O trabalho das bibliotecas de usuários é fornecer coleções de funções reutilizáveis úteis. Não é tarefa das bibliotecas de usuários conter todo o código que já foi escrito.
O trabalho do código fonte de um aplicativo é fornecer os bits restantes de código que são realmente relevantes apenas para esse aplicativo.
Se você deseja um software de tamanho único, obtém algo incrivelmente complexo. Você precisa modularizar para reduzir a complexidade a níveis gerenciáveis. E você precisa modularizar para permitir implementações parciais :
A biblioteca de encadeamento é inútil no controlador incorporado de núcleo único. Permitir que a implementação da linguagem desse controlador incorporado não inclua a pthread
biblioteca é a coisa certa a fazer.
A biblioteca de matemática é inútil no microcontrolador que nem possui uma FPU. Mais uma vez, não ser forçado a fornecer funções como sin()
facilita muito a vida dos implementadores da sua linguagem para esse microcontrolador.
Até a biblioteca padrão principal é inútil quando você está programando um kernel. Você não pode implementar write()
sem um syscall no kernel e não pode implementar printf()
sem write()
. Como programador de kernel, é seu trabalho fornecer o write()
syscall, você não pode apenas esperar que ele esteja lá.
Uma linguagem que não permite tais omissões nas bibliotecas padrão simplesmente não é adequada para muitas tarefas . Se você deseja que seu idioma seja utilizável de maneira flexível em ambientes incomuns, ele deve ser flexível em quais bibliotecas padrão estão incluídas. Quanto mais o seu idioma depende de bibliotecas padrão, mais suposições são feitas no ambiente de execução e, portanto, restringe seu uso a ambientes que fornecem esses pré-requisitos.
Obviamente, linguagens de alto nível, como python e java, podem fazer muitas suposições em seu ambiente. E eles tendem a incluir muitas e muitas coisas em suas bibliotecas padrão. Linguagens de nível inferior como C fornecem muito menos em suas bibliotecas padrão e mantêm a biblioteca padrão principal muito menor. É por isso que você encontra um compilador C funcionando para praticamente qualquer arquitetura, mas pode não ser capaz de executar nenhum script python nele.
Um grande motivo pelo qual os compiladores e as bibliotecas padrão são separados é porque eles servem a dois propósitos diferentes (mesmo que ambos sejam definidos pela mesma especificação de idioma): o compilador converte código de nível superior em instruções da máquina, e a biblioteca padrão fornece pré-testes implementações da funcionalidade comumente necessária. Os escritores de compiladores valorizam a modularidade, assim como outros desenvolvedores de software. De fato, alguns dos primeiros compiladores C dividiram ainda mais o compilador em programas separados para pré-processamento, compilação e vinculação.
Essa modularidade oferece várias vantagens:
Historicamente falando (pelo menos da perspectiva de C), as versões originais de pré-padronização da linguagem não tinham uma biblioteca padrão. Fornecedores de SO e terceiros costumavam fornecer bibliotecas cheias de funcionalidades usadas com freqüência, mas implementações diferentes incluíam coisas diferentes e eram amplamente incompatíveis entre si. Quando C foi padronizado, eles definiram uma "biblioteca padrão" na tentativa de harmonizar essas implementações díspares e melhorar a portabilidade. A biblioteca padrão C desenvolvida separadamente da linguagem, como as bibliotecas Boost para C ++, mas posteriormente foram integradas às especificações da linguagem.
Resposta adicional em maiúsculas: Gerenciamento de propriedade intelectual
Um exemplo notável é a implementação do Math.Pow (duplo, duplo) no .NET Framework que foi comprado pela Microsoft à Intel e permanece não revelado, mesmo que o framework tenha sido de código aberto. (Para ser preciso, no caso acima, é uma chamada interna, e não uma biblioteca, mas a idéia é válida.) Uma biblioteca separada da própria linguagem (teoricamente também um subconjunto de bibliotecas padrão) pode dar aos patrocinadores da linguagem mais flexibilidade para desenhar os linha entre o que manter transparente e o que deve permanecer não divulgado (devido a contratos com terceiros ou outros motivos relacionados à PI).
Math.Pow
não menciona nenhuma compra ou nada sobre a Intel e fala sobre pessoas lendo o código-fonte da implementação da função.
Erros e depuração.
Bugs: Todo software possui bugs, sua biblioteca padrão possui bugs e seu compilador possui bugs. Como usuário da linguagem, é muito mais fácil encontrar e solucionar esses bugs quando eles estão na biblioteca padrão e não no compilador.
Depuração: É muito mais fácil para mim ver um rastreamento de pilha de uma biblioteca padrão e me dar uma idéia do que pode estar errado. Porque esse rastreamento de pilha tem código que eu entendo. É claro que você pode aprofundar e também rastrear suas funções intrínsecas, mas é muito mais fácil se estiver em um idioma que você usa o tempo todo, dia após dia.
Esta é uma excelente pergunta!
O padrão C ++, por exemplo, nunca especifica o que deve ser implementado no compilador ou na biblioteca padrão: apenas se refere à implementação . Por exemplo, símbolos reservados são definidos pelo compilador (como intrínseco) e pela biblioteca padrão, de forma intercambiável.
No entanto, todas as implementações de C ++ que eu conheço terão o número mínimo possível de intrínsecas fornecidas pelo compilador e o máximo possível pela biblioteca padrão.
Portanto, embora seja tecnicamente viável definir a biblioteca padrão como funcionalidade intrínseca no compilador, ela raramente é usada na prática.
Vamos considerar a idéia de mover algumas funcionalidades da biblioteca padrão para o compilador.
Vantagens:
Desvantagens:
std
) para ser experimentada.Isso significa que mover algo para o compilador é caro , agora e no futuro e, portanto, requer um caso sólido. Para algumas partes da funcionalidade, é necessário (elas não podem ser escritas como código regular), mas mesmo assim vale a pena extrair partes mínimas e genéricas para ir para o compilador e criar sobre elas na biblioteca padrão.
Como designer de linguagem, gostaria de repetir algumas das outras respostas aqui, mas fornecê-lo através dos olhos de alguém que está construindo um idioma.
Uma API não é concluída quando você adiciona tudo o que pode. Uma API é concluída quando você termina de tirar tudo o que pode.
Uma linguagem de programação deve ser especificada usando alguma linguagem. Você precisa transmitir o significado de qualquer programa escrito em seu idioma. Essa linguagem é muito difícil de escrever e ainda mais difícil de escrever bem. Em geral, tende a ser uma forma de inglês muito precisa e bem estruturada, usada para transmitir significado não ao computador, mas a outros desenvolvedores, especialmente aqueles que escrevem compiladores ou intérpretes para o seu idioma. Aqui está um exemplo da especificação do C ++ 11, [intro.multithread / 14]:
A sequência visível de efeitos colaterais em um objeto atômico M, em relação a um cálculo de valor B de M, é uma subseqüência máxima contígua de efeitos colaterais na ordem de modificação de M, em que o primeiro efeito colateral é visível em relação a B , e para todos os efeitos colaterais, não é o caso de B acontecer antes dele. O valor de um objeto atômico M, conforme determinado pela avaliação B, deve ser o valor armazenado por alguma operação na sequência visível de M em relação a B. [Nota: Pode ser demonstrado que a sequência visível dos efeitos colaterais de um valor A computação é única, considerando os requisitos de coerência abaixo. - end note]
Blek! Qualquer um que tenha mergulhado no entendimento de como o C ++ 11 lida com o multithreading pode entender por que o texto deve ser tão opaco, mas isso não perdoa o fato de que é ... bem ... tão opaco!
Compare isso com a definição de std::shared_ptr<T>::reset
, na seção de biblioteca do padrão:
template <class Y> void reset(Y* p);
Efeitos: Equivalente a
shared_ptr(p).swap(*this)
Então qual a diferença? Na parte de definição da linguagem, os escritores não podem assumir que o leitor entende as primitivas da linguagem. Tudo deve ser especificado com cuidado na prosa inglesa. Quando chegamos à parte de definição da biblioteca, podemos usar a linguagem para especificar o comportamento. Isso geralmente é muito mais fácil!
Em princípio, pode-se ter uma construção suave a partir de primitivas no início do documento de especificação, definindo o que consideraríamos "recursos de biblioteca padrão", sem precisar traçar uma linha entre "primitivas de linguagem" e recursos de "biblioteca padrão". Na prática, essa linha é extremamente valiosa para desenhar, pois permite que você escreva algumas das partes mais complexas da linguagem (como aquelas que precisam implementar algoritmos) usando uma linguagem projetada para expressá-las.
E de fato vemos algumas linhas borradas:
java.lang.ref.Reference<T>
pode ser subclassificado pelas classes de biblioteca padrão e porque os comportamentos de são tão profundamente entrelaçados com a especificação da linguagem Java que eles precisavam colocar algumas restrições na parte desse processo implementada como classes de "biblioteca padrão".java.lang.ref.WeakReference<T>
java.lang.ref.SoftReference<T>
java.lang.ref.PhantomReference<T>
Reference
Isto é uma adição às respostas existentes (e é muito longo para um comentário).
Há pelo menos dois outros motivos para uma biblioteca padrão:
Se um recurso de idioma específico estiver em uma função de biblioteca e eu quiser saber como ele funciona, eu posso ler a fonte dessa função. Se eu quiser enviar uma solicitação de relatório de bug / patch / pull, geralmente não é muito difícil codificar uma correção e casos de teste. Se estiver no compilador, tenho que ser capaz de cavar os internos. Mesmo que esteja no mesmo idioma (e deveria ser, qualquer compilador que se preze deve ser auto-hospedado), o código do compilador não é nada parecido com o código do aplicativo. Pode levar uma eternidade para encontrar os arquivos corretos.
Você está se afastando de muitos colaboradores em potencial se seguir esse caminho.
Muitos idiomas oferecem esse recurso em um grau ou outro, mas seria muito complicado recarregar o código que está sendo recarregado a quente. Se o SL for separado do tempo de execução, ele poderá ser recarregado.
Esta é uma pergunta interessante, mas já existem muitas boas respostas, portanto não tentarei uma completa.
No entanto, duas coisas que eu acho que não receberam atenção suficiente:
Primeiro é que a coisa toda não é super clara. É um pouco de espectro exatamente porque existem razões para fazer as coisas de maneira diferente. Como exemplo, os compiladores geralmente conhecem as bibliotecas padrão e suas funções. Exemplo do exemplo: A função "Hello World" de C - printf - é a melhor que consigo pensar. É uma função de biblioteca, tem que ser, pois depende muito da plataforma. Mas seu comportamento (definido pela implementação) precisa ser conhecido pelo compilador para alertar o programador sobre más invocações. Isso não é particularmente interessante, mas foi visto como um bom compromisso. Aliás, essa é a resposta real para a maioria das perguntas "por que esse design": muito comprometimento e "parecia uma boa idéia na época". Nem sempre o "este era o caminho claro para fazê-lo" ou "
A segunda é que ela permite que a biblioteca padrão não seja todo esse padrão. Existem muitas situações em que uma linguagem é desejável, mas as bibliotecas padrão que geralmente as acompanham não são práticas e desejáveis. Esse é geralmente o caso de linguagens de programação de sistemas como C, em plataformas não padrão. Por exemplo, se você possui um sistema sem um sistema operacional ou um agendador: não terá o encadeamento.
Com um modelo de biblioteca padrão (e com suporte para threading), isso pode ser tratado de maneira limpa: o compilador é praticamente o mesmo, você pode reutilizar os bits das bibliotecas que se aplicam e qualquer coisa que não seja possível remover. Se isso for inserido no compilador, as coisas começam a ficar confusas.
Por exemplo:
Você não pode ser um compilador compatível.
Como você indicaria seu desvio do padrão. Observe que geralmente existe alguma forma de sintaxe de importação / inclusão, que pode causar falha, por exemplo, a importação de pythons ou a inclusão de C que facilmente aponta para o problema se houver algo faltando no modelo de biblioteca padrão.
Problemas semelhantes também se aplicam se você deseja ajustar ou ampliar a funcionalidade da 'biblioteca'. Isso é muito mais comum do que você imagina. Apenas para ficar com o threading: janelas, linux e algumas unidades de processamento de rede exóticas, todos funcionam de maneira bem diferente. Embora os bits do linux / windows possam ser razoavelmente estáticos e possam usar uma API idêntica, o material da NPU mudará com o dia da semana e a API com ele. Os compiladores se desviavam rapidamente à medida que as pessoas decidiam quais bits eles precisavam suportar / poderiam fazer com rapidez se não houvesse maneira de dividir esse tipo de coisa.