Skip to content

Instantly share code, notes, and snippets.

@AlyoshaS
Created April 16, 2018 23:00
Show Gist options
  • Save AlyoshaS/00aa5152b985a963e5ff42b3ef6f2502 to your computer and use it in GitHub Desktop.
Save AlyoshaS/00aa5152b985a963e5ff42b3ef6f2502 to your computer and use it in GitHub Desktop.
aaaaaaaaa JS!!

Functional Side - a Demonstration

Introduction

Desde o seu surgimento, a programação funcional vem sendo a queridinha dos aficionados pela Ciência da computação, valorizada pela sua pureza matemática e natureza intrigante, manteve-se escondida em laboratórios de informática, empoeirados... ocupados por cientistas de dados e PhDs esperançosos. Entretanto, de um tempo para cá, vem regenerando-se graças às linguagens modernas, como Python, Julia, Ruby, Clojure e - por último mas não menos importante - JavaScipt.

-- Você disse, JavaScript? Aquela linguagem de script usada em programação web?

-- Sim!

A JavaScript provou ser uma tecnologia importante, e que veio para ficar - por longo tempo. Em grande parte dos casos, isso é devido ao fato de ela poder ser "renascida", extendida, com novas estruturas providas por bibliotecas como o Backbone.js, o jQuery, Dojo, underscore.js e tantas mais. Isto está intimamente relacionado com a sua identidade como "linguagem de programação funcional". Compreendermos a aplicação de estruturas e algoritmos provenientes da programação funcional nos será útil por longo tempo, independentemente de nível e/ou habilidades de quem a procura.

-- Por que devo procurá-la?

A programação funcional é muito poderosa, robusta e elegante; extremamente útil para a manipulação de grandes estruturas de dados. Podemos obter diversas vantagens com JavaScript — uma linguagem de script do lado do cliente - se usada como o meio da aplicação do paradigma funcional em tarefas como, respostas ao dom, exibir de forma ordenada a resposta de uma Api e tantas outras das quais precisam os nossos modernos websites.

Neste livro, aprenderemos grande parte do necessário sobre a programação funcional com JavaScript, a emponderarmos aplicações web mediante o uso de "JavaScript Funcional", a desbloquear seus poderes secretos permitindo-nos escrever programas mais curtos e poderosos, mais rápidos, pois num piscar de olhos são transmitidos pela camada TCP devido ao ínfimo tamanho. Veremos a ideia central do paradigma funcional e como aplicá-lo com o auxílio da JavaScript, um passo-a-passo de problemas e precauções que podem surgir, e, juntamente a programação orientada a objetos que será o nosso auxílio - "auxílio do auxílio".

The demonstration

Talvez seja uma rápida demonstração a melhor maneira de iniciarmos com o paradigma funcional. Realizaremos uma só tarefa de duas maneiras, na primeira utilizaremos os métodos já existentes no 'Core' e noutra o paradigma funcional. Em seguida, compararemos os dois métodos.

The application – an e-commerce website

Digamos que estamos a construir uma aplicação do mundo real, um comércio eletrônico para uma empresa de feijão e café que tem como objetivo aceitar pedidos por conrrespondência. Eles vendem café de qualidade e quantidade distintas, acarretando mudanças de preço.

Imperative methods

Primeiramente, vamos conferir o estilo imperativo. Para melhor demonstração, temos de criar objetos que mantenham os dados. Neles podemos buscar valores como se estivéssemos operando no banco de dados; e posteriormente, talvez possamos operá-los por lá. Mas, no momento, assumiremos que são definidos de forma estática.

// create some objects to store the data
var columbian = {
  name: 'columbian',
  basePrice: 5
};
var frenchRoast = {
  name: 'french roast',
  basePrice: 8
};
var decaf = {
  name: 'decaf',
  basePrice: 6
};

// we'll use a helper function to calculate the cost
// according to the size and print it to an HTML list
function printPrice( coffee, size ) {
  if( size == 'small' ) {
    var price = coffee.basePrice + 2;
  }
  else if( size == 'medium' ) {
    var price = coffee.basePrice + 4;
  }
  else {
    var price = coffee.basePrice + 6
  }

  // create the new html list item
  var node = document.createElement( 'li' );
  var label = coffee.name + ' ' + size;
  var textnode = document.createTextNode( label + ' price: $'+price );
  node.appendChild( textnode );
  document.getElementById( 'products1' ).appendChild( node );
}

// now all we need to do is call the printPrice function
// for every sigle combination of coffee type and size
printPrice( columbian, 'small' );
printPrice( columbian, 'medium' );
printPrice( decaf, 'medium' );

Notemos a simplicidade do código. E se houvessem mais sabores além dos três? quem sabe 20, ou talvez 40? Imaginemos então que se além do tamanho existisse as opções orgânico e não orgânico. Haja código!

Usando este método, estamos dizendo à máquina o que imprimir para cada sabor e tamanho. Isso é o que há de pior em código imperativo - repetição.

Functional programming

Enquanto no paradigma imperativo dizemos para a máquina como ela deve agir para resolver o problema - passo a passo -, no funcional o descrevemos matematicamente para que a máquina aja sob ele.

Tomando o princípio funcional, teremos o mesmo aplicativo com o seguinte código:

// separate the data and logic from the interface
var printPrice = function( price, label ) {
  var node = document.createElement( 'li' );
  var textnode = document.createTextNode( label + ' price: $' + price );
  node.appendChild( textnode );
  document.getElementById( 'products2' ).appendChild( node );
};

// create function objects for each type of coffee
var columbian = function() {
  this.name = 'columbian';
  this.basePrice = 5;
};
var frenchRoast = function() {
  this.name = 'french roast';
  this.basePrice = 8;
};
var decaf = function() {
  this.name = 'decaf';
  this.basePrice = 6;
};

//create object literals for te different sizes
var small = {
  getPrice : function() { return this.basePrice + 2 },
  getLabel : function() { return this.name + ' small' }
};
var medium = {
  getPrice : function() { return this.basePrice + 4 },
  getLabel : function() { return this.name + ' medium' }
};
var large = {
  getPrice : function() { return this.basePrice + 6 },
  getLabel : function() { return this.name + ' large' }
};

// put all the coffee types and sizes into arrays
var coffeeFlavors = [ columbian, frenchRoast, decaf ];
var coffeeSizes = [ small, medium, large ];

// build new objects that are combinations of the above
// and put them into a new array
var coffees = coffeeFlavors.reduce( function( previous, current ) {
  var newCoffee = coffeeSizes.map( function( mixin ) {
    // `plusmix` function for functional mixins, see Ch.7
    var newCoffeeObj = plusMixin( current, mixin );
    return new newCoffeeObj();
  });
  return previous.concat( newCoffee );
}, [] )

// we've now defined how to get the price and label for each
// coffee flavor and size combination, now we can just print them
coffees.forEach( function( coffee ) {
  printPrice( coffee.getPrice(), coffee.getLabel() );
})

Nos é evidente que este é enormemente maior em termos de modularidade. Torna-se agora tarefa simples adicionarmos novos tamanhos e sabores, segue-se abaixo um exemplo:

var arabica = function() {
  this.name = 'arabica',
  this.basePrice = 11;
};

var extraLarge = {
  getPrice : function() { return this.basePrice + 10 },
  getLabel : function() { return this.name + ' extra large' }
};

coffeeFlavors.push( arabica );
coffeeSizes.push( extraLarge );

Os arrays de objetos coffee são "misturados" com os de objetos size - com o auxílio da função plusMixin (consulte o Cap. 7, Programação funcional e orientada a objetos em javascript). As classes de tipo coffee é quem armazena em variáveis o nome do sabor e o preço padrão, as de tipo size contêm métodos para operarmos com os nomes e preços. A "mistura" acontece dentro de uma operação map(), que aplica uma função pura em cada elemento do array e retorna uma nova função dentro da operação reduce() - outra higher order function similar a função map(), exceto pelo fato dele "reduzir" todos os elementos de um Array num só elemento. E por conseguinte, o novo array contendo todas as combinações de tipos e tamanhos é iterado com o método forEach(); o forEach() é outra higher order function que aplica um callback em cada objeto do array. Neste exemplo, fornecemos uma função anônima que instancia os objetos e chama a função printPrice() passando os métodos getPrice() e getLabel() como argumentos.

