domingo, 9 de novembro de 2014

Programação Funcional - Por-quê agora?

Primeiro vamos analizar a pertinência da pergunta.





Este é o endereço original da imagem em tamanho natural.

De fato a primeira linguagem funcional da história da humanidade foi o cálculo-λ proposto por Alonso Church em 1938, mas por motivos tecnológicos e práticos essa linguagem nunca teve um compilador construído para ela (confiem em mim, mesmo que existisse um compilador λ vcs não iam querer programar nessa linguagem). Mas a sintaxe  de aplicação de função foi tomada de emprestimo por Lisp, 20 anos depois, essa sim foi a primeira linguagem funcional compilada da história.

Então a primeira parte da pergunta é porque havendo uma linguagem funcional desde 1958, foi preferido o uso da programação imperativa? E a resposta está ilustrada abaixo, chama-se máquina de Von-Neumann.

É essa arquitetura da qual se baseiam todos os dispositivos processadores digitais transistorizados, em resumo todos os aparelhos eletrônicos deste século. Desde a central eletrônica de automóveis passando por celulares e desktops até supercomputadores.
Então temos mais duas dúvidas, porque Von-Neumann vingou e porque está em uso até hoje?
Simplesmente porque é a arquitetura mais fácil e barata de se implementar por semi-condutores, o material de que é feito o processador que se encontra dentro do dispositivo do qual vc lê este artigo. Mesmo sendo possível realizar fisicamente outros conceitos de máquinas processadoras Von-Neumann dominou tanto que seria financeiramente inviável a sua substituição.
E o que a programação imperativa tem a ver com Von-Neumann? Tudo!
Um programa de uma máquina de Von-Neumann é uma sequência de passos, a unidade de controle busca (fetch) as instruções da memória, as decodifica (decode)  a unidade lógico-aritmética (ULA) as executa (execute) e armazena (store) o resultado na memória. Os dados usados pela ULA no mesmo ciclo ficam numa memória ultra-rápida (mais que o cache) chamada banco de registradores de uso geral. De grosso modo são eles que determinam o comprimento de bits da máquina (32 ou 64 bits geralmente) porque esses registradores frequentemente armazenam endereços de memória.
Há várias sofisticações na arquitetura das CPUs dos grandes fabricantes (Intel e AMD) como pipelines, previsores de branch e muitos outros, mas tudo o que eles fazem é aumentar o número desses ciclos por segundo (throughtput). O ciclo é o mesmo há mais de 50 anos.

As linguagens imperativas nada mais são que abstrações desse conceito da máquina de Von-Neumann. As implicações mais importantes desse paradigma são:

  1. Variáveis representam endereços de memória (principal ou RAM) contendo valores arbitrários e esse valor pode mudar no ciclo seguinte.
  2. Um programa é uma sequencia de instruções que geralmente são vários ciclos.
Para um programador criando programa dentro de um sistema de único núcleo processador (single-core) isso é muito conveniente pois há vários algoritmos que aproveitam dessas propriedades e diminuem o número de operações. E por haver um mapeamento biunívoco entre a arquitetura Von Neumann e as linguagens imperativas é fácil escovar os bits (giria que se refere à técnica de otimização baseado no funcionamento da máquina que executa o programa). 

Agora fica a última dúvida:
O que aconteceu para que a programação funcional ganhasse tanta atenção só agora (+/- depois de 2006)?
Isso aconteceu:







Os núcleos (core) continuam realizando o bom e velho ciclo Von Neumann mas o paradigma imperativo tornou-se um grande estorvo à arquitetura multi-nucleo. Porque agora para um programa sequencial em que a ordem dos passos é tudo e cada passo pode depender do anterior, dividir as rotinas entre as várias unidades de processamento disponíveis é algo extremamente complicado.
Existe sim a programação concorrente dentro do paradigma imperativo, com os conceitos de tarefas, threads e outros que permitem a divisão se um programa em vários segmentos (threads) e vários métodos de sincronização entre eles. O problema é que essa sincronização gasta preciosos recursos do sistema operacional e pode levar a uma condição em que um segmento A espera o segmento B terminar alguma tarefa mas ao mesmo tempo o B está travado esperando o A finalizar outra tarefa, esse intertravamento é conhecido por deadlock e é uma de várias complicações da programação concorrente.
A verdade é que há tantos fatores a se tratar para a devida sincronização na programação concorrente,  que ela se torna algo muito improdutivo, limitado somente a aplicativos em que o desempenho é de longe o fator mais relevante. Mas porque não podemos dividir o programa em várias tarefas assíncronas e independentes?
Porque na programação imperativa uma variável pode ter um valor diferente, logo um dos núcleos pode modificar uma variável e outro poderia depender dessa mesma variável entretanto com o valor antigo. Essa variável (da qual depende mais de uma unidade de processamento) é um estado compartilhado e é um grande empecilho à divisão de uma tarefa ou programa entre vários processadores.

E porque a programação funcional resolveria esse impasse?
Porque no paradigma funcional:

  • Variáveis são como na matemática, seu valor é fixo
  • Um programa é uma associação de várias computações (funções, que por sua vez podem ser associações de outras funções ou das mesmas), embora isso é feito também na programação imperativa, há uma tremenda diferença: na programação funcional a ordem dessas computações não importa
Claro que há toda uma ciência para trazer os efeitos acima e conseguir que o programa se comporte da maneira desejada (leia-se da maneira que o usuário final ou cliente espera e pela qual ele pagou). Essa ciência é a programação funcional e será descrita de forma mais elaborada nos próximos artigos



Nenhum comentário:

Postar um comentário