Eloquent Javascript – Parte 2

Fevereiro 20, 2012

Esse post é uma continuação do meu estudo do livro Eloquent Javascript, iniciado aqui. Agora eu também mantenho uma página com o índice de livros com todos os posts referentes a eles.

Capítulo 6 – Programação Funcional

Esse capítulo apresenta algumas funcionalidades comuns a linguagens funcionais como funções anônimas, map, reduce, aplicação parcial de função e composição de funções. Boa parte do capítulo é dedicada a apresentar uma solução para um problema de converter texto em HTML. Achei interessante mostrar a aplicação de programação funcional num exemplo real, mas achei que fugiu um pouco do tópico.

Achei estranho o fato de o autor não mencionar closures, que é uma técnica muito empregada em javascript e que a meu ver é base para várias dessas funcionalidades de programação funcional apresentadas.

Em javascript podemos ter funções dentro de funções. Nesse caso, as funções internas têm acesso às variáveis locais definidas nas funções mais externas. No código abaixo, a função interna (anônima) tem acesso (leitura e escrita) à x e também à variável y.

Em geral, quando a função termina, perdemos a referência para as variáveis locais definidas dentro dessa função. Em javascript, funções podem ter comportamento parecido com o de objetos. Uma prova disso é que podemos retornar uma função, como no exemplo abaixo.

Combinado com a propridade de acessar membros das funções externas, temos um mecanismo de acesso indireto aos membros internos da funções. Esse é o conceito mais básico de closures.

function closure(y){
    var x = 10;
    return function(z){
        alert(x + y + z);
        x++;
    }
}
var foo = closure(5);
foo(3); // imprime 18
foo(3); // imprime 19

Com closure podemos fazer a aplicação parcial de funções. O segredo é que a keyword arguments contém todos os argumentos passados para uma função, mesmo que eles não sejam usados pela função. Assim, a função partial definida abaixo recebe uma função func como argumento. Todos os parâmetros adicionais, ele repassa para func, junto com os parâmetros que serão passados para a função anônima quando ela for invocada.

A função asArray serve apenas para converter parameters para um array, pois é esse o tipo esperado pela função apply.

// É preciso converter a lista de argumentos para array
// antes de passar para a função apply
function asArray(quasiArray, start){
    var result = [ ];
    for(var i = (start || 0); i < quasiArray.length; i++)
        result.push(quasiArray[i]);
    return result;
}

// Partial salva os primeiros argumentos da função em 'fixedArgs'
// Quando a função for de fato chamada, apenas os argumentos restantes
// precisam ser passados
function partial(func){
    // O primeiro argumento é func.
    var fixedArgs = asArray(arguments, 1); 
    return function(){
        return func.apply(null, fixedArgs.concat(asArray(arguments)));
    };
}

Para ficar mais claro, considere uma função anônima implementando uma soma e suponha que passamos esta função para partial, mais o parâmetro 3. Este parâmetro será aplicado à soma antes dos parâmetros passados para a função “objeto” foo.

var foo = partial(function(a, b){ return a + b; }, 3);
var res = foo(20); // 23

Usando uma ideia parecida, podemos fazer a composição de duas funções. No exemplo abaixo, se passarmos uma função foo() e bar() para composition, obteremos uma função equivalente a foo(bar()):

// Composição de duas funções
function composition(func1, func2){
    return function(){
        return func1(func2.apply(null, asArray(arguments)));
    };
}

Um exemplo de uso dessa função é a composição de uma função que retorna o dobro do valor da entrada e outra que soma dois elementos.

var composed = composition(
    function(v){ 
        return 2*v;
    }, 
    function(a, b){
        return a + b;
    }
)
alert(composed(6, 7)); // 26

Capítulo 7 – Busca

Logo no começo do capítulo o autor observa que este não introduzirá nenhuma funcionalidade nova de Javascript. De fato, é apresentado o problema do menor caminho, o qual é atacado inicialmente através de um algoritmo aleatório força bruta. Depois ele descreve rapidamente o algoritmo A* e apresenta uma solução em Javascript, usando alguns dos conceitos ensinados até então.

Capítulo 8 – Orientação a Objetos

Neste capítulo são apresentadas técnicas para trabalhar com orientação a objetos em Javascript. Basicamente, podemos criar um objeto a partir de uma função com o operador new.

function Foo(){
}
var obj = new Foo();

Pensando em classes, Foo age como construtor e definidor da classe. Poderíamos declarar membros e métodos dentro desse construtor.

function Foo(int x){
    this.x = x;
    this.bar = function(){
        alert(this.x);
    }
}
var a = new Foo(10);
a.bar();
var b = new Foo(11);
b.bar();

É possível adicionar e modificar métodos da “classe” depois que ela já foi definida através dos chamados protótipos (prototype). Toda função tem uma propriedade chamada prototype e este herda um conjunto de propriedades do protótipo de Object. Entre essas propriedades estão toString e constructor. O constructor aponta para a própria função, de forma que as duas linhas abaixo são equivalentes:

var a = new Foo(10);
var a = new Foo.prototype.constructor(10);

O autor continua apresentando técnicas para se trabalha com OO em Javascript através de um exemplo bem interessante de simulação, consistindo de um mapa 2D e várias criaturas ficam se movimentando. É possível ver a simulação em tempo real (em modo texto) com o interpretador embutido na página.

Ao apresentar o código dessa simulação, ele descreve uma situação problemática: passar um método como parâmetro para uma função externa (callback), como no exemplo abaixo:

function say(func){
    alert(func());
}

function Foo(){
    this.v = [10];
    this.get = function(){
        return this.v;
    };
    this.bar = function(){
        say(this.get);
    };
}
var c = new Foo();
c.bar();

A função foo irá imprimir undefined. Segundo [3], ao executar a função say, a referência ao objeto é perdida. Para contornar a situação, é possível usar um closure, para manter a referência ao objeto.

Nesse sentido, é comum definir a seguinte função:

function bind(func, obj){
    return function(){
        return func.apply(obj, arguments);
    }
}

A função retornada por bind contém a referência ao objeto obj. Podemos reescrever o método bar da seguinte maneira:

this.bar = function(){
    say(bind(this.get, this));
}

Herança

Um mecanismo de herança pode ser obtido em Javascript através de protótipos. A ideia é clonar o protótipo da classe base.

function clone(object) {
    function Constructor(){};
    Constructor.prototype = object;
    return new Constructor();
}

Object.prototype.inherit = function(baseConstructor) {
  this.prototype = clone(baseConstructor.prototype);
  this.prototype.constructor = this;
};

Um exemplo de uso:

function Base(){ } 
Base.prototype.x = 10;
Base.prototype.bar = function(){
    alert(this.x);
}
function Derived(){ }
Derived.inherit(Base);

Capítulo 9 – Modularidade

Este capítulo não apresenta nenhuma novidade de Javascript, focando mais em boas práticas sobre organização do código. São ensinados truques para gerenciar dependências entre módulos e maneiras de se publicar somente um subconjunto das funções presentes no módulo.

Capítulos 10, 11, 12, 13 e 14

O capítulo 10 apresenta expressões regulares em Javascript. Os capítulos restantes falam sobre o básico programação Web, DOM (Document-Object Model), eventos dos browsers e requisições HTTP.

Como eu já conhecia o básico de cada assuntos desses e no livro não foram muito aprofundados, não tenho nenhum comentário adicional para fazer aqui.

Conclusão

Javascript é mais complicado do que parece! Ainda tenho bastante dúvida sobre closure e protótipos e pretendo pesquisar mais sobre o assunto.

Leitura adicional

[1] Stackoverflow – How do JavaScript closures work?
[2] Javascript Closures
[3] Stackoverflow – JavaScript Callback Scope

Anúncios

Eloquent JavaScript – Parte 1

Janeiro 29, 2012

Já programei em Javascript diversas vezes, mas nunca me disciplinei a aprender a linguagem direito. Sempre seguia tutoriais para descobrir o jeito mais direto de fazer o que eu queria.

Recentemente voltei a programar em Javascript e desta vez decidi ler livros introdutórios sobre essa linguagem. Comecei com o Eloquent Javascript, que está disponível gratuitamente online.

Capítulos 1 e 2

Esses dois capítulos apresentam o básico de programação e a sintaxe básica de Javascript. Passei rapidamente por esses dois capítulos. Uma coisa que eu não lembrava (ou não sabia :P) era o seguinte:

Temos 6 tipos em Javascript: number, string, boolean, object, function e undefined.

O interessante é que segundo a especificação do Javascript (ecma) number é implementado como um double 64 bits (padrão IEEE 754), ou seja, 1 bit pra sinal, 11 bits para expoente e 52 bits de precisão. Isso significa que podemos representar inteiros entre -2^{52} - 1 a 2^{52} -1.

Capítulo 3 – Funções

Neste capítulo comenta-se sobre funções em programação geral e em Javascript. Há duas principais novidades que aprendi aqui.

Quando o número de parâmetros passados a uma função é menor que o número de parâmetros exigidos pela mesma, os parâmetros faltantes são completados com undefined. Por outro lado, se o número de parâmetros passados for maior, os parâmetros excedentes são descartados.

A outra coisa é que apenas funções definem escopo. Ao contrário de linguagens como C++, blocos ou laços não definem escopo. Assim, considere o exemplo abaixo:

var a = 2;
for(var i = 0; i <= 10; i++){
    var a = i;
}
// 'a' vale 10

As variáveis a no código acima se referem à mesma coisa. Agora considere o mesmo exemplo, só que com uma função:

function f(){
    var a = 10; 
}
var a = 2;
f();
// 'a' vale 2

Neste exemplo a variável a tem escopo local em f.

Capítulo 4 – Estruturas de dados: Objetos e Arrays

Uma das principais estruturas em Javascript é o Object. Ele é basicamente um array associativo (também conhecido como dicionário), ou seja, um array onde podemos usar strings como índices e elementos podem ser adicionados dinamicamente.

A sintaxe básica para a instanciação de um objeto é

var obj = new Object();
// ou simplesmente...
var obj = {};

Para inserir uma chave no objeto, podemos usar alguma das seguintes alternativas:

obj["chave"] = valor;
// ou...
obj.chave = valor;
// ou...
var v = "chave";
obj[v] = valor;

Se a chave não existia, ela é criada. Se já existia, seu valor é substituído. Para acessar o valor é da mesma forma, só que se tentarmos acessar o valor de uma chave que não existe, retorna-se undefined.

Para deletar uma chave do dicionário:

delete obj["chave"];
// ou...
delete obj.chave;
// ou...
var v = "chave";
delete obj[v];

Para testar se uma chave está definida no dicionário, usa-se a keyword in:

key in obj; // retorna um boolean

Outra estrutura útil é o Array, que pode ser instanciado como

var arr = new Array();
// ou simplesmente...
var arr = [];

Essa estrutura funciona com um mecanismo parecido com o dicionário, com a principal diferença de que as chaves devem ser números naturais e ao criar uma chave de valor N, todos os índices de 0 a N que ainda não existem são criados também.

Exemplo,

var arr = [];
arr[0] = 2; // arr = [2]
arr[4] = "oi"; // arr = [2, undefined, undefined, undefined, "oi"] 

Capítulo 5 – Tratamento de erros

Javascript possui um mecanismo de tratamento de exceção, com sintaxe try-catch-finally parecida com a de Java:

function foo(){
    throw Error("mensagem de erro");
}

try {
    foo();
}
catch (erro) {
    alert(erro.message); // Imprime "mensagem de erro"
}
finally {
    // Trecho de código A
}
// Trecho de código B

Basicamente qualquer objeto pode ser repassado para a keyword throw e nesse caso o bloco do catch será chamado. No exemplo, a variável erro apontará para o objeto Error, que é pré-definido e possui o campo message.

O bloco do finally é chamado no caso em que ocorre uma interrupção prematura do bloco try ou do catch (via return ou outro throw, por exemplo), representando um fluxo de código alternativo ao trecho B, que provavelmente só deve ser executado na ausência de erros.

Uma dúvida que me surgiu é como tratar diferentes tipos de exceções. Em Java é possível utilizar múltiplos blocos de catch. Já em Javascript, você deve tratar explicitamente através de if e else‘s. Uma alternativa nesse caso é adicionar no objeto um campo errorType contendo uma string, por exemplo [3].

Referências

[1] Dynamically creating keys in javascript associative array
[2] W3 Schools – JavaScript Array Object
[3] Handling specific errors in JavaScript