Poderíamos ainda deixá-lo mais funcional removendo a variável coffees e fazendo um encadeamento de funções - este é um poder secreto da programação funcional.

coffeeFlavors.reduce( function( previous, current ) {
  var newCoffee = coffeeSizes.map( function( mixin ) {
    // `plusMixin` function for functional mixins, see Ch. 7
    var newCoffeeObj = plusMixin( current, mixin );
    return new newCoffeeObj();
  });
  return previous.concat( newCoffee );
}, []).forEach(function( coffee ) {
  printPrice( coffee.getPrice(), coffee.getLabel() );
})

Além disso, o fluxo de controle não é "uma sentada de cima pra baixo" como aconteceu em nosso código imperativo. Na programação funcional, as higher-order functions tomam o lugar dos laços for e while, e, em consequência quase ou nenhuma importância é dada quanto a ordem de execução. Iniciantes tedem a tremer quando "batem o olho" em código quem tem como princípio o paradigma funcional, no começo pode até dar medo, confesso, mas basta um pouco de prática para pegarmos o jeito - não é doloroso -, e logo verá o quão bom é código funcional.

Este exemplo não nos apresentou, mas nem de longe, o que a programação funcional com JavaScript tem a oferecer. Veremos exemplos ainda mais poderosos da abordagem funcional.

Summary

First, the benefits of adopting a functional style are clear.

Second, don't be scared of functional programming. Yes, it is often thought of as pure logic in the form of computer language, but we don't need to understand Lambda calculus to be able to apply it to everyday tasks. The fact is, by allowing our programs to be broken down into smaller pieces, they're easier to understand, simpler to maintain, and more reliable. map() and reduce() function's are lesser-known built-in functions in JavaScript, but we'll look at them.

JavaScript is a scripting language, interactive and approachable. No compiling is necessary. We don't even need to download any development software, your favorite browser works as the interpreter and as the development environment.

Interested? Alright, let's get started!

Fundamentals of Functional Programming

Até agora só vimos "1/10" dos poderes provenientes da programação funcional. Mas o que realmente significa programação funcional? O que faz uma linguagem funcional e não outra? O que torna um estilo funcional e não outro?

Neste capítulo, primeiramente vamos responder as perguntas e em seguida estudar os principais conceitos que a programação funcional engloba:

  • funções e arrays para controlar o fluxo
  • funções puras, funções anônimas, funções recursivas e muito mais
  • Passing functions around like objects[transitar/passar/passear funções como se fossem objetos(qualquer tipo de dado) comuns]
  • funções map(), filter() e reduce()

Functional programming languages

A linguagem de programação é apenas um dos meios de aplicação do paradigma funcional. Com o risco de simplificar demais, podemos dizer que, se a linguagem possui os recursos necessários de que precisa a programação funcional, então ela é uma linguagem funcional - simples assim. Quase sempre o determinante do que é ou não funcional, é o seu estilo.

What makes a language functional?

Não podemos programar funcionalmente com C nem Java. Essas e muitas outras simplesmente não possuem a devida estrutura para apoiá-la. São puramente orientadas a objeto e estritamente "não funcionais".

Em contraposto, não podemos programar orientado a objetos com linguagens puramente funcionais, como Scheme, Haskell , Lisp e tantas mais.

No entanto, existem linguagens que possuem recursos capazes de sustentar ambos os estilos. O Python é exemplo famoso, porém, há outras: Ruby, Julia e - o que interessa - JavaScript. Como essas podem suportar dois padrões de estilo(design patterns) que são muito diferentes uns dos outros? Elas tem posse de todos os recursos requeridos por ambos os paradigmas. Estes em JavaScritp "são tesouros", ficam escondidos - encontráramo-los.

Não caiamos em erro de pensar que, "ser funcional", seja apenas o antedito. Mas, então o que torna a linguagem funcional?

+----------------------+---------------------------+------------------------------------+
| Característica       | Imperativa                | Funcional                          |
+----------------------|---------------------------|------------------------------------+
|                      | Executa as tarefas passo  | Define qual é o problema e quais   |
| Estilo de prog.      | a passo e gerencie as mu- | as transformações de dados neces-  |
|                      | danças de estado          | sárias para alcançar a solução     |
+----------------------+---------------------------+------------------------------------+
| Mudanças de estado   | Importante                | Não existe                         |
+----------------------+---------------------------+------------------------------------+
| Ordem de execução    | Importante                | Não é importante                   |
+----------------------+---------------------------+------------------------------------+
| controle  de fluxo   | Laços, condicionais, e    | Chamadas a funções e recursão      |
| primário             | chamadas a funções        |                                    |
+----------------------+---------------------------+------------------------------------+
| unidade de manipula- | Estruturas e classes de   | Funções como objetos de primeira   |
| ção primária         | objetos                   | classe e a estrutura do tipo set   |
+----------------------+---------------------------+------------------------------------+

Sua sintaxe deve permitir certos "padrões de estilo" - ou talvez "padrões de estilos e costumes" -, como um sistema de inferência de tipos e capacidade de lidar com funções anônimas. Essencialmente, ela deve implementar o cálculo Lambda. Além disso, a estratégia de avaliação do interpretador deve ser non-strict e call-by-need(também conhecida como deferred execution [!execução deferida!]), permitindo usarmos estruturas de dados imutáveis e "non-strict", de usufruir da avaliação preguiçosa(lazy evaluation).

Advantages

Posso apostar que a experência de "iluminação profunda" que temos ao "realmente pegar o jeito da coisa" fará com que todo o nosso aprendizado sobre o paradigma funcional valha a pena. Diante tal experiência tornar-nos-emos melhores programadores para o resto de nossas vidas, mesmo se não formos "funcionalistas" em tempo integral.

Porém, não se trata de aprender a meditar; estamos falando em aprender uma ferramenta extremamente útil, e, em igual escala o ótimo programador a quem dela valer-se.

Formalmente falando, quais são exatamente as vantagens de "pensar à luz" da programação funcional?

Cleaner code

Programas funcionais são limpos, mais simples e menores; simplificando a depuração, o teste e a manutenção.

Queiramos uma função que converta um array bidimensional em unidimensional. À luz das técnicas imperativas podemos a conceber como segue-se:

function merge2dArrayIntoOne( arrays ) {
  var count = arrays.length;
  var merged = new Array( count );
  var c = 0;
  for( var i = 0; i < count; ++i ) {
    for( var j = 0, jlen = arrays[ i ].length; j < jlen; ++j ) {
      merged[ c++ ] = arrays[ i ][ j ];
    }
  }
  return merged
}

À luz funcional podemos:

var merge2dArrayIntoOne2 = function( arrays ) {
  return arrays.reduce( function( p, n ) {
    return p.concat( n );
  });
};

Ambas são equivalentes, porém, o exemplo funcional é mais limpo e conciso.

Modularity

A programação funcional força-nos a dividir um grande problema em instâncias menores do mesmo. Isso requer que tenhamos código modular. Código modular é coeso, fácil de depurar e mais simples de manter. O teste é mais fácil porque podemos checar o verossímil em cada peça(módulo) do código.

Reusability

Os programas funcionais têm diversas funções auxiliares em comum devido à modularidade do paradigma. Veremos que muitas são reutilizadas por diversos aplicativos.

Veremos adiante muitas dessas "funções comuns". No entanto, um programador funcional "full-time", certamente possuirá a sua biblioteca de pequenas funções que podem ser usadas aqui ou acolá. Por exemplo, uma função bem projetada que efetua buscas em linhas de um arquivo de configuração poderá fazer o mesmo com uma tabela hash.

Reduce coupling

O acoplamento é o grau de depência entre módulos de um programa. Basta sermos bons programadores funcionais a escrever funções de primeira classe, de ordem superior, puras e independentes umas das outras, não causando efeitos colaterais em variáveis globais, para que este reduza. Entretanto, com isso, inevitávelmente as funções precisarão umas das outras. Mas o alterar de uma não reflete noutra, desde que a relação "um-para-um" entre "inputs" e "outputs" permaneça correta.

Mathematically correct

Aqui teremos um nível teorico mais elevado. Graças às suas raízes no cálculo Lambda, podemos comprovar a veracidade de programas funcionais com técnicas matemáticas. Esta é uma grande vantagem para os pesquisadores que precisam provar a taxa de crescimento, a complexidade de tempo ou a correção matemática de um programa.

Olhemos para a sequência de Fibonacci. Embora usada apenas como prova de conceitos, a usaremos exatamente por isso - vai ser boa para provar os nossos. A maneira padrão de avaliarmos a sequência de Fibonacci é criando a função recursiva fibonacci(n) = fibonacci(n-2) + fibonacci(n-1), onde o caso base - o caso de parada - retorna 1 quando n < 2, "forçando" a parada da recursão, dando inicio a soma dos valores retornados em cada passo dado pela pilha de chamadas recursivas - a recursive call stack.

Vejamos a descrição das etapas intermediárias que se desenrolam ao calcularmos a sequência:

const fibonacci = function( n ) {
  if( n < 2 ) return 1
  else return fibonacci( n-2 ) + fibonacci( n-1 );
}
console.log( fibonacci( 8 ) );
// Output: 34

Porém, com o auxílio de uma biblioteca que implemente a estratégia de execução preguiçosa(lazy execution strategy), podemos gerar uma sequência indefinida mediante apenas a uma equação matemática. Mas, apenas os necessitados serão computados.

const fibLazy = Lazy.generate( function() {
  let 
    x = 1,
    y = 1;
  return function() {
    var prev = x;
    x = y
    y += prev;
    return prev;
  }
}());

console.log( fibLazy.length() ); // undefined
console.log( fibLazy.take( 12 ).toArray() ); // [ 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144 ]

const fibLaize = Lazy.generate( function() {
  let 
    x = 1,
    y = 1;
  return function() {
    var prev = x;
    x = y
    y += prev;
    return prev;
  }
}());

console.log( fibLaize.take( 9 ).reverse().first( 1 ).toArray() ); // [ 34 ]

A matemática soa muito mais no segundo exemplo, mas a solução teve que contar com o apoio da biblioteca Lazy.js. Porém há outras com objetivos comuns, como a Sloth.js e a wu.js. Em breve iremos vê-las e configurá-las.

Functional programming in a nonfunctional world

A programação funcional e a não funcional podem entrelaçarem-se? Embora este seja tema para o futuro, é importante esclarecermos algumas coisas antes de avançarmos.

Não prentendo ensinar-lhe a implementar uma aplicação funcional pura; do zero. Essas raramente saem da Academia. Em vez disso, pretendo ensinar-lhe técnicas da programação funcional em código imperativo.

Por exemplo, assuma que precisemos das quatro primeiras palavras em um texto qualquer; segue-se abaixo como resolver tal problema

let words = [], count = 0;
text = myString.split(' ');
for( let i = 0; count < 4, i < text.length; i++ ) {
  if( !text[ i ].watch(/[0-9]/) ) {
    words = words.concat( text[ i ] );
    count++;
  }
}
console.log( words )

em contraposto, um programador funcional pode resolvê-lo da seguinte forma

let words = [];
let words = myString.split( ' ' ).filter(function( x ) {
  return ( !x.match( /[1-9]+/ ) );
}).slice( 0, 4 );
console.log( words );

e com o auxílio de uma biblioteca funcional, podemos deix ainda mais elegante

var words = toSequence( myString ).match( /a-zA-Z+/ ).first( 4 );

A chave para identificarmos funções que podem ser reescritas de maneira funcional, consiste em encontrar loops e variáveis temporárias, como há no primeiro exemplo - words e count. Geralmente, podemos substituí-los por funções de ordem superior.

Is JavaScript a functional programming language?

Há uma última pergunta a respondermos. A JavaScript é ou não liguagem funcional?

JavaScript é sem dúvida a linguagem funcional, e digo em âmbito universal, cuja popularidade iguala-se ao incompreensível. Ela é funcional, porém, travestida de "C-like". Sua sintaxe é "c-like", impossível negarmos, e comprovamos quando usamos a notação infixa e a sua sintaxe em blocos - do C. Um de seus grandes problemas, acredite se quiser, é o seu nome. É bem provável que sejamos induzidos ao erro de pensar que Java está relacionado a ela - JavaScript; e pelo nome somos! Mas não basta muito para notar tão pouca semelhança. E para finalizar, devo dizer-lhe que ela também é orientada a objetos; bibliotecas e framworks - e são muitas - é que têm feito o rijo trabalho de abstrair-la ao máximo para tal afirmação. Ela é da época em que orientação a objetos era descolada, 1990; talvez essa seja a causa do erro que muitos cometem ao pensar que a JavaScript tem como fundamento o paradigma orientado a objetos . Não tem!

A JavaScript é da linhagem Lisp & Scheme, duas linguagens funcionais clássicas. Ela é funcional, sempre foi. Suas funções são de primeira classe e podem ser aninhadas, possui clojures e também composição, assim permitindo currying e monads. Tudo isso é de extrema importância para o paradigma funcional. Segue-se outros motivos de ela ser funcional:

  • O Léxico-gramatical da JavaScript permite-nos passar funções como argumentos, possui sistema de inferência de tipos, funções anônimas, de ordem superior, clujures e muito mais. Conceitos todos importantes para alcançarmos tanto a estrutura, quanto o comportamento da programação funcional.

  • Não é puramente orientada a objetos, mas alcança a maioria dos design patterns através da cópia do Protótipo do objeto(copying the Prototype object), um mimi modelo orientado a objetos.A European Computer Manufacturers Association Script (ECMAScript), padrões e especificações de implementação da Javascript, estabelece o seguinte na especificação 4.2.1:

"O ECMAScript possui classes peculiares, não são como as do C++, Smalltalk ou Java, ele suporta construtores que criam objetos. Em uma linguagem orientada a objetos clássica, onde no geral o estado é carregado pela instância, os métodos pela classe e a herança é apenas estrutura e comportamento. No ECMAScript o estado e os métodos são carregados por objetos, e, estrutura, comportamento e estado são todos herdados".

  • É interpretada. Às vezes chamados de "motores", seus interpretadores se parecem muito com os de Scheme. Ambos são dinâmicos, possuem tipos de dados flexíveis de fácil combinação e transformação, avaliam o código em blocos de expressões e tratam as funções de forma semelhante.

Pois bem, podemos notar com clareza que a JavaScript não é puramente funcional. Falta-lhe a avaliação preguiçosa e um suporte nativo a dados imutáveis. Tais "empecilhos" provém de interpretadores call-by-name, o que não acontece com os que adotam call-by-need(https://en.wikipedia.org/wiki/Evaluation_strategy). Não lida muito bem com recursões, pois, trata-as de maneira "tail call"(https://cstheory.stackexchange.com/questions/7540/stack-memory-usage-for-tail-calls). No entanto, podemos mitigá-las com um pouco de atenção. A avaliação non-strict, necessária em sequências infinitas e na avaliação preguiçosa, pode ser alcançada mediante a biblioteca Lazy.js. Podemos obter dados imutáveis simplesmente com técnicas de programação, mas isso é de responsabilidade nossa, não podemos deixar a linguagem se virar sozinha com tais tarefas. E por fim, podemos alcançar a recursão tail call elimination(http://2ality.com/2015/06/tail-call-optimization.html) com um método chamado Trampolining(http://www.datchley.name/recursion-tail-calls-and-trampolines/).

Muito já se debateu sobre a JavaScript ser ou não funcional, orientada a objetos, ambos, ou nenhuma das alternativas. Este não será o último.

No final, a programação funcional é uma maneira de escrever código limpo através de maneiras inteligentes de mutar dados, combinar e usar funções. E a JavaScript oferece um bom suporte para esse estilo. Se realmente queremos ser monges JavaScript, devemos aprender a invocar seu poderes funcionais.

Working with functions

"Às vezes, o elegante é uma função. Não um método. Não uma classe. Não uma estrutura. Apenas uma função."

John Carmack, Pai do game Doom

Em programação funcional todo problema é decomposto em um conjunto de funções. Muitas vezes, as funções são encadeadas, aninhadas entre si, passadas como parâmetros e tratadas como cidadãs de primeira classe. Se você já trabalhou com framworks como jQuery e Node.js, provavelmente já usou algumas dessas técnicas imperceptivelmente!

Vamos iniciar um dilema com a JavaScript.

Digamos que precisamos compilar uma lista de valores atribuídos a objetos genéricos. Os objetos podem ser qualquer coisa: datas, objetos HTML e assim por diante.

let
  obj1 = { value: 1 },
  obj2 = { value: 2 },
  obj3 = { value: 3 };

let values = [];
function accumulate( obj ) {
  values.push( obj.value );
}
accumulate( obj1 );
accumulate( obj2 )
console.log( values );

Funciona, mas quebra fácil. Qualquer código externo pode modificar o vetor de objetos values sem precisar chamar a função accumulate(). E se esquecermos de declarar o conjunto vazio, [], para armazenar instâncias de value, nosso código não funcionará.

Mas se declararmos o conjunto dentro da função, este não poderá ser afetado por nenhum código desonesto.

let
obj1 = { value: 1 },
obj2 = { value: 2 },
obj3 = { value: 3 };

function accumulate2( obj ) {
  let values = [];
  values.push( obj.value );
  return values;
}

console.log( accumulate2( obj1 ) );
console.log( accumulate2( obj2 ) );
console.log( accumulate2( obj3 ) );

Não funciona! Somente o valor do último objeto passado foi retornado.

Podemos resolvê-lo com o aninhamento da função accumulate2().

const ValueAccumulator = function( obj ) {
  let values = [];
  var accumulate2 = function() {
    values.push( obj.value )
  };
  accumulate2();
  return values;
}

Mas o problema é o mesmo, e agora não temos acesso a função accumulate nem a variável values.

Estamos precisando é de uma função auto executável.

Self-invoking functions and closures

E se pudéssemos retornar uma function expression que sempre retorna o vetor de elementos? As variáveis declaradas dentro de uma função estão disponíveis para todo código dentro da função, incluindo as auto executáveis.

Ao usar uma função auto executável, o nosso dilema é resolvido.

let
obj1 = { value: 1 },
obj2 = { value: 2 },
obj3 = { value: 3 };

const ValueAccumulator = function() {
  let values = [];
  let accumulate2 = function( obj ) {
    if( obj ) {
      values.push( obj.value )
      return values;
    }
    else {
      return values;
    }
  };
  return accumulate2;
}

let accumulator = ValueAccumulator();
accumulator( obj1 );
accumulator( obj2 );
console.log( accumulator() )

Tem tudo a ver com o escopo da variavel. A variável values fica a dispor da função interna accumulate2(), mesmo quando a função é chamada por códigos fora do escopo. Isto é chamado de clojure.

Clujures em JavaScript são funções que têm acesso ao escopo pai, mesmo este sendo local.

Closures estão presentes toda e qualquer linguagem funcional; o que não ocorre em linguagens imperativas.

Higher-order functions

De certa forma podemos dizer que uma Self-invoking function é uma higher-order function. Higher-order functions são funções que recebem ou retornam outras.

Não é comum vermos Higher-order functions em linguagens tradicionais. O programador imperativo certemante usará um loop para iterar um array, porém, o funcional adotará uma abordagem completamente diferente. Podemos trabalhar o array com uma higher-order function, aplicando-a em cada item prar criar um novo array.

Essa é a ideia central do paradigma funcional. Uma Higher-order function permite-nos passar sua lógica a outras funções, bem como objetos.

Funções em JavaScript são tratadas como "Cidadãs de primeira classe", tal comportamento pode ser encontrado no Haskell, Scheme ou em linguagens funcionais clássicas. Esse termo pode soar bizarro - Cidadãs de primeira classe -, mas isso simplesmente quer dizer que funções são tratadas da mesma maneira que tipos primitivos: números e objetos. Se números e objetos tem "passe livre", funções também têm.

Para vermos isto em ação, usaremos uma higher-order com nossa função ValueAccumulator() da seção anterior:

// using forEach() to iterate through an array and call a
// callback function, accumulator, for each item
var accumulator2 = ValueAccumulator();
var objects = [ obj1, obj2, obj3 ]; // could be huge array of objects
objects.forEach( accumulator2 );
console.log( accumulator2() );

Pure functions

Uma Função Pura retorna o valor computado apenas com argumentos a ela passados. Com ela evitamos o chamado efeito colateral, ou seja, não tocamos em variáeis externas nem no estado global. Em outras palavras, ela deve manter tais argumentos intactos. Podemos concluir que: a função pura apenas retorna valores.

Exemplo disso é uma função matemática. A função Math.sqrt(4) sempre retornará 2, não usará nenhuma informação "obscura", como configurações ou estado, e nunca causará efeitos colaterais.

Podemos dizer que uma função pura corresponde ao termo 'função matemática'; O Amor Uno entre a entrada e a saída. São de fácil interpretação e altamente reutilizáveis. Por terem total independência, são mais suscetíveis a serem reuzadas aqui ou acolá, no projeto x ou y.

Segue-se abaixo o exemplo de função pura e "não pura" - [non-pure].

// function that prints a message to the center of the screen
var printCenter = function( str ) {
  var elem = document.createElement( "div" );
  elem.textContent = str;
  elem.style.position = 'absolute';
  elem.style.top = window.innerHeight / 2 + "px";
  elem.style.left = window.innerWidth / 2 + "px";
  document.body.appendChild( elem );
};
printCenter( 'hello world' );

// pure function that accomplishes the same thing
var printSomewhere = function( str, height, width ) {
  var elem = document.createElement( "div" );
  elem.textContent = str;
  elem.style.position = 'absolute';
  elem.style.top = height;
  elem.style.left = width;
  return elem;
};
document.body.appendChild(
  printSomewhere(
    'hello world',
    ( window.innerHeight / 2 ) + 10 + "px",
    ( window.innerWidth / 2 )  + 10 + "px"
  )
);

A de percebermos que na "non-pure" exige-se o estado do objeto window para computar a altura [height] e largura [width], entretanto, a pura exige-nos que passêmo-las - height e width - como argumentos. E tal comportamento - que da pure function provém - permite-nos exibir a mensagem em qualquer posição, tornando-a muito versátil.

Tendemos a pensar que a função non-pure seja melhor devido a ela ter adicionado[appendChild] o elemento [elem] em vez de retorná-lo, entretanto, a função printSomewhere() está mais apta a encaixar-se nas técnias da programação funcional.

var messages = [ 'Leibniz', '1646', 'função', 'matemática' ];
messages.map( function( s, i ) {
  return printSomewhere( s, 100 * i * 10, 10 * i * 10 );
}).forEach( function( element ) {
  document.body.appendChild( element );
});

Quando as funções são puras, ou seja, independentes de estado ou do ambiente, não precisamos dar a mínima importância para quando ou onde elas serão computadas. Veremos isso mais adiante com a avaliação preguiçosa.

Anonymous functions

Outro benefício de tratá-las como objetos de primeira classe é que delas proverá as "funções anônimas" [anonymous functions].

Com o nome já concluimos que, funções anônimas são funções sem nomes. Permite-nos definir lógica ad-hoc¹, onde e quando necessário. É conveniente, pois, sabemos que a função será referida apenas uma vez - e tal referência será em apoio a outra função -, para que nomeá-la?

Segue-se abaixo alguns exemplos de funções anônimas:

// The standard way to write anonymous functions
function() { return "hello world" };

// Anonymous function assigned to variable
var anon = function( x, y ) { return x + y };

// Anonymous function used in place of named callback function,
// this is one of the more common uses of anonymous functions.
setInterval( function() { console.log( new Date().getTime() ) }, 1000 );

// Output: 1413249010672, 1413249010673, 1413249010674, ...

// Without wrapping it in anonymous function, it immediately
// execute once then return undefined as the callback:
setInterval( console.log( new Date().getTime() ), 1000 );

// Output: 1413249010671

Segue-se abaixo o uso de uma função anônima em apoio a uma higher-order function:

function powersOf( x ) {
  return function( y ) {
    // this is an anonymous function!
    return Math.pow( x, y );
  }
}

powerOfTwo = powersOf( 2 );
console.log( powerOfTwo( 1 ) ); // 2
console.log( powerOfTwo( 2 ) ); // 4
console.log( powerOfTwo( 3 ) ); // 8

powerOfThree = powersOf( 3 );
console.log( powerOfThree( 3 ) ); // 9
console.log( powerOfThree( 10 ) ); // 59049

A função de retorno não precisa ser nomeada; ela não pode ser usada fora da função powersOf(), e por isso é uma função anônima.

Lembra da nossa função accumulator? Pois bem, podemos reescrevê-la usando funções anônimas.

var
  obj1 = { value: 1 },
  obj2 = { value: 2 },
  obj3 = { value: 3 };

var values = (function() {
  // anonymous function
  var values = [];
  return function( obj ) {
    // another anonymous function!
    if( obj ) {
      values.push( obj.value );
      return values;
    }
    else {
      return values
    }
  }
})(); // make it self-executing
console.log( values( obj1 ) ); // Returns: [obj.value]
console.log( values( obj2 ) ); // Returns: [obj.value, obj2.value]

Nossa! Uma função pura, de ordem superior e anônima. Como é que conseguimos tal feito!? Na verdade, é mais que isso. É também self-executing como dita a estrutura, function(){...})();. O par de parênteses após a função anônima faz com que ela seja executada imediatamente. No exemplo acima, a instância values é atríbuida à saída após a chamada da função "auto-executável".

