quinta-feira, 26 de agosto de 2010

Paradigmas de programação

Glider Muito mais empolgante do que aprender uma nova linguagem de programação é aprender um novo paradigma de programação.

A parte mais louca é perceber como o paradigma funciona. Isso acontece de modo intuitivo, muito parecido com como quando se aprende uma língua nova: nada faz sentido no começo, tudo é muito mecânico; de repente um estalo e tudo faz sentido!

Há muitos paradigmas de programação, já andei falando disso antes, mas muitos são variações dos mesmos tipos básicos: paradigmas imperativo, funcional e declarativo.

Programação imperativa


É o primeiro paradigma com o qual a maioria dos programadores trava contato.

Na programação imperativa, o programa se parece com uma receita de bolo: uma sequência de ordens, chamadas comandos ou instruções (statements em inglês), que devem ser executadas.

A partir desse conceito, derivam diversos paradigmas secundários. O principal é a programação estruturada ou procedimental (procedural em inglês), onde os comandos são organizados em grupos, chamados funções, que podem ser evocados em momentos diferentes.

As funções podem receber ou não parâmetros, que alteram seu comportamento, e podem retornar valores. Em algumas linguagens, quando o grupo de comandos não recebe parâmetros nem retorna valores, ele é chamado subrotina.

Para exemplificar o funcionamento da programação imperativa, vamos implementar fatorial em C – mais simples impossível.

Para quem não sabe, fatorial consiste em uma função recursiva cuja parada é:
0! = 1


E o passo é:
n! = n * (n - 1)!


Outra forma de entender é reiterativamente:
n! = 1 * 1 * 2 * 3 * … * (n - 2) * (n - 1) * n


Vamos implementar a função reiterativa em C para ilustrar melhor o paradigma:
#include <stdlib.h>
#include <stdio.h>

int factorial(int);


int main(int argc, char **argv) {
printf("O fatorial de 5 é %d\n", factorial(5));
return EXIT_SUCCESS;
}


int factorial(int x) {
int result = 1;
int i;
for (i=1; i<=x; ++i)
result *= i;
return result;
}


Cada comando é uma ordem dada ao sistema: atribua 1 à variável result; para cada i de 1 até o valor do argumento x, multiplique o valor de result pelo valor de i; retorne o valor de result.

São ordens dadas ao sistema.
[update 2011-07-24]
Na programação imperativa, é muito comum a manipulação de alterações de estado, daí o uso de variáveis – contentores (ou slots) que podem sofrer alterações de valor ao longo da existência do processo em execução.
[/update]


Orientação a objetos


Orientação a objetos não passa de um variante da programação imperativa, mas um variante digno de citação.

Na orientação a objetos, as ordens não são dadas a um sistema abstrato, mas objetos recebem as ordens e as executam.

O conceito de objeto é bem genérico: qualquer coisa pode ser um objeto, um número, uma janela na tela, uma coleção…

Na orientação a objetos as ordens dadas a um objeto são chamadas mensagens e a definição de como o objeto reage a cada mensagem é chamada método.

O exemplo será a versão recursiva de fatorial implementada em Smalltalk:
!Integer methodsFor: 'mathematical functions'!

factorial
self = 0 ifTrue: [↑ 1].
self > 0 ifTrue: [↑ self * (self - 1) factorial].
self error: 'Not valid for negative integers'.
!
!!


Transcript
show: 'O fatorial de 5 é ';
show: 5 factorial printString;
cr.


[update 2011-07-16]
Atualizei o código em Smalltalk segundo implementação da máquina virtual Pharo.
[/update]


O princípio é parecido com o anterior, mas em vez de dar uma ordem ao sistema – calcule o fatorial de 5 –, é dada ao próprio número – número 5, qual seu fatorial?

Observação: a orientação a objetos pode ser aplicada também à programação funcional.

Programação funcional


Enquanto na programação imperativa ordens são dadas ao sistema ou a objetos, no paradigma funcional são definidas funções, como as matemáticas, e o programa nasce da interação entre as funções: o resultado de umas funções é passado como parâmetro para outras.
[update 2011-07-24]
Na programação funcional, o estado do sistema tende a ser constante, havendo apenas a troca de informação por parâmetros. Assim é comum que não haja variáveis, mas incógnitas constantes, que não sofrem (ou não devem sofrer) alteração de valor ao longo da execução do programa.
[/update]


O mesmo exemplo, fatorial, em Common Lisp:
(defun factorial ((x integer))
(if (zerop x)
1
(* x (factorial (- x 1)))))


(format t "~%O fatorial de 5 é ~A~&" (factorial 5))


O conceito é ligeiramente difente: a função factorial, que recebe um parâmetro inteiro nomeado x, é definida como o resultado da função if; a função if recebe como primeiro parâmetro o resultado da função zerop, que é verdadeiro quando x é igual a zero; o segundo parâmetro de if, 1, é o resultado caso o primeiro parâmetro seja verdadeiro; o terceiro parâmetro é o resultado caso seja falso; o terceiro parâmetro é o resultado da função de multiplicação. Entendendo até aqui, o resto é autoexplicativo.

[update 2011-06-19]

Bônus!



Fatorial em Scheme:
(define (factorial x)
(if (zero? x)
1
(* x (factorial (- x 1)))))

(display "O fatorial de 5 é ")
(display (factorial 5))
(newline)


E em Erlang:
-module (fact).
-export([factorial/1]).

factorial(0) -> 1;
factorial(X) -> X * factorial(X - 1).


Salve como fact.erl. No prompt:
1> c("fact.erl").
{ok,fact}
2> fact:factorial(5).
120

[/update]


Programação declarativa


Este paradigma é um dos mais complicados para pescar

[update 2011-07-24]
Na programação declaração o código diz o que o programa deve fazer, não como.
[/update]


O programa consiste em uma lista de declarações de verdades.

Nos paradigmas anteriores havia variáveis, que sofriam atribuições. Na programação declarativa há incógnitas, que não sofrem atribuições! O valor de cada incógnita é constante e inicialmente desconhecido. O que o programa faz é cruzar as consultas (queries) com as declarações que formam o conjunto verdade para deduzir o valor das incógnitas.

O exemplo consiste no fatorial implementado em Prolog:
factorial(0, 1).

factorial(N, F) :-
N > 0,
N1 is N - 1,
factorial(N1, F1),
F is N * F1.


O que este conjunto verdade diz é que o fatorial de 0 é 1 e que o fatorial de N é F quando:
  1. N é maior que zero;
  2. N1 é N menos 1;
  3. o fatorial de N1 é F1;
  4. e F é igual a N vezes F1.


Para a consulta:
| ?- [factorial].
fatorial.pro compiled, 7 lines read - 869 bytes written, 7 ms
(1 ms) yes
| ?- factorial(5, X),
write('O fatorial de 5 é '),
write(X),
nl.


Para entender como funciona, há um applet em Java em um tutorial que demonstra o chinês. É só encontrar o applet na página, ir clicando em Step e ver acontecer.

A linguagem declarativa da vez é Erlang.

[update 2011-06-19]
Na época deste artigo, em vez de estudar Erlang, perguntei por aí e fui informado que Erlang seria uma linguagem declarativa. Agora que tomei vergonha na cara e estudei um pouco, descobri que é uma linguagem funcional, extremamente similar a Haskell.

Para experimentar Erlang, há o applet Try Erlang.
[/update]


[]’s
Cacilhας, La Batalema
blog comments powered by Disqus