Parallel Haskell
Published:
Haskell
Haskell é uma linguagem de programação puramente funcional e de uso geral. Por ser puramente funcional o ator principal da linguagem é a função em contraste com as instruções e estado, nas linguagens procedurais e os objetos nas linguagens orientadas a objetos. Outra característica é a total ausência de estados, ou seja, todas as variáveis no Haskell são imutáveis.
Com isso, a computação pode ser pensada como uma sequência de transformação dos dados por funções. Tome como exemplo o cálculo da média entre dois números que pode ser descrito por uma função de duas variáveis:
media x y = (x + y) / 2
Essa função recebe dois valores numéricos como entrada e retorna um valor numérico como saída. Vamos agora verificar uma simples função, em C, para calcular a média de valores de uma array de inteiros:
double media (int * valores, int n) {
double soma = 0;
int i;
for (i = 0; i < n; i++)
soma += valores[i];
return soma / n;
}
A variável valores
é passada como ponteiro de memória, ou seja, qualquer alteração na função, altera o valor original da variável. Imagine que o contexto do programa principal é de processamento concorrente e que, durante o cálculo da média, outro thread modifica o conteúdo de valores
. O resultado da média será diferente do resultado esperado.
Em outro contexto, imagine que a função é feita da seguinte forma:
double media (int * valores, int n) {
double soma = 0;
int i;
for (i = 0; i < n; i++)
soma_valor(&soma, valores[i]);
return soma / n;
}
e a função soma_valor
:
void soma_valor (double * soma, int valor) {
soma += valor;
}
Você consegue encontrar o bug? Obviamente em funções simples esses erros não passam desapercebidos, porém em programas maiores com muitas funções complexas a chance de o programador cometer esse tipo de erro aumenta. Tais erros são difíceis de serem identificados, pois não ocasionam mensagem de erro, apenas um resultado inconsistente.
Em Haskell, além de não permitir variáveis mutáveis e mudança de estado, todas as funções devem ser bem especificadas de acordo com os tipos que recebem de entrada e o tipo retornado de saída. Embora a linguagem faça inferência dos tipos, ela só o faz se tem certeza absoluta dos tipos envolvidos. Além disso, como não existe variável mutável, erros que podem ocorrer em linguagens com tipagem automática são evitados:
def mult (x):
return x * 2
x = 1
...
x = 'a'
...
mult(x) # retorna 'aa', possivelmente inesperado
Outra característica do Haskell que pode ser benéfico ou não é o lazy evaluation em que as expressões são avaliadas apenas quando necessárias, isso já ocorre em linguagens imperativas no caso de estruturas de seleção:
if (a) {
foo();
} else {
bar();
}
No exemplo acima, a função foo
será executada apenas caso a condição a
seja verdadeira, foo
e bar
nunca serão avaliadas antes de testar a condição. Agora considere a seguinte função:
int f(int x, int y) {
return 2*x;
}
int main () {
int x = 2,;
f(x*x, 4*x + 3);
return 0;
}
Ao chamar a função f
, tanto a expressão x*x
como 4*x + 3
serão avaliadas. Considere a mesma função em Haskell:
f x y = 2*x
main = do
let z = 2
print (f (z*z) (4*z + 3))
Na função acima a expressão 4*z + 3
não será avaliada, pois nunca é utilizada. Isso evita computação desnecessária e, potencialmente, uso menor de memória quando utilizando coleções de dados. Além disso, é possível definir e trabalhar com estruturas de dados infinita e fluxo contínuo de dados:
let lista = [1..] -- lista infinita
take 10 lista -- avalia os 10 primeiros elementos da lista
Essas características são importantes no contexto de Big Data pois desejamos criar algoritmos capazes de tratar fluxos de dados sem que tenhamos que alocá-los em memória (ou totalmente). Outro ponto é na distribuição dos dados, uma vez que não temos variáveis mutáveis e estado, se torna mais simples paralelizar os algoritmos criados.
Esse mini-curso tem o objetivo de introduzir a linguagem Haskell para aqueles que nunca programaram com foco nos componentes interessantes para o curso de Big Data.