Funções anônimas não são mero açucar sintático. Elas são a encarnação do cálculo Lambda. Bem... o Lambda Calculus fora inventado muito antes dos computadores e de suas linguagens. Fora apenas uma noção matemática que estudava funções recursivas. [!Notavelmente, descobriu-se que - apesar do fato de que define apenas três tipos de expressões: referências de variáveis, chamadas de função e funções anônimas - era Turing-complete.!]. Hoje, o cálculo lambda está no cerne de todas as linguagens funcionais, basta encontrá-lo; e isso inclui a JavaScript. Por esse motivo, funções anônimas são muitas vezes chamadas de expressões lambda.

Paira sobre a função anônima uma desvantagem. É difícil identificá-la na pilha de execução, tornando a depuração muito complexa. Usar-nos-emos dela com moderação.

Method chains

É bem comum vermos o Method chains [encadeamento de métodos] em JavaScript. Se já usou jQuery, provavelmente usou-a. Às vezes é chamado de "Builder Pattern". É uma técnica usada para simplificar o código em que várias funções são aplicadas a um objeto, uma após a outra.

// Instead of applying the functions one per line...
arr = [ 1, 2, 3, 4 ];
arr1 = arr.reverse();
arr2 = arr1.concat( [ 5, 6 ] );
arr3 = arr2.map( Math.sqrt );

// ...they can be chained together into a one-liner
console.log( [ 1, 2, 3, 4 ].reverse().concat( [ 5, 6 ] ).map( Math.sqrt ) );

// parentheses may be used to illustrate
console.log( ( ( ( [ 1, 2, 3, 4 ].reverse() ).concat( [ 5, 6 ] ) ).map( Math.sqrt ) ) );

Isso só funciona quando as funções são métodos do objeto com que se trabalha. Se criarmos uma função que, por exemplo, receba dois vetores e retorne um único contendo-os "zipados", devemos declará-la como membro do objeto Array.prototype como segue-se abaixo

Array.prototype.zip = function( arr ) {
  // ...
}

para então podermos

arr.zip( [ 11,12,13,14 ] ).map( function( n ){ return n * 2 } );
// Output: 2, 22, 4, 24, 6, 26, 8, 28

Recursion

A recursão é provavelmente a técnica de programação funcional mais famosa. Caso não saiba, uma função recursiva é aquela que chama a si mesma.

Quando uma função chama a si mesma, algo estranho acontece. O seu comportamento é como o loop, na medida em que vai executando o mesmo código várias vezes numa espécie de empilhamento de funções.

Ao usá-las devemos tomar cuidado com o loop infinito(ou melhor dizendo, com a recursão infinita). Bem como nos loops, uma condição deve ser usada para saber quando parar. Isso é chamado de caso base.

Vejamos um exemplo:

var foo = function( n ) {
  if( n < 0 ) {
    // base case
    return 'hello';
  }
  else {
    // recursive case
    foo( n-1 );
  }
}
console.log( foo( 5 ) );

É possível converter qualquer loop em recursão e vice-versa. Mas um algoritmo recursivo é mais apropriado, quase necessário, em situações que diferem muito daquelas onde o loop é apropriado.

Um bom exemplo é a travessia de árvores [tree traversal]. Percorre-la recursivamente é algo simples, porém, percorre-la com loops é muito mais complexo e demanda o uso de uma pilha. E isso vai contra o espírito da programação funcional.

var getLeafs = function( node ) {
  if( node.childNode.length == 0 ) {
    // base case
    return node.innerText;
  }
  else {
    // recursive case:
    return node.childNodes.map( getLeafs );
  }
}

Divide and conquer

A recursão é mais que maneira adversa de iterar estruturas. Um padrão de algoritmo, conhecido como dividir e conquistar, divide recursivamente os problemas em instâncias menores do mesmo problema até que estes sejam pequenos o suficiente para serem resolvidos.

O exemplo clássico é o algoritmo de Euclides para encontrar o máximo divisor comum entre dois números.

function gdc( a, b ) {
  if( b == 0 ) {
    // base case (conquer)
    return 0
  }
  else {
    // recursive case (divide)
    return gcd( b, a % b );
  }
}

console.log( gcd( 12, 8 ) );
console.log( gcd( 100, 20 ) );

Pois bem, em teoria o dividir e conquistar funciona eloquentemente, mas, há uso no mundo real? Sim! A função que ordena arrays da JavaScript não é muito boa. A ordernação muta o array, tornando-a assim não confiável e inflexível. Com o dividir e conquistar podemos fazer melhor.

O algoritmo merge sort usa o design de algoritmo recursivo de divisão e conquista para ordenar um array de forma eficiente dividindo-o recursivamente em sub-arrays menores e, em seguinda, une-os num só array.

A implementação completa em JavaScript é de cerca de 40 linhas de código. No entanto, o pseudo-código é o seguinte:

var mergeSort = function( arr ) {
  if( arr.length < 2 ) {
    // base case: 0 or 1 item arrays don't need sorting
    return items;
  }
  else {
    // recursive case: divide the array, sort, then merde
    var middle = Math.floor( arr.length / 2 );
    // divide
    var left = mergeSort( arr.slice( 0, middle ) );
    var right = mergeSort( arr.slice( middle ) );
    // conquer
    // merge is a helper function that returns a new array
    // of the two arrays merged together
    return merge( left, right );
  }
}

Lazy evaluation

A avaliação preguiçosa, também conhecida como avaliação non-strict, call-by-need e deffered execution, é uma estratégia de avaliação que aguarda até que o valor seja necessário para calcular o resultado de uma função e é particularmente útil para a programação funcional. É evidente que a linha de código x = func() está dizendo para atribuir o valor retornado por func() em x. Mas no que diz respeito ao valor de x não importa até que seja necessário. Esperar para chamar func() até que x seja necessário é conhecido como avaliação preguiçosa.

Essa estrágia pode resultar em um grande aumento no desempenho, especialmente quando usada junto ao encadeamento de métodos e arrays, as técnicas de controle de fluxo favoritas de um programador funcional.

Um empolgante benefício da avaliação preguiçosa é a existência de séries infinitas. Como nada é executado até que se faça necessário, podemos fazer:

// wishful JavaScript pseudocode
var infinateNums = range( 1 to infinity );
var tenPrimes = infiniteNums.getPrimeNumbers().first( 10 );

Isso abre as portas para muitas possibilidades: execução assíncrona, paralelização e composição, só para citar algumas.

No entanto, há um problema: a JavaScript não realiza a avaliação preguiçosa por conta própria, porém, existem bibliotecas que simulam muito bem a avaliação preguiçosa. Veremos-as no Capítulo 3, Setting Up the Functional Programming Environment.

The functional programmer's toolkit

Se analizarmos atentamente os poucos exemplos até agora vistos, talvez note que alguns utilizam-se de métodos que talvez não seja de seu conhecimento. Estes são as funçãos map(), filter() e reduce() que, são de vital impotância para qualquer programa e/ou linguagem funcional. Esses permitem-nos eliminar loops e declarações, resultando em um código limpo.

As funções map(), filter() e reduce() compõem o core [núcleo] do kit de ferramentas do programador funcional; é a coleção de funções puras e de ordem superior que é "o burro de carga" da metodologia funcional. Essas são a epítome das funções puras e de ordem superior; tomam uma função como argumento e retornam uma saída sem causar efeitos colaterais.

Estão disponíveis por padrão em navegadores que implementam a espcificação ECMAScript 5.1 e, só funcionam em arrays. À cada chamada um novo array é criado e retornado. O array a ela passado permanece intacto. E tem mais, elas recebem uma função como argumento, anônima, classificando-a como função de callback; elas iteram pelo array aplicando o callback em cada item!

const myArray = [ 1, 2, 3, 4 ];
const newArray = myArray.map( function( x ) { return x * 2 } );
console.log( myArray );   // Output: [ 1, 2, 3, 4 ]
console.log( newArray );  // Output: [ 2, 4, 6, 8 ]

Só mais um coisa. Por funcionarem apenas com arryas, noutras estruturas de dados iteráveis, como em certos objetos, teremos problemas. Porém, não há com o que preocuparmos, pois, bibliotecas como underscore.js, Lazy.js, stream.js e tantas mais possuem versões exclusivas dos métodos map(), filter() e reduce() que são bem mais versáteis.

Callbacks

Se nunca trabalhou com callback, achá-lo-á intrincado. E por se tratar de JavaScript, podê-lo-á agravar-se devido à vário maneiras de declararmos funções. Um função callback() é passada para outras funções a usarem; é passarmos lógica - a função callback - como se esta fosse um objeto:

var myArray = [ 1, 2, 3 ];
function myCallback( x ) { return x + 1 };
console.log( myArray.map( myCallback ) );

A fim de simplificarmos, podemos passar uma função anônima:

consol.log( myArray.map( function( x ) { return x + 1 } ) );

Não apenas em programação funcional, os callbacks também são usados em coisas mais na JavaScript. A fim de exemplo, vejamos seu uso numa chamada Ajax feita com jQuery:

function myCallback( xhr ) {
  console.log( xhr.status );
  return true;
}
$.ajax( myURI ).done( myCallback );

Observemos que somente seu nome foi usado. E como não estamos chamando o callback, e sim apenas passando seu nome, seria incorreto fazermos:

$.ajax( myURI ).fail( myCallback( xhr ) );
// or
$.ajax( myURI ).fail( myCallback() );

O que aconteceria se o chamássemos? Nesse caso, o método myCallback() tentaria executar-se - "undefined" seria impresso no console e seu retorno seria True. Quando a chamada ajax() for concluída, ela terá "true" como sendo o nome do callback a ser usado e, logicamente, causará um erro.

Pois bem, pelo antevisto nos é evidente que não podemos especificar quais argumentos devem ser passados para o callback. E se por algum motivo precisarmos de parâmetros extras além dos que serão passados ao callback quando a função ajax() for executada, podemos envolvê-lo(s) numa função anônima:

function myCallback( status ) {
  console.log( status );
  return true;
}
$.ajax( myURI ).done( function( xhr ) { myCallback( xhr.status ) } );

Array.prototype.map()

A função map() é a capitã do time. Ela simplesmente aplica o callback em cada elemento do array.

Sintaxe: arr.map( callback [, thisArg] );

Parâmetros:

  • callback(): esta função produz um elemento para o novo array, recebendo os argumentos:
    • currentValue: fornece-nos o elemento em processamento.
    • index: fornece-nos a posição do elemento em processamento.
    • array: fornece-nos o array em processamento.
  • thisArg(): Esta função é opcional. O valor é usado com this ao executar o callback.

Exemplos:

let
  integers = [ 1, -0, 9, -8, 3 ],
  numbers = [ 1, 2, 3, 4 ],
  str = "Santo Tomás de Aquino";

// map integers to their absolute values
console.log( integers.map( Math.abs ) );

// multiply an array of numbers by their position in the array
console.log( numbers.map( ( x, i ) => x * i ) );

// Capitalize every other word in a string
str
  .split(' ')
  .map( ( s, i ) => !( i % 2 ) && s.toUpperCase() || s );

Embora o método Array.prototype.map pertença à o objeto Array, este pode ser facilmente extendido e personalizado a gosto nosso.

MyObject.prototype.map = function( f ) {
  return new MyObject( f( this.value ) );
}

Array.prototype.filter()

A função filter() é usada para selecionar elementos de um array. O callback deve retornar True (para incluir o elemento no novo array) ou False (para ignorá-lo). Podemos obter um comportamento semelhante mediante o uso da função map() retornando o valor null para os elementos que desejarmos eliminar, mas a função filter() eliminará o elemento do novo array em vez de inserir null em seu lugar.

Sintaxe: arr.filter(callback [, thisArg]);

Parâmetros:

  • callback(): Esta função é usada para testar cada elemento do array. Retorna True para manter o elemento, False caso contrário. Segue-se seus parâmetros:
    • currentValue: fornece-nos o elemento em processamento.
    • index: fornece-nos a posição do elemento em processamento.
    • array: fornece-nos o array em processamento.
  • thisArg(): Esta função é opcional. O valor é usado com this ao executar o callback.

Exemplos:

let 
  myArray = [ 1, 3, 6, 10, 15 ],
  words = "4 é o segundo número quadrado. 2² = 2 x 2 = 2 + 2.".split(' '),
  re = /[^x][a-zA-Z]|[éóáúí]/;

// remove all negative numbers
console.log( [ -1, 4, -10, 19 ].filter( x => x > 0 ) );

// remove null values after a map operation
console.log( words.filter( s => s.match( re ) ) );

// remove radom objects from a array
console.log( myArray.filter( () => 0 | Math.random() * 20 ) );

Array.prototype.reduce()

Às vezes chamada de fold, a função reduce() é usada para reduzir os elementos do array em um. O callback retorna a lógica responsável por combinar os objetos. Geralmente a usamos com números para obter o produdório ou o somatório destes. E se tratando de um conjunto strings, a usamos para concatená-las formando uma só única.

Sintaxe: arr.reduce(callback [, initialValue]);

  • callback(): esta função retorna a combinação de dois elementos num só. Segue-se seus parâmetros:
    • previousValue: fornece-nos o valor previamente retornado pela última execução do callback, ou o initialValue, se fornecido
    • currentValue: fornece-nos o elemento em processamento.
    • index: fornece-nos a posição do elemento em processamento.
    • array: fornece-nos o array em processamento.
  • previousValue(): Está função é opcional. É o objeto a ser usado pela primeira execução do callback.

Exemplos:

let numbers = [ 1, 2, 3, 4 ];

// sum up all the values of an array
console.log( numbers.reduce( ( x, y ) => x + y, 0 ) );

// prod up all the values of an array
console.log( numbers.reduce( ( x, y ) => x * y, 1 ) );

// find the largest number
console.log( numbers.reduce( ( a, b ) => Math.max( a, b ) ) );

Honorable mentions

A caixa de ferramentas a auxiliar-nos não é composta apenas pelas funções map(), filter() e reduce(). Existem tantas mais, as quais podemos usar em nossos aplicativos funcionais.

Array.prototype.forEach

Essencialmente a versão não pura do map(), forEach() também itera e aplica um callback() sobre cada elemento do array, porém, nada retorna. É simplesmente uma alternativa "clean" ao laço for.

Sintaxe arr.forEach(callback [, thisArg]);

Parâmetros:

  • callback(): Está função será executada sobre cada elemento do array. Segue-se seus parâmetros:
    • currentValue: fornece-nos o elemento em processamento.
    • index: fornece-nos a posição do elemento em processamento.
    • array: fornece-nos o array em processamento.
  • thisArg(): Esta função é opcional. O valor é usado com this ao executar o callback.

