Currying de funções em Haskell

Currying de função é uma técnica de avaliação parcial dos argumentos de uma função. O nome da técnica é em homenagem a Haskell Curry, que segundo a Wikipedia re-inventou o método (Moses Schönfinkel já havia desenvolvido o método anteriormente). O curioso é que esse matemático também deu nome à linguagem Haskell.

Nesse post vou falar um pouco sobre currying de funções em Haskell, baseando-me principalmente no capítulo 4 do livro “Real World Haskell“.

Funções com múltiplos argumentos em Haskell

A sintaxe para especificarmos o tipo de uma função em Haskell com múltiplos argumentos parece meio confusa no começo. Por exemplo, se quisermos definir uma função que soma dois inteiros, devemos especificá-la da seguinte forma.

 
soma:: Int -> Int -> Int
soma a b = a + b

A partir do cabeçalho não dá a impressão de que a função recebe dois inteiros como argumento e retorna um inteiro. Isso porque as funções em Haskell recebem na verdade apenas um argumento. Quando há múltiplos argumentos, chamamos a função ‘f’ com o primeiro argumento e retornamos uma função ‘g’ com as ocorrências desse primeiro argumento substituídas pelo valor passado como argumento.

Por exemplo, considere uma função f(a, b, c) sobre três inteiros a, b e c. A primeira chamada da função f(1,2,3) retorna uma nova função g(b, c) = f(1, b, c), que por sua vez retorna uma outra função h(c) = g(2, c), que recebe apenas um argumento.

Para testar isso na prática, podemos fazer no terminal (ghci):

> :type soma
soma :: Int -> Int -> Int
> let somaTres = soma 3
> :type somaTres
soma3 :: Int -> Int
> somaTres 7
10 

No exemplo acima, o parâmetro a de soma, foi substituído por 3 e a função resultante atribuída a somaTres, que agora só recebe um argumento.

Em uma função com vários parâmetros, podemos trabalhar com versões intermediárias do processo de currying provendo apenas alguns dos parâmetros. Por exemplo,

-- Função que retorna uma tripla a partir de 3 argumentos
foo a b c = (a, b, c)
foo1 = foo 3
foo2 = foo 4 5
foo3 = foo 6 7 8

Nesse caso, foo1 recebe dois argumentos, foo2 apenas um e foo3 já é o resultado da função foo.

Omissão de parâmetros usando currying

Suponha que queiramos selecionar os números primos de uma lista de entrada. Uma versão bem simples, onde tentamos encontrar um divisor de p indo de 2 até \sqrt{p}, pode ser dada por:

ehPrimo p | p < 2 = False
          | otherwise = ehPrimoAux p 2 
                      
ehPrimoAux p q | q*q > p        = True
               | p `mod` q == 0 = False 
               | otherwise      = ehPrimoAux p (q+1)

Na biblioteca padrão do Haskell, existe a função filter, que recebe um array e uma função que recebe um elemento desse array e retorna verdadeiro ou falso. Podemos passar nossa função ehPrimo para gerar uma lista com os elementos primos de 1 a 50 por exemplo.

> :type filter
filter :: (a -> Bool) -> [a] -> [a]
> filter ehPrimo [1..50]
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47]

Para facilitar, podemos criar uma função que recebe um array e retorna a lista de primos:

listaPrimos xs = filter ehPrimo xs

Utilizando currying, é possível definir a função acima sem o parâmetro

listaPrimos = filter ehPrimo

No primeiro caso, estamos definindo uma função que chama filter ehPrimo xs. No segundo, estamos apenas associando o resultado da avaliação parcial da função filter com seu primeiro argumento, no caso o ehPrimo.

Currying sobre o segundo parâmetro

Por padrão, a substituição dos argumentos é feita a partir do primeiro argumento, ou seja, da esquerda para a direita. Entretanto, para funções com dois parâmetros (binárias), podemos usar a notação infixa para fazer a substituição do segundo argumento.

A notação infixa consiste em chamar a função entre acentos graves (backtick). Por exemplo, a função soma poderia ser chamada assim:

> 1 `soma` 2
3

Essa sintaxe é interessante para fins de legibilidade de código. Ao chamar a função na forma infixa passando somente o segundo argumento, a substituição é feita somente para ele. Como um exemplo, vamos definir a função potência que recebe dois inteiros a e b e eleva a a b (a função soma não tem graça porque ela é comutativa).

> let potencia a b = a ^ b

Agora, podemos fazer currying e aplicar a função a um argumento apenas

-- Faz a substituição do parâmetro b
> let quadrado = (`potencia` 2)
> quadrado 9
81

Currying em C++

A STL do C++ provê algumas ferramentas para trabalhar com o paradigma funcional, através da biblioteca functional. Através dela podemos reproduzir o nosso primeiro exemplo em Haskell:

#include <functional>
#include <iostream>
using namespace std;

struct adder : public binary_function<int, int, int> {
    int operator() (int a, int b) const {
        return a + b;
    }
};
// currying sobre o primeiro argumento da funcao 
int main (){

    adder soma;
    binder1st<adder> somaTres = bind1st(soma, 3);  
    cout << somaTres(7) << endl; // 10
    cout << somaTres(-1) << endl; // 2
}

Nesse caso trabalhamos com um functor, que é um objeto representando uma função. A classe adder deve derivar da classe binary_function. A função bind1st faz um bind do primeiro argumento da função, e retorna um objeto do tipo binder1st, que deriva da classe unary_function e que recebe um argumento.

É possível fazer o bind do segundo argumento através da função bind2nd.

Os comentários estão fechados.

%d bloggers like this: