C++ para Pythonistas – Introdução e iteradores

C++ pode ter a fama de ser uma linguagem com complexidade comparável à legislação tributária brasileira, mas ao mesmo tempo é uma linguagem extremamente poderosa, podendo ser usada tanto desde microcontroladores até super computadores, sem contar satélites espaciais. Tamanha complexidade pode assustar quem vem de linguagens mais simples como Python (malloc? rvalue references?). Uma ajuda para tratar essa sensação de “como que eu faço aquilo?”  pode ser conhecer as ferramentas que a linguagem oferece em comparação com o que já estamos acostumados.

Nessa série de posts vamos mostrar uma visão geral de algumas dessas ferramentas de C++ e comparar como seria a funcionalidade equivalente em Python. Mais especificamente, a STL – Standard Template Library – e seu header <algorithm>. Esse header possui várias funções que podem simplificar o código e deixar o programador que vem de Python mais à vontade, sem ter que fazer tanta coisa “na mão”. Mas antes de entrar na STL propriamente dita, é importante conhecer o que são os templates de C++, que são o bloco fundamental da STL.

Antes de tudo, um aviso: Dadas as diferenças das linguagens, certamente existirão formas mais eficientes de aplicar alguns conceitos de C++ do que uma mera conversão 1-1 de código em Python, especialmente no que diz respeito ao gerenciamento de recursos (RAII vs gc vs mallocs, etc). Longe de ser um tratado de como extrair o máximo de C++, o objetivo destes posts é apenas facilitar um pouco a vida dos expatriados.

Templates

Templates são uma ferramenta muito útil para programação genérica, permitindo reuso de código de uma maneira mais segura. Numa analogia bem grosseira, eles podem ser vistos como uma espécie de “duck typing em tempo de compilação” de C++. Duck typing no sentido de que, diferente de uma função ou classe normal onde o desenvolvedor já escreve explicitamente quais são todos os tipos envolvidos, num template alguns tipos podem ficar em aberto, para serem definidos posteriormente. O usuário do template então escreve seu código usando ele normalmente e o compilador cuida de checar se todos os tipos fornecidos para o template implementam as operações necessárias para ele ser gerado.

Por exemplo, a função add abaixo recebe como parâmetro do template um tipo T, os dois parâmetros da função são do tipo T e o retorno também é do tipo T. Dentro, é chamado o operador +() para os dois parâmetros T. Como o exemplo mostra, ela pode ser usada tanto para inteiros como para strings:

#include <iostream> // Para imprimir na tela

template <class T>
T add(T a, T b)
{
    return a + b; // operator +()
}

int main()
{
   int x = 5;
   int y = 4;
   std::string name = "Monty";
   std::string surname = "Python";

   std::cout << add(x, y) << std::endl;
   std::cout << add(name, surname) << std::endl;
   return 0;
}

Templates são o cerne da STL, usados por exemplo, nos containers, para definir o tipo a ser guardado nas coleções (std::vector<T>, std::map<K, V>). Além disso, também são usados em coisas mais esotéricas de C++ como Template Metaprogramming, onde as features de template são usadas para efetivamente rodar programas dentro do compilador e estão muito além do escopo dessa série.

Uma diferença entre os templates C++ e as funções normais de Python está no código gerado. Em python, você normalmente só tem uma única instância da função e a máquina virtual faz o “duck typing” em tempo de execução, chamando os métodos dos tipos apropriados. Mas como o código em C++ é convertido diretamente em código executável, sem um interpretador ou máquina virtual, o compilador na prática efetua a substituição dos tipos nos templates e gera uma função “anônima” para aquela substituição (conjunto de tipos). Isso é a chamada instanciação de templates. Ou seja, um eventual template add<T>(T a, T b) que seja chamado para T = int e T = std::string vai gerar duas funções, algo como __add_int(int a, int b) e __add_string(std::string a, std::string b). Por isso que algums sistemas que abusam demais de templates podem acabar ficando grandes demais se não tivermos cuidado.

STL: A Biblioteca Padrão de Arcabouços Templates