Exemplos:

let arr = [ 1, 4, 10, 20 ];

let nodes = arr.map( function( x ) {
  let elem = document.createElement( "div" );
  elem.textContent = x;
  return elem;
});

// log the value of each item
arr.forEach( function( x ) { console.log( x ) } );

// append notes to the DOM
nodes.forEach( function( x ) { document.body.appendChild( x ) } );

Array.prototype.concat

Ao trabalharmos com diversos arrays e, este trabalho não envolver loops for e while, certamente precisaremos uní-los. Eis a função "built-in" concat(). E o legal é que ela retorna os arryas mantendo-os intactos, ou seja, é pura. E tem mais, ela é capaz de unir uma quantidade indefinida de arrays.

// concatenate two arrays
console.log( [ 2, 4, 6 ].concat( [ 'a', 'b', 'c' ] ) ); // out: [ 2, 4, 6, 'a', 'b', 'c' ]

Podemos perceber que ela uniu-os mantendo o original intacto. Isso quer dizer que podemos fazer o encadeamento sem maiores problemas.

let
  arr1 = [ 2, 6, 12, 20 ],
  arr2 = [ 1, 4, 9, 16 ],
  arr3 = [ 1, 5, 13, 25 ];

let x = arr1.concat( arr2, arr3 );
let y = arr1.concat( arr2 ).concat( arr3 );
let z = arr1.concat( arr1.concat( arr3 ) );
console.log( x, y, z )

As variáveis x, y e z todas contêm [ 2, 6, 12, 20, 1, 4, 9, 16, 1, 5, 13, 25 ].

Array.prototype.reverse

Existe outra função nativa que ajuda-nos em transformações de arrays - a reverse(). Essa inverte um array, fazendo com que o primeiro elemento seja o último e o último o primeiro.

Porém, ele não retorna um novo array; muta o original e isso não é bom. Podemos fê-la de melhor maneira. Segue-se abaixo um método puro de inversão de arrays:

let invert = function( arr ) {
  return arr.map( function( x, i, a ) {
    return a[a.length - ( i + 1 )];
  });
};
let q = invert( [ 1, 2, 3, 4 ] );
console.log( q );

Array.prototype.sort

Bem como no map(), filter() e reduce(), também o método sort() usa-se de um callback para determinar como os objetos de um array devem ser ordenados. Mas, assim como o método anterior, essa não é pura.

const arr = [200, 12, 56, 7, 344];
console.log( arr.sort( function( a, b ) { return a-b } ) );
// arr is now: [ 7, 12, 56, 200, 344 ]

Até poderíamos escrevê-la de maneira pura, porém, algoritmos de ordenação é fonte de tristezas. O fato é que, caso tenhamos uma matriz significamente grande que necessite de ordenação, devemos selecionar o algoritmo/estrutura de dados que melhor adequar-se-á em específico caso. Temos: mergeSort, quickSort, bubbleSort dentre tantos mais.

Array.prototype.every and Array.prototype.some

As funções Array.prototype.every() e Array.prototype.some() são puras e de ordem superior, são métodos do objeto Array usados para testar seus elementos mediante o callback() que retornará um Booleano à respectiva entrada. A every() retorna True se o callback() retorna True para todo elemento do array, a some() retorna True se apenas alguns elementos forem True.

Exemplo:

function isNumber( n ) {
  return !isNaN( parseFloat( n ) ) && isFinite( n );
}

console.log(
  [ 1, 2, 3, 4 ].every( isNumber ),     // Return: true
  [ 1, 2, 3, 'a' ].every( isNumber ),   // Return: false
  [ 1, 2, 3, 'a' ].some( isNumber )     // Return: true
)

Sumary

In order to develop an understanding of functional programming, this chapter covered a fairly broad range of topics. First we analyzed what it means for a programming language to be functional, then we evaluated JavaScript for its functional programming capabilities. Next, we applied the core concepts of functional programming using JavaScript and showcased some of JavaScript's built-in functions for functional programming.

Although JavaScript does have a few tools for functional programming, its functional core remains mostly hidden and much is to be desired. In the next chapter, we will explore several libraries for JavaScript that expose its functional underbelly.


