Kumpera.net delírios sobre linguagens de programação
Meus achados sobre tecnologia
O trânsito paulistano admite uma solução tecnológica?
Essa foi uma pergunta que eu me fiz outro dia. Ela surgiu durante uma discussão sobre possíveis soluções para o inferno diário de milhões de pessoas. O argumento da maioria dos presentes era que a única real solução era construir metro. Vim a acreditar que essa é, de longe, aquilo que nossa cidade precisa.
Basta um pouco de matemática para provar que não é cavando túneis que vamos reduzir o caos do transporte. Vamos pegar o caso de uma cidade com um transporte considerado referencia global. Londres possui 408km de metro para 7,5 milhões de habitantes, ou um quilômetro para cada 18,3mil pessoas. Enquanto isso na região metropolitana de São Paulo sobrevivem 19,2 milhões de pessoa com 68km de metro e 257km de trem, ou seja, 282,3mil se debatendo por quilômetro de metro ou 59mil se espremendo no transporte metroviário da cidade.
Fazendo uma simples regra de três, a cidade precisa de mais 720km de trilhos para ter o mesmo nível de conforto de Londres. Pronto, resolvido, só construir agora. Porém as pessoas esquecem que dois detalhes, custo e prazos. Primeiro aos custos, a linha 4 foi orçada originalmente em 1,25 bilhões de dólares e tem uma extensão de quase 13km, o que significa 155 milhões de reais por quilômetro. Por esse parâmetro, São Paulo precisa de aproximadamente 112 bilhões em investimentos, ou sete vezes mais que os 15,8 destinados a todos programas do PAC para o ano de 2008. Mas vamos esquecer isso, pois dinheiro não é o problema. Sério.
O ponto importante é o tempo de construção. No atual ritmo de quatro quilômetros ao ano as obras ficariam prontas somente em 2188. Mas as coisas vão melhorar e vamos andar na mesma velocidade frenética da construção do metrô chines em Pequim, que é cinco vezes mais rápida. Ainda assim, as obras seriam entregues em 2044, daqui 36 anos.
Ouviram bem, serão trinta e seis anos de espera se, por milagre, passarmos a construir na velocidade maluca dos chineses. Vejamos, daqui todo esse tempo eu vou ter 62 anos e a todos dos leitores desse blog terão bem mais de cinqüenta. Ou seja, vamos estar na terceira idade, próximo de nos aposentarmos. Porém se o ritmo das obras se mantiver, talvez nossos bisnetos assistam a conclusão perto do final de suas vidas.
Eu não quero esperar até minha aposentadoria para ter um transporte urbano que preste na minha cidade. Não podemos esperar mais de três décadas, precisamos disso o mais rápido possível. Precisamos para ontem, não podemos que cada um jogo fora centenas de horas parado em engarrafamentos todo ano.
Dito isso, o quanto a tecnologia mudou nossas vidas nos últimos dez anos? Em especial, vamos refletir na questão organizacional das nossas vidas. Hoje é muito fácil via celular e internet organizar reuniões e grupos - basta um tweet “vamos beber” que em uma horas estão todos no bar certo. Hoje é muito fácil centralizar, catalogar, manipular e consultar informação pertinente a milhões de pessoas em tempo real - basta pensar em coisas como redes sociais, rss e sites de busca. Por fim, hoje a informação possui uma tremenda mobilidade, um celular moderno tem localização via GPS e até 1mb de largura de banda com a internet.
Agora vamos pensar em como opera o transporte público hoje. Temos linhas de ônibus fixas e definidas por engenheiros de tráfego baseados em pesquisar de origem e destino. Nós temos rotas estáticas de transporte, você conseguiria imaginar a internet sem roteamento dinâmico de pacotes? Nós temos rotas otimizadas segundo critérios antigos - o que seria do Google se a busca fosse baseada no conteúdo de um anos atrás apenas? Nós temos itinerários e linhas que não são adaptadas as necessidades dos usuários no momento que usam - imagine usar um navegador com 30 minutos de atraso.
Nosso sistema de transporte não deve ser modernizado, precisa ser recriado, repensado e quebrar todas barreiras convencionais. Porque você precisa pegar um ônibus que passa em lugares e horários pré-definidos? Imagine se bastasse do seu celular informar seu destino que um sistema ajustaria a rota de um veículo para melhor servir você e otimizar o uso das nossas vias? Algo quase como um taxi coletivo a custos de um convencional. Hoje temos todas as peças do quebra cabeça tecnológico para viabilizar isso.
Basta começar a pensar no assunto para centenas de idéias surgirem, a maioria delas muito melhor que as minhas. Basta pensar em um modelo de transporte que seja tão ágil quanto nossa vida moderna é, ou deveria ser.
Minha contribuição, entretanto, não é a sugestão tecnológica. O transporte de São Paulo é um mercado de 8 bilhões de reais ao ano, pelo menos. Muita grana não? Porém existe uma reserva de mercado para novas empresas entrarem e na forma de atuação delas, além de um cartel de empresas controlando o serviço hoje. Todos sabemos dos prejuízos que tais reservas causam ao interesse público, por isso a única solução é abrir o setor ao mercado como um todo e permitir que as empresas decidam como operar modal que irão oferecer. O resto deixem por conta da iniciativa privada, que é mister sua capacidade de inovação em face a livre concorrência.
Todo paulistano tem a obrigação de aproveitar que estamos em ano eleitoral, discutir o assunto, questionar seu candidato e tomar uma decisão informada em novembro. Não vamos mais uma vez deixar políticos nos enganarem com suas promessas vazias. Vamos dessa vez mostrar que não temos sangue de barata e mostrar exatamente aquilo que queremos. Eu estou aqui fazendo a minha, agora você Paulistano vá fazer a sua!
O futuro da programação - hoje
Sempre que leio qualquer coisa feita pelo Iam Piumarta fico impressionado. Outro dia ao ler os slides sobre o progresso do grupo dele para construir uma linguagem que seja uma real evolução ao que temos hoje, me dei conta de como aquilo que eles propõem é radicalmente diferente ao que a prática de que estamos habituados.
A atenção excessiva na meta-recursividade do sistema, assim como ser trivial criar e integrar novas gramáticas a linguagem, me faz pensar qual seria o limite de duas propriedade com emergence como essas. O efeito imediato é no compilador, que tem suporte direto a PEGs e pattern matching.
O compilador é capaz de se adaptar a mudanças nas regras de sintaxe e passar a usá-las instantaneamente apos serem definidas, ou seja, você pode definir uma mini linguagem em 5-10 linhas e usá-la logo em seguida. Não só isso, mas elas possuem escopo e podem operar em apenas um bloco de texto do seu fonte. Junta isso ao fato de que PEGs poderem ser compostas facilmente e o compilador suportar staged compilation que o resultado é ter uma linguagem que é maleável ao extremo.
A outra é a máquina virtual, que é construída com essa nova linguagem, de forma que ela mesmo siga o modelo de protótipos e delegação na sua implementação. Isso permite que ela seja tão flexível e dinâmica quanto suas aplicações. Essa é, de longe, a característica mais interessante, pois nenhuma linguagem baseada e máquina virtual que eu conheço chega a esse ponto.
Mas quais seriam aplicações reais disso? Aquilo que hoje fazemos com frameworks e bibliotecas, poderíamos fazer com DSLs. Então em vez de aprender um enorme framework que precisa seguir às regras da linguagem, bastaria aprender uma pequena nova linguagem. Da mesma forma, muita coisa que hoje se traduz em referencia direta ao código de bibliotecas, poderia ser feito como uma série de modificações à gramática corrente.
Porém eu acho que é possível ir muito mais além, com uma VM flexível, seria possível ajustar o modelo de execução para melhor realizar a tarefa que o programa deseja. Como, por exemplo, introduzir processos leves ao estilo do Erlang simplesmente incluindo uma biblioteca.
Quem ainda não se ligou o que gente como Ian Piumarta e Alan Kay tem feito no Viewpoints Research Institute deve parar um pouco e se dedicar àquilo que um dia poderia ser o próximo passo na evolução da computação.
Como fazer uma linguagem dinâmica ser rápida?
Muito se fala em como as implementações de Ruby estão ficando rápidas, que estão evoluindo rapidamente. Porém não consigo pensar em como todas elas parecem mas preocupadas em repetir o caminho das pedras que outras linguagens dinâmicas passaram em décadas passadas.
Hoje a maioria ainda está no estágio de possuir um interpretador razoável e estar começando a investir em algum mecanismo primitivo de compilação para código nativo. Talvez a única diferença seja o uso de inline caches para resolução de nomes, que foi uma contribuição significativa do pessoal do Self no começo dos anos 90. Porém os principais resultados obtivos por pesquisas a um bom tempo, como tracing, especialização e especulação não deram as caras ainda.
Inline caching é uma optimização no qual o resultado da resolução de nomes, para dispatch de método por exemplo, é armazenado entre ativações distintas. O truque é introduzir uma clausula de guarda que verifica o tipo do objeto seletor e sua versão. Caches devem ser invalidados sempre que alguém alterar uma classe. Temos dois tipos de cache, os monomórficos que fazem caching de apenas uma invocação e os polimórficos, que armazenam uma série de resultados. Para melhor vamos exemplificar com pseudo código:
//ruby def test (a) a.foo end //pseudo-código gerado em C# para um cache monomórfico class CallSite { Type type; MethodInfo method; int version; } static CallSite test_site_0; object test(object a) { //guarda do cache monomórfico verifica tipo e versão if (test_site_0.type == a.Type && test_site_0.version == A.Type.version) return method (a); else //ResolveAndInvoke resolve o método "foo", invoca ele e atualiza o cache return ResolveAndInvoke (a, "foo", ref test_site_0); } //pseudo-código gerado em C# para um cache polimórfico delegate object InlineCacheNoArg (object this_); static InlineCacheNoArg test_site_0; object test(object a) { return test_site_0 (a); } //pseudo-código inicial do delegate de test_site_0 object test_site_0_v0 (object _this) { return ResolveAndInvoke (a, "foo", ref test_site_0); } //pseudo-código do delegate depois de chamarmos test (99) object test_site_0_v1 (object _this) { if (_this.Type == typeof (int) && _this.version == 1) //o dispatch aqui pode ser via um delegate dependendo da resolução return ((int)_this).foo (); return ResolveAndInvoke (a, "foo", ref test_site_0); } //pseudo-código do delegate depois de chamarmos test ("str") object test_site_0_v2 (object _this) { if (_this.Type == typeof (int) && _this.version == 1) return ((int)_this).foo (); if (_this.Type == typeof (string) && _this.version == 1) return ((string)_this).foo (); return ResolveAndInvoke (a, "foo", ref test_site_0); }
Como fica claro pelo exemplo, um cache polimórfico tem uma performance superior pois usa literais e funciona muito melhor no caso de não existir um tipo dominante entre as ativações. Aqui fica clara a limitação de uma das implementações, o JRuby não pode se dar ao luxo de gerar tantos métodos pois cada um precisa de uma classe e um classloader novos, ou seja, abusa da PermGen do Java.
Apesar de inline caches resultarem em ganho expressivo de performance, estão longe de produzirem algo razoável. O grande problema continua sendo o enorme custo de dispatch para código simples. A chave disso é fazer inferência dos tipos, de forma a conseguir eliminar por completo o overhead dos caches e resolução de nomes. As atuais implementações não implementam se quer inferência estática, ou seja, código como “123.to_s” é executado sem qualquer conhecimento prévio de 123.
O grande avanço ocorre se fizemos inferência em tempo de execução. Ou seja, instrumentamos o código para coletar os tipos que aparecem pelo código durante a execução e baseado nisso a máquina virtual gera versões mais eficientes do código. Existem duas técnicas bastante difundidas de como fazer isso, uma é via especialização parcial de métodos e a outra é via trace-based optimization.
Trace-based optimization é a tecnologia adotada pela Tamarin, a próxima VM de Javascript da Mozilla. De maneira sucinta, essa técnica consiste em gravar os trechos, e os tipos encontrados, que executam mais freqüentemente e gerar código eficiente baseado nessa informação. Os trechos gravados costumam incluir vários métodos diferentes e suas ativações juntas. Suas principais vantagens é a conseguir fazer inlining de métodos de maneira muito eficiente e normalmente gastar menos tempo no JIT. Porém existem uma enorme quantidade de problemas complexos de serem resolvidos como limitar expansão descontrolada da quantidade de código gerado.
Especialização parcial de métodos também leva em conta os métodos que executam mais freqüentemente. Os tipos dos parâmetros e valores de retorno
são armazenados e posteriormente utilizados para gerar versões especializadas dos métodos em questão. Sua principal vantagem é a maior simplicidade
e o fato de existir muito mais literatura e ferramentas para gerar código eficiente nestes casos. Para se ter uma idéia do poder dessa técnica um exemplo cai bem:
//ruby def fun (a, b) 2 * a - b end //fun é sempre chamada com números como argumento, "fun (1,2)" por exemplo. //pseudo-código C# gerado inicialmente (sem usar caches) object fun_v0 (object a, object b) { object tmp = Invoke (2, "*", a"); //_this, nome do método, argumentos return Invoke (tmp, "-", b); } //pseudo-ćodigo C# gerado após especialização: int fun_int_int (int a, int b) { int tmp = 2 * a; //o método que implementava multiplicação foi inlined return tmp - b; } object fun_v1 (object a, object b) { if (a.Type == typeof (int) && b.Type == typeof (int) && typeof (int).version == 1) return fun_int_int ((int)a, (int)b); object tmp = Invoke (2, "*", a"); //_this, nome do método, argumentos return Invoke (tmp, "-", b); }
Não precisa ir muito longe para imaginar a diferença de performance entre as duas versões. Porém entre descobrir os métodos as serem especializados e gerar código de máquina existe um Just-In-Time compiler que é, surpresa, surpresa, muito trabalhoso de ser escrito para executar rapidamente e gerar código eficiente.
Possui um bom JIT é atualmente um grande dilema entre as implementações de ruby. Pois ao usar o JIT de máquinas virtuais maduras, como HotSpot ou mono, a implementação fica limitada ao que é possível a linguagens gerenciadas. Em contrapartida, ao não utilizá-las, construções de baixo nível como profiler e interpretador podem ser implementadas de forma muito mais eficiente.
Apesar de do futuro parecer muito legal, futuras VMs terão o trabalho adicional de educar a comunidade de desenvolvedores sobre como escrever código rápido em ruby. Coisas como monkey patching furam qualquer esquema de caching ou especialização pois cada objeto passa a ter uma singleton class distinta.
Não existe solução fácil e todas implementações tem muito chão pela frente até Ruby ter uma performance competitiva com outras linguagens dinâmicas. Soluções como as aqui apresentadas devem conseguir melhorias de uma ordem de magnitude tranquilamente, de acordo com os resultados já encontrados. Performance é um problema que é resolvido via muito suor, com uma contínua série de melhoras, atacando um pouco por vez.
Vamos todos impedir a regulamentação da profissão de analista de sistema
Dia 5 de março de 2008 foi aprovada na Comissão de Ciência, Tecnologia, Inovação, Comunicação e Informática (CCT) o projeto de lei 607/2007 que regulamenta a profissão de analista de sistemas. Muita gente sabe disso e a grande maioria é expressamente contra esse projeto que vai causar muitos problemas sem melhorar ou resolver qualquer um existente.
Muita gente expressou sua indignação publicamente, mas ao que tudo indica, não causou qualquer reação junto aos nobres senadores. Por isso acho que chegou a hora de todos nós pararmos de reclamar e iniciar um protesto que fará o legislativo sentir nossa opinião de maneira muito mais contundente.
Temos que agir rapidamente pois o assunto está aguardando ser designado um relator da Comissão de Assuntos Sociais (CAS) para dar procedimento a este absurdo. Precisamos deixar bem claro a todos membros da CAS que não queremos essa lei de maneira alguma.
Uma vez que esta lei irá sucatear o mercado de TI do Brasil, vamos nos adiantar e mandar toda nossa sucata tecnológica para os senadores que compõem a CAS. Vamos mandar todas nossas peças velhas de computador para eles. Não adianta enviarmos milhares de emails diários pois eles tem uma horda de assessores para filtrar todo esse spam.
Entulhar os escritórios deles com pacotes do correio com nossa sucata digital é a melhor maneira de deixarmos bem claro o que achamos do projeto de lei 607/2007. Não podemos, claro, esquecer de enviar com uma carta dizendo do que protestamos.
Vamos mandar aquela trident de 1 mega, aquele gravador de cdrom 1x ou, melhor ainda, aquele gabinete velho de 486. Vamos enviar nosso recado para a presidente da CAS Patrícia Saboya ou um de seus membros titulares. Não sejamos tímidos de só mandar um pacote, quanto mais sucata para mais senadores, melhor.
Minha contribuição é uma placa de vídeo antigona. O pacote irá sem dúvida alguma para a senadora cearense Patrícia Saboya que é a atual presidente da CAS. Uma foto do pacote para inspirar a todos.

Existem algumas formas de participar dessa mobilização, a primeira é enviar sua sucata também, e a outra, tão importante quanto, é divulgar essa iniciativa via blog, email, foruns, listas de discussão e qualquer outro meio que dispor. O endereço de envio é o mesmo, “Senado Federal - Praça dos Três Poderes - Brasília DF - CEP 70165-900″, mudando apenas o gabinete. No caso dos titulares da CAS são estes:
Patrícia Saboyia (PDT-CE):
Ala Sen. Teotônio Vilela, gab. 07
Tel.: (61) 3311-2301/2302
Fax: (61) 3311-2865
Flávio Arns (PTR-PR)
Ala Senador Filinto Müller, gab. 06
Tel.: (61) 3311-2401 a 3311-2407
Fax: (61) 3311-1935
Augusto Botelho (PT-RR)
Ala Senador Filinto Müller, gab. 11
Tel.: (61) 3311-2041 a 2048/3664
Fax: (61) 3311-1931
Paulo Paim (PT-RS)
Anexo I, 22º andar, gab. 04
Tel.: (61) 3311-5227/5232
Fax: (61) 3311-5235
Marcelo Crivella (PRB-RJ)
Ala Ruy Carneiro - gab 02
Tel.: (61) 3311-5225/5730
Fax: (61) 3311-2211
Inárcio Arruda (PC do B-CE)
Ala Senador Filinto Muller, gab. 07
Tel.: (61)3311-5791 / (61)3311-5793
Fax: (61)3311-5798
José Nery (PSOL-PA)
Ala Senador Teotônio Vilela, gab. 17
Tel.: (61) 3311-2104
Fax: (61) 3311-1635
Romero Jucá (PMDB-RR)
Ala Senador Afonso Arinos, gab. 12
Tel.: 311-2111 a 2117
Fax: (61) 311-1653
Valdir Raupp (PMDB-RO)
Ala Senador Teotônio Vilela, gab. 25
Tel.: (61) 3311-2252/2253
Fax: (61) 3311-2853
Wellington Salgado de Oliveira (PMDB-MG)
Ala Senador Teotônio Vilela, Gabinete 15
Tel.: (61) 3311-2244/2245
Fax: (61) 3311-1830
Demósteles Torres (DEM-GO)
Ala Senador Filinto Müller, gab. 10
Tel.: (61) 3311-2091 a 2099
Fax: (61) 3311-2964
Jayme Campos (DEM-MT)
Ala Senador Teotônio Vilela, gab. 24
Tel.: (61) 3311.4061 / (61) 3311.1048
Fax: (61) 3311.2973
Kátia Abreu (DEM-TO)
Ala Senador Teotônio Vilela, gab. 04
Tel.: (61) 3311-2464 / 3311-2708
Fax: (61) 3311-2990
Rosalba Ciarlini (DEM-RN)
Ala teotonio Vilela Gab 03
Tel.: (61) 3311 1777
Fax: (61) 3311 1701
Eduardo Azeredo (PSDB-MG)
Ala Senador Afonso Arinos, gab. 05
Tel.: (61) 3311-2323
Fax: (61) 3311-2883
Lúcia Vânia (PSDB-GO)
Ala Sen. Teotônio Vilela, gab. 16
Tel.: (61) 3311-2035/2844
Fax: (61) 3311-2868
Papaléo Paes (PSDB-AP)
Ala Senador Filinto Müller, gab. 13
Tel.: (61) 3311-3253/3258/3262/3277
Fax: (61) 3311-3293
Gim Argello (PTB-DF)
Ala Afonso Arinos, gab. 10
Tel.: 3311-1161, 3311-1547
Fax: 3311-1650
João Durval (PDT-BA)
Ala Senador Teotônio Vilela, gab. 09
Tel.: (61) 3311-3173
Fax: (61) 3311-2862
Por favor, meus direitos!
Cada dia que passo eu perco mais minha esperança no pais que moro. Antes sempre era porque os nativos, enquanto capazes de reclamar sem fim sobre política, são incapazes de ser cidadãos plenos, votando de maneira incorreta e nunca exigindo de seus governantes. Sempre fui partidário que os habitantes da terra da banana faziam por merecer a lama que estão.
Porém, recentemente notei que existe algo que ao qual ainda não entendo bem, mas tudo indica que os símios dessa nação são ineptos à civilidade e ao respeito ao próximo. Nascer brasileiro significa ser incapaz de viver em comunidade, significa que dar um jeitinho é mais importante que respeitar a lei, a ordem e a decência. Como pode uma pessoa que não entende que vivem em sociedade significa necessariamente ter um apresso aos outros e aceitar que sua vontade não pode se sobrepor aos direitos dos demais.
Minha saga de vítima começou a alguns meses, quando decidi que já era em tempo para exigir meus direitos dos outros. Nada espúrio, apenas o mínimo que qualquer pessoa dotada de suas faculdades mentais diria se tratar de obviedades.
Resolvi começar pela lei 9294 de 1996 que limita os lugares no qual é permitido fumar. Quem me conhece sabe que não fumo e a fumaça me é terrivelmente incômoda. Comecei por lembrar as pessoas da lei quando se tratava de um lugar proibido. Resultado, fui na maioria das fezes muito hostilizados pelos fumantes perto de mim, mesmo de estranhos que ouviram a conversa de longe vieram se intrometer para serem rudes e grosseiros comigo.
Não perdoei nem meus amigos, eles sabem quanto os perturbei. Mas eles, ao menos, se tomaram pela vergonha do desrespeito ao próximo e costumam fumar do lado de fora de restaurantes sem área para fumante. Poderia eu dizer vitória?
Minha segunda tentativa de exigir meus direitos foi o novo código brasileiro de trânsito. Este que regulamenta o uso de bicicletas na rua e ciclovias. Desta vez eu queria apenas poder andar em paz nas ruas de São Paulo e contar que o motoristas iriam ao menos me respeitar e não me matar. Não quis nem exigir o meu direito de ciclista de ter a preferencial sobre carros pois não sou muito fã de ossos quebrados.
Antes de continuar, vou aproveitar para transmitir um pouco de conhecimento. Saibam todos que uma ciclovia existe para o trânsito de bicicletas, ouviram bem? De novo, bicicletas! Bicicletas! Uma ciclovia cuja finalidade não é se deslocar dentro da cidade também admite outros modais como patins ou crianças aprendendo. Mais uma vez eu estou falando do óbvio ao que tudo parece. Mas não, definitivamente isso é muito longe da realidade de muitos freqüentadores de parques da minha cidade.
Hoje não fui eu a vítima do desrespeito, mas sim minha esposa. Estamos a andar no parque do Ibirapuera quando uma mulher resolveu usar a ciclovia toda para passear com o cachorrinho dela. Eu consegui frear, minha mulher passou 6 horas no hospital e terminou com um braço engessado. Tudo isso por conta de gente que é incapaz de respeitar o direito alheio. Esta mulher não se prestou ao menos a ver o que aconteceu com minha mulher, simplesmente virou as costas nos xingando pois quase acertamos o bichinho dela - na ciclovia caso tenha esquecidos.
Decidi então que é hora de tomar uma atitude e educar freqüentadores do parque que não respeitam a ciclovia. A partir de hoje, caso o seu cachorro se encontrar na ciclovia e não for capaz de me matar, ele será atropelado. Se possível espero também deslocar o ombro do lado que estiver segurando a coleira. Dito isso, preciso ir pois tenho sangue futuro a limpar da minha bicicleta.
Monkeypatching: gambiarra du-jour
Finalmente o óbvio atingiu a comunidade de desenvolvedores de Ruby. Enfim descobriram que meta-programação sem disciplina é um engôdo. No começo produz resultados fabulosos rapidamente, só que mais adiante se torna um inferno de integração. Não falta gente calejada em Rails para te contar uma miríade de problemas ao integrar frameworks que modificam classes como Object ou Fixnum.
Sistemas grandes e complexos exigem que seus componentes sejam isolados o máximo possível entre si para minimizar a interferência que um pode causar no outro. Em linguagens como Java, com um sistema de tipos simples, normalmente isso se resume a usar interfaces para delimitar fronteiras e protocolos de comunicação inter-módulo. Porém quando temos classes abertas o problema é muito maior, já que o sistema todo pode ser modificado de um único lugar. Permitir que a modificação de classes fundamentais como Kernel em um canto do sistema influencie todo o resto é um sério problema pois fica difícil controlar o estrago que essas mudanças causam.
O problema com Ruby pode ser entendido melhor se olharmos para ele segundo o a classificação de domínios de page-jones. Segundo o mesmo, podemos classificá-los em fundamental, arquitetural, negócios e aplicação; sendo que a especificidade aumenta na mesma ordem. Outro ponto é que o domínio menos específico não deve depender de um mais específico, assim como dependências laterais devem ser evitadas. Por essa ótica, código da aplicação alterando uma classe fundamental do sistema é uma clara violação desse modelo.
O motivo pelo qual Ruby viola o modelo de page-jones é por não ser possível definir um escopo que não seja global para alterações feitas a classes externas a código em questão. Outro problema que agrava a situação é a impossibilidade de realmente controlar o mecanismo de resolução de nomes de um bloco de código, isso é um grande problema para criação de DSLs que acabam por fazer enormes cirurgias no core da linguagem ou apelam para o monkeypatching, uma “solução” muito menos intrusiva.
Existem duas formas de ser realizar meta-programação; uma é a intrínseca, a qual se permite operar sobre a definição dos tipos e, uma vez feita a alteração, ela é visível a todos usuários do dado tipo, podemos dizer também que é meta-programação no ponto de definição; a outra é a extrínseca, na qual se altera o mecanismo de resolução de nomes para um dado corpo de código, isso permite realizar as mesmas operações que o método anterior, porém todo código precisa informar direta ou indiretamente se existe algo alterando tal mecanismo, podemos dizer também que é meta-programação no ponto de uso.
A vantagem do primeiro é simplicidade e alcance, uma vez feita a modificação nada mais precisa ser feito para sua aplicação utilizar a versão modificada do tipo, porém também é sua fraqueza, pois se uma função depender explicitamente do comportamento anterior ela não mais funcionará - Ruby é um ótimo exemplo de meta-programação intrínseca. Já a segunda técnica é quase o oposto, pois exige que as modificações sejam explicitamente ativadas, que é sua principal vantagem, pois permite facilmente compor conjuntos de alterações e restringir seu escopo, em contrapartida é sua fraqueza pois não permite que uma dada alteração seja definida por completo em um único lugar - extension methods do C# 3.0 é um exemplo simples de meta-programação extrínseca.
Tentar comparar ambas as técnicas e concluir qual a melhor é um exercício fútil, pois sempre existirão fartos casos no qual uma falha e a outra brilha. Assim como achar que Ruby é uma linguagem quebrada por possuir seus defeitos, pois apesar de tudo é um modelo ao mesmo tempo muito rico e simples de usar.
Eu sobrevivi a Campus Party
Semana passada ocorreu a primeira edição da CampusParty brasileira aqui em São Paulo. Resolvi participar pois seria uma ótima oportunidade para reencontrar alguns amigos e mexer com coisas que não faço normalmente. Fora as pessoas que encontrei e conheci, acabei fazendo bastante coisa interessante.
No primeiro dia, vale a pena contar, que estávamos discutindo sobre a abertura do evento e lembrei de toda palhaçada envolvendo o pagamento de cartões de crédito pelo ministro hacker Gilberto Gil a seus assessores diretos. Disso veio a idéia de protestarmos e surgiu o “Paga meu visa também”. Minha contribuição se resumiu ao “MEU” e dar pilha aos mais criativos que estávamos comigo. Nenhum de nós imaginou que isso geraria qualquer repercussão, mas fico feliz de saber que foi o contrário.
Continuando, participei da criação de um pong usando Quartz Composer com o Villela e o Fabricio. Foi bem divertido e me abriu os olhos para essa ferramenta - definitivamente irei brincar mais com ela.Além disso, aprendi um pouco como usar o pure data, não consegui fazer nada razoavelmente audível, mas é um começo. Aproveitei para ajudar um desenvolvedor do Inkscape com uns problemas relacionados ao C++.
Fora isso descobri que escrever um seletor de instruções na mão é muito penoso e resolvi gerar o código dele a partir de um script escrito em Ruby com várias dicas do Phillip. Assim que o projeto amadurecer e tiver algo para mostrar com certeza irei escrever um artigo sobre o resultado desse experimento.
No geral foi uma semana muito produtiva e divertida. Fiquei inclusive com vontade de ir na edição espanhola do evento. Aos que não foram, a cobertura da imprensa poder dar uma boa idéia da dimensão que foi o CampusParty.
Erlang é realmente difícil?
Conversando com um amigo sobre Erlang, ele me comentou que acha a sua sintaxe quase indecifrável, note que se trata de um ótimo programador. Continuando a discussão, resolvi ver qual a diferença de algoritmos simples. Para tornar a comparação justa, vou mostrar o mesmo trecho de código em Java e Erlang. Meu objetivo é explorar quais são os maiores obstáculos de adoção eaprendizado. Começo com algo bem simples, somar todos os elementos de uma seqüência de inteiros:
Em Java:
int soma_array (int[] array) { int r = 0; for(int i = 0; i < array.length; ++i) r += array[i]; return r; }
Em Erlang:
soma_lista (Lista) -> soma_lista (Lista, 0). soma_lista ([H | T], Acc) -> soma_lista (T, H + Acc); soma_lista ([], Acc) -> Acc.
Nada de especial na versão em Java, já a versão em Erlang é bem enigmática, para a maioria quase indecifrável. O problema, talvez, seja o fato de ser uma linguagem puramente funcional, que significa, entre outras coisas, que não existe destructive assignment - não é possível alterar o valor de qualquer coisa depois de definida. Por isso que todo loop deve ser escrito usando tail recursion*.
Há quem possa achar que a sintaxe não ajuda, pois estamos usando notação própria para pattern matching e processamento de listas. Porém, como veremos, é possível reescrevê-lo removendo a sintaxe enxuta por algo mais familiar a linguagens imperativas. Para exemplificar esse processo, vou aproximar a versão Java o máximo da em Erlang e o primeiro passo é introduzir recursão.
int soma_array(int[] array) { return soma_array (array, 0, 0); } int soma_array(int[] array, int idx, int acc) { if (idx == array.length) return acc; return soma_array(array, idx + 1, acc + array[idx]); }
Bem melhor agora! O código lembra um pouco mais a versão funcional. Porém ainda tem a diferença que Erlang usa listas encadeadas no lugar de arrays e são processadas maneira similar ao LISP, utilizando uma função que retorna o primeiro elemento e outra que retorna o resto da lista depois do primeiro. Se modificarmos o código Java para levar isso em conta, vamos ter algo que lembra muito Erlang. O código fica da seguinte forma:
interface Lista { int head(); List tail(); boolean isEmpty(); } int soma_lista(Lista list) { return soma_array (list, 0); } int soma_lista(List list, int acc) { if (list.isEmpty()) return acc; return soma_lista (list.tail(), acc + list.head()); }
Agora que temos uma versão do código Java com o devido processamento de listas, que tal reescrever a versão em Erlang para não usar a notação de processamento de listas e pattern matching. Com isso teremos algo que é gritantemente próximo ao exemplo anterior, como veremos a seguir:
soma_lista(Lista) -> soma_lista (Lista, 0). soma_lista(Lista, Acc) -> if Lista == [] -> Acc; true -> soma_lista(tl(Lista), Acc + hd(Lista)) end.
Agora sim, temos aquilo que meu amigo pode chamar de código decifrável. Porém não é sem abrir mão do poder da linguagem - o que é uma pena, eu diria. Um programador de Erlang provavelmente iria estranhar ver algo como escrito na última versão. Para terminar esse artigo vou mostrar como ficaria se utilizarmos as funções de acesso aleatorio a listas, algo não muito aconselhável, já que é uma operação O(N).
soma_lista(Lista) -> soma_lista(Lista, 1, 0). soma_lista(Lista, Idx, Acc) -> if Idx > length(Lista) -> Acc; true -> soma_lista (Lista, Idx + 1, Acc + lists:nth(Idx, List)) end.
*Já me descobri péssimo com tradução, por isso não me arriscarei com essa também.
Tags: erlang, java, programming languages
Herança não funciona, parte 2
Quando escrevi anteriormente que herança não funciona meu objetivo não era realmente provar isso, mas atentar ao fato de que não é possível construir uma linguagem na qual esse recurso se integra completamente com os demais. Nos comentários do artigo se falou de é possível, sob algumas premissas, suportar herança, variância e mutabilidade. Porém ir do object calculi do Luca Cardelli para um linguagem de programação existe o abismo da expressividade.
Vamos aos poucos, qual é, fundamentalmente, o grande problema com herança? Chama-se destructive assigment, ou mutabilidade se preferir. Referencias não operam de maneira transparente com herança, pois atribuição possui variância contrária a leitura e isso leva a quimeras como os arrays do Java. Estes que faltam type soundness, pois uma atribuição pode falhar em runtime. A solução, infelizmente, é bem complexa, pois exige que arrays não tenham relação de herança entre si e isso, por sua vez, exige anotação de variância para criar coisas simples como um método que copia valores entre dois arrays (imagine o System.ArrayCopy do Java ou o Array.Copy do .net).
Anotações de variância, sejam elas opcionais ou inferidas pelo compilador introduzem uma série de problemas, desde declarações de tipo enormes, passando pelas implicações que isso traz ao runtime e, chegando até, as mensagens de erro indecifráveis dos compiladores. Mas, em última instância, a questão maior é que essa decisão sobre o design de uma linguagem é fundamental demais para poder ser introduzido depois, não ser criar quase que uma linguagem nova e, por essa razão apenas, que linguagens como Java ou C# nunca vão ter isso devidamente resolvido.
A contradição que existe entre herança e mutabilidade é, para alguns olhos, parte da beleza da orientação a objetos - não existe solução que seja correta, simples e compreensível ao mesmo tempo. Tuplas, por exemplo, simples em teoria, porém nenhuma linguagem com anotação de tipos realmente as possui pois tem de escolher mutabilidade, subtipagem covariante e tipagem segura estática - uma decisão dificil.
Talvez a questão seja que subtipagem da forma como temos em linguagens como Java não seja de toda útil. Linguagens dinâmicas como Ruby se valem de subsumption polymophism, ou simplesmente duck typing. Propriedade esta que um objeto vale mais pelas mensagens qual ele responde do que pelos seus super tipos. Quando um objeto implementar uma dada interface vale mais pela interface ou pelos métodos que são expostos?
Herança ainda é um problema enorme, porém não sem deixar de ser interessante e intrigante. Mesmo com resultados como o “Typed Object Calculi” do Luca Cardelli, ainda não surgiu uma linguagem que implementa OO nem herança de maneira satisfatória. Mesmo que seja inviável tal linguagem, importante seria termos resultados nesta área.
MonoSummit2007
Semana passada ocorreu a MonoSummit2007, evento para conhecer os colegas do time, membros da comunidade, discutir sobre o mono e, principalmente, nos planejar. O evento foi uma ótima oportunidade de conhecer gente muito interessante e discutir sobre tópicos que não é normalmente possível nos eventos que acontecem por ai.
Tive o prazer de conhecer o Rodrigo Oliveira, autor da linguagem boo. Fiquei feliz em vez que finalmente implementaram code quotation, ou quasi-quotations para quem preferir, e pattern matching, de maneira a tornar divertido o uso macros e não a tarefa penosa que era antes disso. Discutindo com ele sobre o DB4O, fiquei muito interessado na forma como eles pretendem implementar transações usando STM. Pessoalmente acho que sistemas prevalentes, o prevayler em especial, são implementações patéticas de um problema que não existe. Por outro lado, com Software Transactional Memory é possivel eliminar aquela coisa horrivel que é a exigência de toda mutação ocorrer através de comandos.
Discuti, ou melhor, tive uma aula com o Massi sobre como fazer a transição para forma SSA de maneira barata e quase linear, além disso ele me apresentou ao conceito de HSSA (Hashed Static Single Assignment), que permite realizar enumeração global de valores enquanto faz a renomeação para SSA - que permite eliminar redundâncias de forma mais agressiva que via CSE. Fora isso ele falou da idéia de que é viável construir um bom alocador de registradores como um passo unificado ao que sai do formato SSA, aproveitando que SSA dá quase que de graça use-def e liveness.
Conheci o Jim Purbrick, o cara da LidenLabs que está usando mono para executar as bilhões de linhas de código de LSL do Second Life. O “pequeno” problema que eles tem é que são dezenas de milhares de scripts executando em paralelo em um mesmo simulador - algo que não é fácil de fazer usando threads. A solução que eles adotaram foi implementar um compilador que transforma todos IL em Continuation Passing Style. Basicamente criando suporte a micro-threading e scheduling cooperativo. Não é algo para quem tem estomago fraco, mas funciona e eles esperam ganhos de performance de duas ordens de magnitude.
Foi muito interessante discutir com meus colegas que também contribuem no runtime e pude verificar que não sou o único que acha que generics é uma coisa extremamente complexa. Em breve estará sendo publicada o roadmap do projeto para o ano de 2008 e tem muita coisa legal adiante - aguardem.