/ 22.Feb.2008 at 07:38pm
Estou na minha temporada de traduções :-) Felizmente a blogosfera produz muitos artigos interessantes. Desta vez foi Steven Devijver que fez um artigo explicando sobre as diferenças entre os termos tipagem dinâmica e linguagem dinâmica.
Vamos à tradução:
Eu acabei de ler um post muito interessante sobre as virtudes de tipagem estática comparado com tipagem dinâmica (nota do Akita: o artigo desse link foi especialmente tecido com a intenção de pegar os despreparados). Levou um tempinho para eu entender os exemplos de código de Ruby, Python, OCaml e Haskell. Mesmo assim consegui concluir algumas coisas desse artigo:
Entretanto a diferença é clara o suficiente. VB Script (Visual Basic Script) tem tipagem dinâmica e mesmo assim não é uma linguagem dinâmica. O código abaixo é VB Script válido (roda no Windows com cscript.exe):
1 2 3 4 5 6 7 8 9 |
dim x dim y x = 1 y = 2 x = "ABC" y = "XYZ" |
Ruby e Python usam tipagem dinâmica e eles também são linguagens dinâmicas. Aqui vai um trecho de código demonstrando o mecanismo de despacho dinâmico do Ruby:
1 2 3 4 5 6 7 8 |
class Dummy def method_missing(m, *args) args[0] + args[1] end end raise "Error" unless Dummy.new.test(1, 2) == 3 |
O método test() chamado na classe Dummy na última linha é despachado pelo Ruby ao método method_missing(). (nota do Akita: note que dizemos “despachado” – dispatched – e não “chamado”. Não ‘chamamos métodos’, mas sim ‘enviamos mensagens’, a diferença é muito grande.) Python e Groovy também suportam Despacho Dinâmico. Em geral, linguagens dinâmicas como Ruby, Python e Groovy tem um Protocolo de Meta-Objeto ou MOP (Meta-Object Protocol).
De volta ao post que mencionei no começo. O autor tenta provar que tipagem estática é superior à tipagem dinâmica. Para provar isso ele usa este código em Ruby (também há exemplos em Python, OCaml e Haskell):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def test(a, b) a + b end def main() if ARGV.length > 3 test(1, test) else test(1, 2) end end Process.exit(main()) |
Esse código funciona bem quando passamos 0, 1, 2 ou 3 argumentos na linha de comando:
1 2 3 4 5 6 7 8 9 |
$ ruby -w -W2 t.rb; echo $? 3 $ ruby -w -W2 t.rb 0; echo $? 3 $ ruby -w -W2 t.rb 0 1; echo $? 3 $ ruby -w -W2 t.rb 0 1 2; echo $? 3 |
Entretanto, quando passamos 4 argumentos na linha de comando o script Ruby falha:
1 2 3 4 5 6 7 |
$ ruby -w -W2 t.rb 0 1 2 3; echo $?
t.rb:7:in `test': wrong number of arguments (0 for 2) (ArgumentError)
from t.rb:7:in `main'
from t.rb:13
1
$
|
Baseado nesse script Ruby, o (famigerado) autor chega à seguinte conclusão:
Como é esperado de uma linguagem com tipagem dinâmica como Ruby, o erro não foi detectado até o momento de execução (runtime) [...] Mesmo se testes unitários tivessem sido utilizados, é bem possível que esse cenário não seria coberto, e um usuário perplexo encararia um erro como o mostrado acima.
Bem, não podemos argumentar contra isso. Ele vai adiante com as versões de OCaml e Haskell do mesmo script. Na conclusão o autor diz:
Como vimos claramente acima, linguagens com tipagem dinâmica como Ruby e Python podem permitir que código estragado seja escrito facilmente. Mas mais perigoso, é possível que esse código rode bem, até que uma certa condição aconteça e aí vai acontecer um erro de runtime. [...] Ainda bem que linguagens de tipagem estática fornecem uma maneira muito natural de evitar muitos problemas de runtime, em vez disso pegando-os no momento de compilação.
E eis quando a confusão se firmou, misturando tipagem dinâmica com linguagem dinâmica. O autor nunca menciona “linguagem dinâmica” em seu post (somente “linguagens de tipagem dinâmica”) e ainda assim ele clama que linguagens de tipagem estática detectam confusão de tipos no momento de compilação, logo são mais seguras para os desenvolvedores usar.
Aqui vai um script tipado estaticamente em Groovy que vai falhar em tempo de compilação:
1 2 |
int x = "test" |
Este é o erro em runtime:
1 2 3 4 |
Caught: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'test' with class 'java.lang.String' to class 'java.lang.Integer' at typesafe.run(typesafe.groovy:1) at typesafe.main(typesafe.groovy) |
Hmm, Groovy sem sombra de dúvida tem tipagem estática (note, entretanto, que não há erro em tempo de compilação). Ainda assim ele também é uma linguagem dinâmica. Aqui vai o script Ruby anterior re-escrito em Groovy:
1 2 3 4 5 6 7 8 9 10 |
def test(int a, int b) {
a + b
}
if (args.length > 3) {
println test(1, "test")
} else {
println test(1, 2)
}
|
Veja a declaração do método test() nas primeiras 3 linhas e seus argumentos tipados estaticamente. Esse script vai compilar? Sim. Esse script vai falhar quando 3 ou menos argumentos forem passados na linha de comando? Não. Aqui vão as saídas para 0 até 4 argumentos na linha de comando:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
C:\>groovy type_safe
3
C:\>groovy type_safe 0
3
C:\>groovy type_safe 0 1
3
C:\>groovy type_safe 0 1 2
3
C:\>groovy type_safe 0 1 2 3
Caught: groovy.lang.MissingMethodException: No signature of method: type_safe.test() is applicable for argument types: (java.lang.Integer, java.lang.String) values: {1, "test"}
at type_safe.run(type_safe.groovy:6)
at type_safe.main(type_safe.groovy)
C:\>
|
Por que esse script compila quando o método test() tem argumentos tipados? A chamada a test() na última linha é claramente incorreta!
Groovy é uma linguagem dinâmica com um Protocolo de Meta-Objeto. O compilador Groovy não tem nenhuma maneira de saber como a chamada da última linha acima será despachada. Talvez seja despachada para o método test() declarado nas primeiras 3 linhas. Mas configuração dinâmica em tempo de execução também pode significar que a chamada seja despachada para outro lugar.
Groovy suporta tipagem estática, Ruby e Python não. Como são linguagens dinâmicas seus compiladores ou interpretadores não podem saber como as chamadas de métodos serão despachados (essa informação está disponível apenas em tempo de execução). Suas implementações do Protocolo de Meta-Objeto são o poder dessas linguagens dinâmicas.
Para os designers de Ruby e Python suportar tipagem estática provavelmente não fazia muito sentido já que não pode ser usado para reforçar o tempo de compilação de qualquer forma. Groovy suporta tipagem estática para suportar, por exemplo, overloading de métodos e construtor.
Fazer pouco de Ruby, Python e Groovy porque eles não checam tipos em momento de compilação não leva em conta o poder das linguagens dinâmicas. Eu entendo que linguagens não-dinâmicas são desejáveis por muitas razões, mas assim também é com linguagens dinâmicas.
Da próxima vez que ouvir pessoas reclamando sobre linguagens dinâmicas por sua falta de tipagem estática você estará melhor informado para entender porque eles reclamam. Talvez eles não gostem de linguagens dinâmicas, ou gostem muito de tipagem estática. Mas pelo menos você saberá que existem diferenças entre tipagem dinâmica e linguagens dinâmicas. E também entenderá que eles preferem linguagens não-dinâmicas de tipagem estática.
Feliz codificação!
9 Comments
Como sempre: encaram tudo preto no branco.
felipe / 23.Feb.2008 at 08:12am
Bela tradução Akita, isto vai ser muito útil pra mim. Valeu.
ozéias sant'ana / 23.Feb.2008 at 09:10am
O site original complica tanto pra comentários que vou me limitar a comentar aqui…
A tipagem de Groovy não é estática; o que foi demonstrado é um exemplo de tipagem manifesta (contrário de tipagem latente). Compilar código explicitamente inválido também retira o direito a se dizer “undeniably type safe” de Groovy. Sinceramente, acho até engraçado o autor pensar que tipagem estática é sinônimo de manifesta, quando as duas linguagens demonstradas no primeiro artigo (Haskell e OCaml) são estáticas e ainda assim latentes.
Vale lembrar também que o sistema de tipos da maioria das linguages “comerciais” é uma brincadeira perto do de algo como Haskell, então querer usar exemplos de Haskell (tem uns realmente incríveis) pra provar que uma solução em C# é automaticamente melhor que em qualquer linguagem dinâmica é uma falácia ;) Seu sistema em Java realmente não vai dar NoMethodError por chamar um método inexistente… mas ainda pode ter UnsupportedOperationExceptions, NullPointerExceptions, ArrayIndexOutOfBoundsExceptions, ClassCastExceptions e tantos outros erros da categoria RuntimeException, que nem mesmo as “checked exceptions” que alguns defendem tão fervorosamente salvam. No fim, o tipo de problema que a tipagem dessas linguagens detecta durante a compilação é frequentemente bem superficial perto dos problemas reais com os quais um programador decente se depara, independente da linguagem.
Uma boa explicação sobre características de sistemas de tipos pode ser vista aqui.
daniel luz / 23.Feb.2008 at 07:46pm
Posso estar falando bobagem, mas na minha opnião o exemplo de Ruby não demostra nada em relação a tipagem dinâmica e sim de linguagem dinâmica.
Para mim, um problema de tipagem dinâmica seria chamar um método inexistente.
E paragens pela tradução.
daniel libanori / 24.Feb.2008 at 02:57am
@daniel, é o contrário, um exemplo de ‘linguagem dinâmica’ seria dynamic method dispatching, por exemplo, chamar um método inexistente e caindo em method_missing. A confusão é justamente essa: ‘tipagem dinâmica’ significa que:
x tem tipagem dinâmica, sendo que em java:
A segunda daria problema em tipagem estática porque, obviamente, o tipo é estático: um int não pode receber um String.
akitaonrails / 24.Feb.2008 at 03:29pm
Akita, o que eu queria dizer é: detectar uma chamada a um método sem a quantidade correta de parâmetros não me parece um problema de tipagem dinâmica.
E pelo que eu vi, é exatamente isso que o exemplo Ruby do cara está fazendo.
Me corrija se eu estiver falando bobagem.
Abraços
daniel libanori / 24.Feb.2008 at 05:30pm
Então, o que o autor do rant (não deste artigo, mas que o autor deste artigo cita no começo) quer dizer: se eu tenho um método com a assinatura metodo(int,int), e eu tentasse chamar como metodo(1,2,3) o compilador já nos avisaria do erro porque a assinatura do método não permite 3 parametros e sim, apenas 2.
Agora, o problema é que isso não é um “defeito” de tipagem de dinâmica. Isso é uma funcionalidade (muito desejável) de linguagens dinâmicas porque eu não estou “chamando” um método, estou despachando um método (que por acaso de parametros) a um objeto. O objeto vai decidir o que fazer com essa mensagens (o default sendo procurar um método com o mesmo nome). Mas uma linguagem dinâmica permite coisas como method_missing, permite injetar um novo método com assinaturas diferentes em runtime, etc. Por isso é impossível a um compilador decidir de ante-mão que a chamada em questão é um erro e é isso que o autor do rant está confundindo e não entendendo e que o autor deste artigo explica.
akitaonrails / 24.Feb.2008 at 06:33pm
A tipagem estática do Object Pascal é que me faz estar segurando o osso até os dias atuais…
eraldo / 24.Feb.2008 at 10:33pm
Ola Akita, acompanho seu blog a muito tempo porem nunca postei nada. Vamos la! Consigo compreender que tipagem dinâmica é uma funcionalidade, brilhante na minha opinião, porem não consigo convencer, principalmente programadores java, que isso não é um defeito e mais ainda, nao consigo achar argumentos para convencer que isso traz benefícios enormes e mais que isso não sei como agabar com o único argumento sempre dito por eles de que tipagem dinâmica em grandes projetos leva a erros e etc que você já deve estar acostumado a escutar.
Post como esse defendendo tipagem estática é o que mais se vê aonde trabalho.
noel rocha / 25.Feb.2008 at 02:25pm
Leave a Comment