A STL é um dos pilares do C++ moderno. E ela pode ser dividida em 4 principais componentes: Containers, Iteradores, Funções e Algoritmos.

Os containers são classes que armazenam elementos, cuidando do gerenciamento de memória usado para guardar os mesmos e oferecendo uma interface uniforme de acesso através de iteradores.  Entre os containers oferecidos estão listas (std::list), mapas (std::map), filas de prioridade (std::priority_queue) e outros. Os containers são implementados como templates para facilitar o reuso deles para diferentes tipos de objetos a serem armazenados.

Iteradores (descritos em maior profundidade mais abaixo) são formas de acessar os items de um container de maneira uniforme, sem se preocupar tanto com o container específico. Por exemplo, ao iterar uma sequencia de itens, você pode acessar da mesma maneira tanto uma std::list quanto um std::vector. Numa analogia com Python, os containers seriam iterables e os iterators seria, bem, iterators.

Funções são representadas principalmente pelos function objects, classes que fazem overload do operator (), semelhante ao método __call__(). Isso permite por exemplo você fornecer predicados para as funções de algoritmo de maneira mais simples. Um functor pode armazenar um contexto mais apropriado para um determinado predicado do que um mero ponteiro de função ou um lambda. Alguns dos tipos de funções mais utilizados pela STL são os Predicate (funções unárias retornando booleano sem modificar o argumento) e os BinaryPredicate (funções binárias retornando booleano sem modificar o argumento). Imagine std::function (a principal classe function object) como sendo poder passar funções C++ como um objeto qualquer, como em Python.

Algoritmos por sua vez fazem uso extensivo desses containers, iteradores e funções para implementar diversas funcionalidades, como map, reduce, produto interno, merge, heap, etc.

Iteradores

Na prática, iteradores são uma ferramenta que permite acessar o conteúdo de containers como mapas, listas, vetores, strings sem se importar diretamente com a estrutura de dados por baixo. Um iterador pode ser visto como um ponteiro que num dado momento referencia algum item do container. A semelhança é tanta com ponteiros que o operator * é usado para acessar o conteúdo de um iterator, tal qual ponteiros.

Os iterators se agrupam em de acordo com suas funcionalidades, por exemplo, se permitem só leitura (Input iterators), se os dados podem ser acessados randomicamente (Random Access iterators) ou sequencialmente (Forward / Bidirectional iterators), se permite modificação ou não (const vs non-const iterators), etc.

Normalmente, os dois métodos mais utilizadas para acessar os iterators de um container são begin() e end(). O primeiro retorna um iterador para o primeiro elemento do container, enquanto a segunda retorna uma posição além do final do container (ambas formando o intervalo [begin, end), assim como o range() de Python).

range-begin-end

(Fonte da imagem: Método begin())

Um dos usos mais comuns de iteradores em C++ é nos for loops. Tradicionalmente, os loops em C/C++ com iteradores possuem a forma:

for (auto it = cont.begin(); it != cont.end(); it++) {...}

Com C++11, os range loops permitem usar uma sintaxe mais enxuta (e parecida com o for…in de Python):

for (auto&& it : cont) {...}

Na prática, é apenas um açúcar sintático para primeira forma, onde o compilador automaticamente declara, compara e incrementa as variáveis. Se cont for uma classe que possui os métodos begin() e end() – como vários containers –  eles serão utilizados para inicializar e comparar os iteradores.

No próximo post, começaremos a destrinchar o <algorithm> comparando com as funções análogas de Python.

o/

Anúncios

[C] Varargs e passando eles adiante

Saindo do mundo C# e voltando para o “metal” C, uma coisa que pode parecer magia para quem nunca usou são as funções variádicas – funções com um número variável de argumentos. Quem nunca se perguntou como uma função como printf é implementada? Em Python (:heart:) nós temos os velhos conhecidos *args e **kwargs, que permitem coletar argumentos extras em uma tupla (posicionais) e um dicionários (argumentos nomeados), respectivamente. Mas em C, o processo é um pouco mais complicado, envolvendo um tipo (va_list) e três macros básicas (va_start, va_arg e va_end), todos disponíveis no header <stdarg.h>.

Exemplo básico:

Abaixo segue um pequeno exemplo de uma função – sum_all – que recebe um inteiro com um contador e uma lista variável de argumentos – que esperamos serem ints –  e retorna a soma desses argumentos variáveis.

#include <stdarg.h>
#include <stdio.h>

int sum_all(int count, ...) {
    va_list args;
    va_start(args, count);
    int acc = 0;

    for (int i=0; i < count; i++) {
        int value = va_arg(args, int);
        acc += value;
    }

    va_end(args);
    return acc;
}

int main() {
    printf("%d\n", sum_all(3, 1, 5, 1000));
}

Uma função variádica é declarada usando “” no final da lista de argumentos – a qual deve conter obrigatoriamente pelo menos um argumento com nome. Dentro do corpo da função por sua vez, você declara uma variável do tipo va_list e passa essa variável para a macro va_start junto com o nome da última variável antes da lista anônima. Esse segundo argumento é importante para o código saber de onde ele deve começar a buscar os argumentos.

Uma vez que temos a va_list inicializada, podemos então passar a pegar os argumentos propriamente ditos. Para isso utilizamos a macro va_arg, que recebe como argumento a va_list inicializada e o tipo esperado do argumento. Essa segunda informação é extremamente importante para o compilador saber qual o tamanho do argumento que ele deve buscar na lista. Ao final, va_end é chamada para limpar a va_list utilizada.

CUIDADO!

Duas observações: O compilador não faz *nenhuma* checagem de tipo entre o tipo passado numa chamada da função e o tipo que va_arg tenta extrair. Nada impede por exemplo de você chamar va_arg(args, double) dentro do loop acima e receber lixo. E diferente de Python onde você recebe uma tratável exceção, em C isso pode significar o programa simplesmente dar um belo segfault na cara do usuário.

A outra observação é quanto ao problema de se descobrir quando chegamos ao final da lista de argumentos. va_arg não dá informação nenhuma a respeito, já que ele apenas extrai da pilha – onde os argumentos são guardados – um valor do tamalho do tipo fornecido. Os dois modelos mais usados para resolver isso são usar um contador/string de formato e usar sentinelas. No primeiro caso, que é como o printf e nossa sum_all acima fazem, à medida que você vai parseando a string/incrementando o contador você sabe quando deve parar ou não. Já com o uso de sentinelas, ao parsear o argumento você determina valores (ex: NULL) que ao serem lidos indicarão que chegamos ao fim da lista.

Passando adiante

Agora digamos que você tenha uma bela função variádica mas você quer “decorar” ela com outra função sua – para fins de debug/log, por exemplo. Como podemos passar esses argumentos anônimos adiante? Passar a va_list diretamente não passaria todos os argumentos diretamente tal como seria uma chamada func(*args) em Python. Na verdade, passar a va_list é equivalente a chamar func(args) – a função recebe um único argumento com a va_list.

E é aproveitando essa última informação que uma boa prática com varargs é fornecer uma variante da função alvo, só que recebendo uma va_list. Por exemplo, temos o par printf e vprintf. A primeira é variádica e na prática é implementada em termos da segunda, que recebe a va_list. Traduzindo para nosso exemplo acima:

int vsum_all(int count, va_list args) {
    int acc = 0;
    for (int i=0; i < count; i++) {
        int value = va_arg(args, int);
        acc += value;
    }
    return acc;
}

int sum_all(int count, ...) {
    va_list args;
    va_start(args, count);

    int acc = vsum_all(count, args);

    va_end(args);
    return acc;
}

E caso você necessite utilizar uma va_list com os argumentos antes de passar eles adiantes, você pode utilizar a va_copy(mylist) para incializar uma nova va_list com uma cópia da va_list original.

o/

C#, COM, OLE e threads

Seguindo as aventuras no mundo dos bindings C# para a EFL, um problema que enfrentei semana passada durante o port dos bindings para o Windows envolvia a famigerada API Win32. Tudo estava correndo bem até tentar mostrar algo na tela, quando os módulos de UI simplesmente se recusavam a carregar, ao contrário de quando tentávamos rodar apenas o código nativo, sem o binding. O culpado: OleInitialize.

A EFL no Windows utiliza por baixo dos panos a Win32, API tradicional para apps nativas do sistema operacional. Entre os componentes utilizados, está o velho OLE (Object Linking and Embedding), responsável por compartilhar itens entre aplicações, como por exemplo os serviços de drag and drop e clipboard, desde o começo dos anos 90. Ao longo do tempo, o OLE acabou gerando o COM (Component Object Model), que serve comunicação interprocessos para outras tecnologias do Windows – numa analogia meio “grosseira”, seria uma espécie de “DBus do Windows”, porém pelo menos 10 anos mais velho.

Para lidar com threading, o COM agrupa os objetos em apartments, que podem ser single thread – todas as chamadas a um objeto saem da mesma thread – e multi thread – cada objeto cuida de sua própria sincronização e chamadas podem vir de múltiplas threads. Entre outras coisas, o Oleinitialize cuida de inicializar o COM caso este já não esteja inicializado, e obrigatoriamente no modo single-thread. Caso a função retorne RPC_E_CHANGED_MODE, significa que o COM já foi inicializado anteriormente em modo multi-thread. E era exatamente isso que estava acontecendo.

Investigando mais um pouco, o principal suspeito era o próprio runtime do .NET. Verificando o número de threads com System.Diagnostics.Process.GetCurrentProcess().Threads.Count, um simples “hello world” indicava 6 threads em execução. E ao inspecionar o código do Mono, realmente o COM era inicializado em modo multithread. E a resposta é dada na documentação do atributo STAThreadAttribute (livre tradução):

A partir da versão 2.0 do framework .NET, o modelo de threading padrão da interoperabilidade COM depende da linguagem em que se está desenvolvendo a aplicação:

  • C++/C#: Multi-thread
  • Visual Basic: Single thread.

Prevendo esse tipo de problema, .NET oferece para C# o já mencionado atributo STAThreadAttribute, que obrigatoriamente deve decorar o Main() da aplicação para indicar que a interoperabilidade COM deve excepcionalmente ser inicializada como single thread. Para aplicações C++, a flag /CLRTHREADATTRIBUTE:STA deve ser fornecida para o linker.

o/

matplotlib.pyplot em (menos de) 5 minutos

import matplotlib.pyplot as plt

x_data = [1,2,4,5,6,7]
y_data = [x**2 for x in x_data]

# Cria um gráfico de linha e retorna o "artista" para detalhar aquela
# série de dados
line, = plt.plot(x_data, y_data)

# Adiciona uma descrição à série acima
line.set_label('Wowness vs doge count')

# As duas operações acima podem ser repetidas para criar novas séries de
# dados.

# Mostra a caixa de legenda
plt.legend()

# Ao invés de um fundo branco, mostra as linhas de 'grade'
plt.grid()

# Descrição dos eixos
plt.xlabel("Number of doges")
plt.ylabel("Wow, how wowsome am i?")

# Título do gráfico
plt.title("Wow, so much graph!")

# Salva o gráfico num arquivo
plt.savefig('doge_graph.png')

# Mostra uma janela com o gráfico
plt.show()

# Fecha a 'janela' virtual, destruindo o gráfico e liberando a memória
plt.close()

Resultado:

doge_graph

 

PS: Se receber um erro “SyntaxError: Non-ASCII character…” rodando o script acima, eu diria que a solução recomendada é mudar para o python 3 🙂

Credo! Um linter para elixir?

Continuando no tema de Elixir, hoje René Föhring lançou o Credo, um linter “que ensina”. Linters são programas que leem o código fonte e fazem uma análise mostrando potenciais problemas ou pontos de melhoria nesse código, desde formatação até coisas mais escopo de variáveis, etc.

No post de anúncio, René discorre sobre como as ferramentas do Elixir costumam dar as mensagens de erro de maneira informativa, apontando uma possível solução ao invés de apenas culpar o desenvolvedor. Ok, vez por outra ainda aparecem algumas mensagens criptografadas mas a tendência é melhorar cada vez mais.

Seguindo essa linha, o Credo busca dar mensagens bem didáticas, ainda com a opção de mostrar uma explicação mais detalhada sobre o item. Abaixo, segue um exemplo do que ele reportou num pet project que comecei hoje (por isso tão poucos erros 😀 ):

  Warnings - please take a look                                                                                                                                                                 
┃ 
┃ [W] ↗ Parameter `k` has same name as a function in the same module.
┃       lib/raycifex/vector.ex:34:33 (Raycifex.Vector.scalar_mult)
┃ [W] ↗ There should be no calls to IO.inspect/1.
┃       lib/raycifex.ex:5:5 (Raycifex.main)
┃ [W] ↗ There should be no calls to IO.inspect/1.
┃       lib/raycifex.ex:6:5 (Raycifex.main)

Please report incorrect results: https://github.com/rrrene/credo/issues

Analysis took 0.1 seconds (0.06s to load, 0.04s running checks)
17 mods/funs, found 3 warnings.

Only considering priority objects: ↑ ↗ →  (use `--help` for options).

Dessas, apenas o ‘k’ repetido que assumo que realmente foi um desleixo, já que os inspect foram conscientes. Dado um aviso desses, você pode pegar o id do erro – isto é, o arquivo + linha/coluna – e rodar o credo novamente, para ver a explicação detalhada.

Para ver um exemplo mais abrangente e mais informações sobre o projeto, podem ver o post de anúncio, linkado acima.

o/

PS: Parabéns para René, tanto pela ferramenta como pelo nome escolhido, que permitiu a “piada” do título…

[Elixir] Coisas legais sobre Elixir

Um post rápido sobre algumas coisas legais em relação a Elixir, uma linguagem funcional dinâmica implementada em cima da máquina virtual do Erlang, mas com uma sintaxe muito mais agradável/produtiva.

Pattern Matching é lindo

Quem nunca escreveu um método em que precisou fazer uma série de if/elses/switches/dispatching complicado dependendo dos valores dos argumentos? Por exemplo, ao calcular o fatorial de um número em Python:

def factorial(x):
  if (x == 0 or x == 1):
    return 1
  else:
    return x * factorial(x-1)

E se ao invés disso pudesse ser algo assim:

def factorial(0): return 1
def factorial(1): return 1
def factorial(n): return n * factorial(n-1)

Não só para argumentos simples, mas Elixir também suporta para tipos mais complexos como “dicts” ou tuplas. Por exemplo, num projetinho onde implementava (ainda em progresso) o Conway’s Game of Life, eu tinha a seguinte função:

def neighbors(%{:dimensions=>{width, height}}, pos) do ...

Essa função aceitaria mapas – que parecem dicionários de Python – que possuíssem a chave :dimensions (um átomo) apontando para uma tupla com 2 valores e um argumento “pos”. Além de dar match no dicionário, essa definição já faz “unpack” e atribui os valores às variáveis width e height.

Robusta

Chris McCord, criador do Phoenix – “o django/rails de Elixir”, recentemente fez um teste em que um servidor manteve 2 milhões de conexões simultâneas, e só não conseguiu mais por limitação no sistema operacional (ulimit). Fabio Akita postou uma timeline do exercício.

Suporte da comunidade

Tanto o canal #elixir-lang na Freenode, o Slack elixir-lang.slack.com (inclusive o canal brasileiro, com muita gente) e a lista de emails elixir-lang-talk são bastate movimentadas, com bastante gente disposta a tirar dúvidas, etc. Inclusive o criador da linguagem, José Valim (yep, é brasileiro) é bastante ativo.

Lambdas em laços for

Duas vezes num espaço de pouco mais de poucas semana me deparei com duas situações em que o programador era vítima de uma característica pouco conhecida da instrução lambda de python.

Ambas as situações envolviam usar lambda dentro de um laço for para criar funções dinamicamente de acordo com o valor fornecido pelo for naquela iteração. Seria algo mais ou menos assim:

for x in range(10):
algo_assincrono.connect(lambda : foobar(x))

No caso, o programador queria que a funcao foobar fosse chamada para cada x gerado no for, enquanto que na prática a função é sempre chamada com o último valor atribuído a x. Isso acontece porque a instrução lambda de python não é exatamente igual às outras lambdas de outras linguagens.

Em python, lambda é apenas uma mandeira simples de encapsular uma expressão dentro de uma função anônima, sem resolver os nomes das variáveis em tempo de definição. Ou seja, lambdas de python não possuem um escopo (espaço de nomes) próprio, trabalhando no escopo em que foram definidas. E como o laço for também não cria um espaço de nomes próprio, a variável x no caso acima estará com o último valor atribuído a ela até o momento da execução do lambda.

Uma alternativa para esse problema é usar aplicação parcial de funções, disponível como a função partial no módulo functools. Ela trabalha recebendo como primeiro parâmetro a função alvo e em seguida os argumentos que devem ser aplicados parcialmente, retornando uma função que receberá apenas os argumentos restantes. Por exemplo:

>>> def foo(x,y,z):
... return x+y+z
...
>>> foo(3)
Traceback (most recent call last):
File "", line 1, in
TypeError: foo() takes exactly 3 arguments (1 given)
>>> from functools import partial
>>> bar = partial(foo, 3)
>>> bar(4,5)
12
>>> bar(4)
Traceback (most recent call last):
File "", line 1, in
TypeError: foo() takes exactly 3 arguments (2 given)
>>> bar(0,0)
3
>>>

Voltando para o exemplo inicial, utilizando partial, ficaria assim:
for x in range(10):
algo_assincrono.connect(partial(foobar, x))

Profiling python com cprofile

Durante o desenvolvimento do raycife+, a performance estava ficando impraticável, chegando a cerca de 3 a 4 minutos para uma cena simples de 100×100 pixels e 7 objetos no meu velho celeron. Seguindo as dicas desse site, rodei o cprofile e pude ver onde poderia ganhar tempo.

No arquivo principal do programa esse código que executa a função main com os argumentos fornecidos na string e salva o resultado num arquivo de log.

if __name__ == '__main__':
    import cProfile
    cProfile.run('main(["raycifeplus.py", "./samples/cornellroomsmall.sdl"])' filename='raycife.cprof')

Para ler o resultado do arquivo de saída:

from pstats import Stats
stats = Stats("raycife.cprof")
stats.sort_stats('time').print_stats()

Exemplo da saída:

Mon Apr 21 11:23:35 2008    raycife.cprof

37298787 function calls in 237.746 CPU seconds

Ordered by: internal time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
1  168.955  168.955  237.734  237.734 ./raycifeplus.py:71(render)
26660558   46.400    0.000   46.409    0.000 /home/lauro/dev/raytracer/raycifeplus/gameobjects/vector3.py:165(__iter__)
5723042    9.791    0.000    9.807    0.000 /home/lauro/dev/raytracer/raycifeplus/gameobjects/vector3.py:265(__sub__)
2422914    4.633    0.000    4.636    0.000 /home/lauro/dev/raytracer/raycifeplus/gameobjects/vector3.py:369(__rmul__)
2422914    4.114    0.000    4.125    0.000 /home/lauro/dev/raytracer/raycifeplus/gameobjects/vector3.py:224(__add__)
10000    2.927    0.000    4.841    0.000 ./raycifeplus.py:80(render_pixel)
1472    0.478    0.000    0.916    0.001 ./raycifeplus.py:99(trace_point)
841    0.129    0.000    0.299    0.000 /home/lauro/dev/raytracer/raycifeplus/objfile.py:30(intersect)
1    0.119    0.119    0.182    0.182 /home/lauro/dev/raytracer/raycifeplus/pnm.py:18(write)
10000    0.044    0.000    0.044    0.000 /home/lauro/dev/raytracer/raycifeplus/pnm.py:15(set_pixel)

Nesse caso, deu para ver que parte do tempo era perdido no módulo vector3, uma implementação de vetores em 3 dimensões usando python.

No próximo vou falar sobre uma tentativa rápida de implementar a mesma funcionalidade usando C++ e criando o binding usando o SIP.

Nota: O código acima ainda poderia ser refinado para receber argumentos na linha de comando mas como é apenas um teste rápido, pode ficar p/ depois uma flag “-p” para habilitar o profiling.

Nota2: Sim, eu sei que a otimização prematura é a raíz de todo mal mas nesse caso é um teste pontual que não vai mudar o algoritmo, além de poder conhecer novas tecnologias 🙂

Java como primeira linguagem e Ônibus Espacial como primeiro avião.

Hoje Marcelo falou num artigo que saiu no Lambda the Ultimate sobre a situação dos currículos nos cursos de computação (mais especificamente, ciência da computação – CC). Entre algumas tendências notadas pelos autores:

  1. O papel da matemática nos cursos de CC está diminuindo .
  2. O desenvolvimento de habilidades em vários tipos de linguagens está sendo substituído pelo uso de grandes bibliotecas e pacotes específicos.
  3. O conjunto de habilidades resultantes é insuficiente (em termos de segurança e proteção) mas fica no nível das indústrias de outsourcing. Traduzindo: estão formando profissionais facilmente substituíveis.

Outro parágrafo que me chamou a atenção, sobre o uso de Java como primeira linguagem (livre tradução):

Vamos propor o seguinte princípio: A beleza irresistível da programação consiste em reduzir processos complexos em blocos muito pequenos de operações primitivas. Java, ao invés de expor essa beleza, encoraja o programador a abordar a solução de problemas como um mecânico numa loja de ferramentas: Revirando uma infinidade de gavetas (pacotes) vamos acabar achando alguma ferramenta (classe) que faz aproximadamente o que queremos. Como ela faz não importa. O resultado é um estudante que sabe como criar um programa simples mas não sabe programar. Um problema maior do uso logo cedo de bibliotecas Java e frameworks é que é impossível para o estudante desenvolver um senso de custo de execução do que está escrito porque é extremamente difícil saber o que cada chamada de método vai eventualmente ser executado.

Não querendo entrar na briga de que linguagem é melhor ou não, mas pelo menos na questão de ser ou não a primeira linguagem. Pela minha experiência (Comecei com Java na faculdade e hoje passo a maior parte do tempo com C e Python), concordo plenamente.

Gutsy, Tomboy, PyMaemo, Python na UFPE

Gutsy

Atualizei o laptop nesse fim de semana, com uma instalação do zero. A princípio, funcionou quase tudo corretamente:

  • Leitor de cartões SD: ao inicializar, o cartão não é montado, precisando recolocar para reconhecer.
  • Wi-fi: Aparece corretamente no lspci mas nada no iwconfig.
  • Scratchbox: Mesmo com o nsswitch.conf correto, não está resolvendo endereços, tanto no wget como no apt-get.

Tomboy

Há algum tempo criei um plugin para o Tomboy que exportava os links entre as notas para um grafo criado pelo graphviz. Na versão mais nova do Tomboy, os plugins foram substituídos pelos Addins. Comecei a migrar e assim que estiver pronto ele estará aqui.

PyMaemo

Com o lançamento do novo aparelho (N810) e versão nova do sistema chegando, durante essa semana deve estar saindo um novo release do Python para Maemo para facilitar a migração das aplicações.

Python na UFPE

Nos dois cursos de computação do CIn/UFPE existe uma disciplina apelidada de “Projetão” em que as equipes (mais de 10 pessoas) simulam o ciclo de desenvolvimento de um produto. Uma das equipes de engenharia vai utilizar python (Apesar da resistência de algumas pessoas…) e amanhã vou dar um mini-curso de 2 horas de introdução à linguagem e alguns recursos que eles possam utilizar.