Oh cara, estou animado para tentar responder a essa pergunta da melhor maneira possível. Espero conseguir colocar meus pensamentos em ordem.
Como o @Doval mencionou e o questionador apontou (ainda que de forma grosseira), você realmente não possui um sistema de tipos. Você tem um sistema de verificações dinâmicas usando tags, que geralmente são muito mais fracas e também muito menos interessantes.
A questão do "o que é um sistema de tipos" pode ser bastante filosófica, e poderíamos encher um livro com diferentes pontos de vista sobre o assunto. No entanto, como este é um site para programadores, tentarei manter minha resposta o mais prática possível (e realmente, os tipos são extremamente práticos em programação, apesar do que alguns possam pensar).
visão global
Vamos começar com um entendimento básico do que é um sistema de tipos, antes de mergulhar nos fundamentos mais formais. Um sistema de tipos impõe estrutura aos nossos programas . Eles nos dizem como podemos conectar várias funções e expressões. Sem estrutura, os programas são insustentáveis e extremamente complexos, prontos para causar danos ao menor erro do programador.
Escrever programas com um sistema de tipos é como conduzir um cuidado em boas condições - os freios funcionam, as portas fecham com segurança, o motor é lubrificado etc. Programas de escrita sem um sistema de tipos são como andar de moto sem capacete e com rodas feitas sem espaguete. Você não tem absolutamente nenhum controle sobre o seu.
Para fundamentar a discussão, digamos que temos uma linguagem com expressão literal num[n]
e str[s]
que representa o número n e a sequência s, respectivamente, e funções primitivas plus
e concat
, com o significado pretendido. Claramente, você não deseja escrever algo como plus "hello" "world"
ou concat 2 4
. Mas como podemos evitar isso? A priori , não há método para distinguir o numeral 2 da string literal "world". O que gostaríamos de dizer é que essas expressões devem ser usadas em diferentes contextos; eles têm tipos diferentes.
Idiomas e tipos
Vamos recuar um pouco: o que é uma linguagem de programação? Em geral, podemos dividir uma linguagem de programação em duas camadas: a sintaxe e a semântica. Estes também são chamados de estática e dinâmica , respectivamente. Acontece que o sistema de tipos é necessário para mediar a interação entre essas duas partes.
Sintaxe
Um programa é uma árvore. Não se deixe enganar pelas linhas de texto que você escreve em um computador; estas são apenas as representações legíveis por humanos de um programa. O programa em si é uma árvore de sintaxe abstrata . Por exemplo, em C, podemos escrever:
int square(int x) {
return x * x;
}
Essa é a sintaxe concreta do programa (fragmento). A representação em árvore é:
function square
/ | \
int int x return
|
times
/ \
x x
Uma linguagem de programação fornece uma gramática que define as árvores válidas dessa linguagem (sintaxe concreta ou abstrata pode ser usada). Isso geralmente é feito usando algo como notação BNF. Eu diria que você fez isso para o idioma que criou.
Semântica
OK, sabemos o que é um programa, mas é apenas uma estrutura de árvore estática. Presumivelmente, queremos que nosso programa realmente calcule algo. Precisamos de semântica.
A semântica das linguagens de programação é um rico campo de estudo. De um modo geral, existem duas abordagens: semântica denotacional e semântica operacional . A semântica denotacional descreve um programa mapeando-o em alguma estrutura matemática subjacente (por exemplo, números naturais, funções contínuas etc.). que fornece significado ao nosso programa. A semântica operacional, pelo contrário, define um programa detalhando como ele é executado. Na minha opinião, a semântica operacional é mais intuitiva para os programadores (inclusive eu), então vamos manter isso.
Não vou explicar como definir uma semântica operacional formal (os detalhes estão um pouco envolvidos), mas basicamente queremos regras como as seguintes:
num[n]
é um valor
str[s]
é um valor
- Se
num[n1]
e num[n2]
avaliar para os números inteiros n_1$ and $n_2$, then
mais (num [n1], num [n2]) `é avaliado para o número inteiro $ n_1 + n_2 $.
- Se
str[s1]
e str[s2]
avaliar para as cadeias s1 e s2, será concat(str[s1], str[s2])
avaliada para a cadeia s1s2.
Etc. As regras são na prática muito mais formais, mas você entende. No entanto, logo encontramos um problema. O que acontece quando escrevemos o seguinte:
concat(num[5], str[hello])
Hum. Este é um enigma. Não definimos uma regra em nenhum lugar para saber como concatenar um número com uma string. Poderíamos tentar criar essa regra, mas sabemos intuitivamente que essa operação não faz sentido. Não queremos que este programa seja válido. E assim somos levados inexoravelmente a tipos.
Tipos
Um programa é uma árvore, conforme definido pela gramática de um idioma. Os programas recebem significado pelas regras de execução. Mas alguns programas não podem ser executados; isto é, alguns programas não têm sentido . Esses programas estão incorretos. Assim, a digitação caracteriza programas significativos em um idioma. Se um programa for bem digitado, podemos executá-lo.
Vamos dar alguns exemplos. Novamente, como nas regras de avaliação, apresentarei as regras de digitação informalmente, mas elas podem ser rigorosas. Aqui estão algumas regras:
- Um token do formulário
num[n]
tem tipo nat
.
- Um token do formulário
str[s]
tem tipo str
.
- Se expressão
e1
tiver tipo nat
e expressão e2
tiver tipo nat
, a expressão plus(e1, e2)
terá tipo nat
.
- Se expressão
e1
tem tipo str
e expressão e2
tem tipo str
, expressão concat(e1, e2)
tem tipo str
.
Assim, de acordo com essas regras, existe o plus(num[5], num[2])
tipo has nat
, mas não podemos atribuir um tipo ao plus(num[5], str["hello"])
. Dizemos que um programa (ou expressão) é bem digitado se pudermos atribuir qualquer tipo e, caso contrário, é digitado incorretamente. Um sistema de tipos é válido se todos os programas bem digitados puderem ser executados. Haskell é som; C não é.
Conclusão
Existem outras visualizações sobre tipos. Os tipos, em certo sentido, correspondem à lógica intuicionista e também podem ser vistos como objetos na teoria das categorias. Entender essas conexões é fascinante, mas não é essencial se alguém apenas quer escrever ou mesmo criar uma linguagem de programação. No entanto, entender os tipos como uma ferramenta para controlar formações de programas é essencial para o design e desenvolvimento da linguagem de programação. Eu apenas arranhei a superfície do que os tipos podem expressar. Espero que você pense que vale a pena incorporar ao seu idioma.