¹ "Ad hoc" é uma frase latina que pode ser aplicada a qualquer coisa, não somente em programação. Basicamente significa, mais ou menos, algo que foi "concebido" em tempo real apenas para lidar com uma situação particular, em oposição a alguma abordagem sistemática para resolver problemas. source: (https://stackoverflow.com/questions/1786735/what-is-exact-meaning-of-ad-hoc-in-programming)

Setting Up the Functional Programming Environment

Introduction

Precisamos saber matemática avançada - teoria das categorias, cálculo lambda, polimorfismos - apenas para escrever código funcional? Precisamos reinventar a roda? A simples resposta para a pergunta é não.

Neste capítulo, pesquisaremos tudo o que pode impactar em nossa maneira de escrever aplicativos funcionais com JavaScript.

  • Bibliotecas
  • Ferramentas
  • Ambientes de desenvolvimento
  • Linguagens funcionais que transpõem seu código para JavaScript
  • E muito Mais

Por favor, entendam que a "bibliotecosofia" funcional do JavaScript é extremamente volátil, bem como o de toda a programação de computadores onde a comunidade efetua mudanças a todo instante; onde novas bibliotecas podem ser adotadas e antigas abandonadas. Por exemplo, creio que durante o processo de escrita deste livro, a popular e estável plataforma I/O - a Node.js - fora bifurcada[forked] por algum membro da comunidade open source. E isso torna-a de destino imprevisível.

Porém, o mais importante a abstraírmos neste capítulo não é sobre saber usar essa ou aquela biblioteca, é sobre como usar toda e qualquer que aprimore a metodologia funcional com JavaScript. Não focaremos em uma ou duas bibliotecas, em vez disso, vamos explorar ao máximo os diversos estilos de programar funcionalmente existentes na JavaScript.

Functional libraries for JavaScript

Dito foi que todo programador funcional têm sua biblioteca, e, incluêm-se também os "funcionalistas JavaScriptianos". Com as atuais plataformas de compartilhamento de código open source, tais como o Github, Bower e NPM, fica simples compartilhar, colaborar e expandi-las. São várias as que facilitam escrevermos código funcional, essas vão desde minúsculas ferramentas até as complexas bibliotecas monolíticas.

Cada biblioteca fomenta um estilo funcional de programar, de matemático e rijo à descontraído e informal. Todas diferem entre si, porém, compartilham de característica comum: possuem conceitos funcionais abstratos que aumentam a reutilização, legibilidade e rebustez de nosso código.

![Até o momento deste escrito, contudo, uma das tantas bibliotecas, não estabeleceu um estilo "de-factum". Alguns argumentam que seja apenas isso, mas posso mostrar-lhes na seção que segue-se, motivos para evitarmos o underscore.js.]! TRADUÇÃO INCERTA

Underscore.js

Aos olhos de muitos a underscore.js tornou-se a principal[standard] biblioteca funcional da JavaScript. É madura, estável, criada por Jeremy Ashkenas, o homem por detrás das bibliotecas Backbone.js e CoffeScript. Em verdade, Underscore é a reimplementação do módulo Enumerable da Ruby, e, talvez isto explique a similaridade entre CoffeScript e Ruby.

Semelhante à jQuery, Underscore não modifica os objetos nativos da JavaScritp, em vez disso, usa um símbolo para definir seu próprio objeto: o caractere underscore _. Usamos-o como segue-se:

let x = _.map( [ 1, 2, 3 ], Math.sqrt );   // Underscore's map function
console.log( x.toString() );

E o método nativo presente no objeto Array, como já o vimos, segue-se:

var x = [ 1, 2, 3 ].map( Math.sqrt );

A diferença é que, no Underscore passamos o objeto Array e o callback() como argumentos do método map() do objeto Underscore (_.map), em contraposto a passarmos apenas o callback para o método map() nativo (Array.prototype.map).

Há muito no Underscore do que mero map(), nele encontramos funções bastante úteis como find(), invoke(), pluck(), sortyBy(), groupBy() e tantas mais.

let greetings = [
  { origin: 'spanish', value: 'hola' },
  { origin: 'english', value: 'hello' }
];

console.log( _.pluck( greetings, 'value' ) );
// Grabs an object's property.
// Returns: ['hola', 'hello']

console.log( _.find( greetings, s => s.origin == 'spanish' ) );
// Looks for the first obj that passes the truth test
// Returns: { origin: 'spanish', value: 'hola' }

let greetings = greetings.concat( _.object( [ 'origin', 'value' ], [ 'french', 'bonjour' ] ) )
console.log( greetings );
// _.object creates an object literal from two merged arrays
// Returns: [{origin: 'spanish', value: 'hola'},
//{origin: 'english', value: 'hello'},
//{origin: 'french', value: 'bonjour'}]

Com ele também podemos encadear métodos:

var g = _.chain( greetings )
  .sortBy( x => x.value.length )
  .pluck( 'origin' )
  .map( x => x.chatAt( 0 ).toUpperCase() + x.slice( 1 ) )
  .reduce( ( x, y ) => x + ' ' + y, '' )
  .value();

// Applies the functions
// Returns: 'Spanish English French'
console.log( g );

O método _.chain() vai "empacotando" as funções Underscore sequencialmente e, não importando o quando e onde invocamos o método _.value(), este irá extrair os valores do até então "pacote". Os "objetos empacotados" são bastante úteis para a entrelaçarmos - a biblioteca underscore.js - com a orientação a objetos. EXTRA [ object wrapped: Decorator: https://addyosmani.com/blog/decorator-pattern/ | method chains: Builder https://medium.com/@axelhadfeg/builder-pattern-using-javascript-and-es6-ec1539182e24 ]

É facil usá-la, de adaptar-nos a ela, contudo, vem sendo criticada por forçar-nos a escrever código extremamente verboso e por incentivar o mau uso dos padrões [patterns].

A partir da versão 1.7.0, liberada após a palestra do caríssimo Brian Lonsdorf intitulada Hey Underscore, you're doing it wrong!, presente no YouTube, fomos impedidos de extender funções como map(), reduce(), filter() e outras mais.

_.prototype.map = function(obj, iterate, [context]) {
  if (Array.prototype.map && obj.map === Array.prototype.map)
  return obj.map(iterate, context);
  // ...
};

Link da palestra https://www.youtube.com/watch?v=m3svKOdZijA

Já sabemos que a JavaScript permite a mutação de dados, porém, uma biblioteca funcional deve evitar que suas "funções auxiliares" mutem objetos a elas passados. Veremos um bom exemplo a seguir. O intenção do snippet é retornar uma nova lista selected com alguma opção padrão marcada, mas o pior acontece, a lista orignal sofre mutação.

function getSelectedOptions(id, value) {
  options = document.querySelectorAll('#' + id + ' option');
  var newOptions = _.map(options, function(opt){
    if (opt.text == value) {
      opt.selected = true;
      opt.text += ' (this is the default)';
    }
    else {
      opt.selected = false;
    }
    return opt;
  });
  return newOptions;
}
var optionsHelp = getSelectedOptions('timezones', 'Chicago');

Para criar uma cópia de cada objeto da lista passada para a função(map), teríamos que inserir a linha opt = opt.cloneNode(); no interior do callback(). A função map() possui cheats para melhor performance, [but it is at the cost of functional feng shui]. A função nativa Array.prototype.map() não necessita dessa cópia, pois, ela não trabalha com coleções do tipo nodelist.

Underscore está longe de ser "matematicamente correto" - nos ditames da programação funcional -, mas tenha em mente que seu intento nunca foi o de tranformar/extender a JavaScript em puramente funcional; tal define-se como sendo "um amontoado de 'utilitários funcionais' úteis". Não creio que seja mera coleção de utilitários, tampouco biblioteca funcional séria.

Há demais bibliotecas por aí? Alguma que implemente a matemática fielmente?

Fantasy Land

Às vezes, a verdade é mais estranha que a ficção.

Fantasy Land é uma coleção de bibliotecas essencialmente funcionais além de especificação formal para a implementação de "estruturas algébricas" em JavaScript. Formalmente, Fantasy Land especifica a interoperabilidade entre estruturas algébricas[sistemas algébricos] comuns, "álgebras" para simplificar: monads, monoids, setoids, functors, chains, e outras. Não são nomes assustadores!? [...] acalmemos, pois, é mero conjunto de valores, de operadores e mais algumas leis a serem seguidas. A simples palavras, apenas objetos.

Vejamos seu funcionamento. Na Fantasy Land cada álgebra é uma especificação a parte, e, podemos tomá-la como base de implementação de outras mais.

Imgur

Segue-se algumas das especificações das álgebras:

  • Setoids:
    • Implementa as leis reflexiva, simétrica e transitiva
    • Define o método equals()
  • Semigroups
    • Implementa a lei associativa
    • Define o método concat()
  • Monoid
    • Implementa as identidades direita e esquerda
    • Define o método empty()
  • Functor
    • Implementa as leis de identidade e composição
    • Define o método map()

A lista continua...

Não é de suma importância sabermos de cór o que cada álgebra é, mas certamente ajuda, especialmente se estivermos a escrever uma biblioteca nos ditames da especificação. Não é apenas uma abstração absurda[de que nada vale], e sim a descrição de como implementar "uma abstração de alto nível" chamada Teoria das Categorias. A veremos em detalhes no Capítulo 5, Category Theory.

A Fantasy Land não é apenas especificação para a implementação da programação funcional, ela também fornece-nos um conjunto de módulos funcionais para JavaScript. Porém, muitos estão incompletos e a documentação é bem escassa. Mas Fantasy Land não é a única biblioteca open source com tais propósitos. Há também outras, a saber: Bilby.js.

Bilby.js

Que diabos é bilby? Não, não é uma criatura mágica da Ilha Fantástica. Ela é daqui mesmo, da Terra, é o cruzamento esquisito/fofo entre um rato e um coelho. No entanto, a biblioteca bilby.js está de acordo com as especificações da "Fantasyland".

Mas o fato é que, a bilby.js é uma biblioteca funcional séria. É tão séria que está presente em sua documentação: Sério, comigo você pode aplicar a Teoria das Categorias em seu código a fim de torná-lo altamente abstrato. Funcional, pois, tornar-se-á referencialmente transparente. Uau! isso é muito sério. Em sua documentação http://bilby.brianmckenna.org/ prossegue-se os dizeres:

  • Multi-métodos imutáveis para polimorfismos ad-hoc
  • Estruturas de dados funcionais
  • Sobrecarga de operadores em sintaxe funcional
  • Automated specification testing [!Especificação e testes automatizados!](ScalaCheck, QuickCheck)

É de longe a que mais se adequada aos moldes das especificações para implementação de álgebras da Fatansy Land, Bilby.js é uma ótima escolha caso queiramos estar fielmente de acordo com o estilo funcional.

Vejamos um exemplo:

// environments in bilby are immutable structure for multimethods
let shapes1 = bilby.environment()
  // can define methods
  .method(
    'area',                     // methods take a name
    a => typeof( a ) == 'rect', // a predicate
    a => a.x * a.y              // and am implementation
  )
  // and properties, like methods with predicates that always
  // return true
  .property(
    'name',  // takes a name
    'shape'  // and a function  
  );

// now we can overload it
let shapes2 = shapes1.method(
  'area',
  a => typeof( a ) == 'circle',
  a => a.r * a.r * Math.PI
)

let shapes3 = shapes2.method(
  'area',
  a => typeof( a ) == 'triangle',
  a => a.height * a.base / 2
);

// and now we can do something like this
let objs = [ { type: 'circle', r: 5 }, { type: 'rect', x: 2, y: 3 } ];
let areas = objs.map( shapes3.area );

// and this
var totalArea = objs.map( shapes3.area ).reduce( add );

Lazy.js

Bacon.js

Honorable mentions

Development and production environments

Browsers

Server-side JavaScript

A functional use case in the server-side environment

CLI

Using functional libraries with other JavaScript modules

Functional languages that compile into JavaScript

Sumary

Pesquise mais

Documentações

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment