Haskell/Lambdas e operadores


Este módulo encontra-se em processo de tradução. A sua ajuda é bem vinda.

Em Haskell existe um tipo de expressão extremamente importante chamada expressões lambdas, comumente chamadas de funções anônimas, as quais vamos introduzir neste módulo. Além disso, aprenderemos um pouco sobre operadores e notação infixa.

Expressões lambdas

editar

Relembrando os exemplos do capítulo Listas II, suponha que queiramos dobrar todos os elementos de uma lista. Uma possível função para este trabalho seria:

dobrarLista ls = map dobro ls
    where dobro x = 2 * x

Ou ainda:

dobrarLista ls = let dobro x = 2 * x
                 in map dobro ls

Outra solução, porém, seria usar uma expressão lambda para representar a função auxilar dobro sem precisar usar let ou where:

dobrarLista ls = map (\x -> 2 * x) ls

Antes de entendermos a notação, precisamos fazer uma breve correlação entre Matemática e Computação.

Na Matemática, a função que dobra seu argumento pode ser representada por  . Com isso, ela passa a se chamar " ", sendo que poderia ter sido qualquer outro nome, como " " ou " ".

Já nas Ciências da Computação existe um área de estudo chamada Cálculo lambda. É a partir dela que muitas linguagens de programação funcional são desenvolvidas, inclusive Haskell. O Cálculo lambda, por sua vez, não exige que as funções tenham nomes, e por isso elas podem ser chamadas de funções anônimas. Toda função é denotada por   seguido das variáveis de entrada, um sinal de gráfico e a expressão matemática da função. A função   pode se escrita como:

  1.  , ou;
  2.  .

A notação usada em Haskell se assemelha com a primeira opção:

  1. \ é usada para representar o  ;
  2. -> substitui a seta  .

A aplicação de funções anônimas também é semelhante em Haskell e no Cálculo lambda:

  • No Cálculo lambda,   resulta em  .
  • Em Haskell, (\x -> 2 * x) 7 resulta em 14.

Portanto, expressões lambdas funcionam como qualquer outras funções, mas, por não possuirem um nome, não podemos chamá-las em diferentes lugares do código. Isso as torna "descartáveis", o que é bastante útil quando precisamos usar certa função apenas uma vez como em map ou alguma fold, por exemplo.

Há um jeito, porém, de dar nomes às funções anônimas. Basta declarar uma variável que seja a função anônima. Por exemplo:

pitagoras = \x y -> sqrt (x^2 + y^2)

Acabamos de criar a função pitagoras a partir de uma expressão lambda que possui dois argumentos. Podemos testar no GHCi:

Prelude> let pitagoras = \x y -> sqrt (x^2 + y^2)
Prelude> pitagoras 3 4
5.0
Exercícios

Reescreva as seguintes expressões usando lambdas:

  • map f xs where f x = x * 2 + 3
  • let f x y = read x + y in foldr f 1 xs

Operadores

editar

Em Haskell, um operador é qualquer função de dois argumentos que possuir um nome que consista apenas de caracteres não-alfanuméricos. Os operadores mais comuns são os aritméticos, como a adição, (+), a subtração, (-). Diferente de outras funções, que usam a notação de prefixos, operadores são infixos, isto é, são escritos entre os argumento, como 2 + 3 e 5 * 7. Mesmo assim, eles ainda podem ser usados com a notação de prefixos, bastando para isso envolvê-los entre parênteses:

Prelude> (+) 1 2
3
Prelude> (^) 2 10
1024

Novos operadores podem ser definidos como quaisquer outras funções, basta não usar caracteres alfanuméricos em seus nomes e colocando parênteses na assinatura de tipo. Por exemplo, vejamos a definição da função (\\) do módulo Data.List, a qual elimina os elementos iguais de duas listas:

(\\) :: (Eq a) => [a] -> [a] -> [a]
xs \\ ys = foldl (\zs y -> delete y zs) xs ys

A segunda linha também poderia ser escrita de forma infixa, mas é opcional:

(\\) xs ys = foldl (\zs y -> delete y zs) xs ys

Seções

editar

Seções são um tipo de açúcar sintático que permite criamos novas funções a partir da aplicação parcial de operadores. Por exemplo, a função dobro que vimos antes pode ser definida como:

dobro x = (2*) x

Ou ainda, usando a notação de ponto livre:

dobro = (2*)

Assim sendo, podemos escrever a função dobrarLista de maneira ainda mais concisa, sem usar expressões lambda:

dobrarLista ls = map (*2) ls

Deve-se notar, entretanto, que para a maioria dos operadores, a ordem do argumento fixo importa:

Prelude> let f = (2^)
Prelude> let g = (^2)
Prelude> f 10
1024
Prelude> g 10
100
Prelude> let k = (!! 4)
Prelude> let h = ([10,20..] !!)
Prelude> k [10,20..]
50
Prelude> h 10
110

Se tentássemos definir (4 !!), por exemplo, teríamos um erro dizendo que o esperado é uma lista do lado esquerdo de (!!), e não um número.

Notação infixa com funções comuns

editar

Se você tiver uma função comum, que deve ser usada na forma de prefixo, é possível usá-la na forma infixa, como um operador. Basta cercá-la de acentos graves `.

Um exemplo bastante comum é a função elem, que retorna True se um valor estiver contido numa lista, ou False, caso contrário:

Prelude> 1 `elem` [10..100]
False
Prelude> 'h' `elem` "haskell"
True

Este truque geralmente é usado para melhorar a leitura do código, e só pode ser usado com funções de dois argumentos.

Exercício

  1. Seções são açúcar sintático para expressões lambdas. Reescreva as seguintes seções na forma de lambdas determine seus tipos:
    • (4+)
    • (1 `elem`)
    • (`notElem` "abc")
  2. Teste as seguintes linhas no GHCi:
norma3D  x y z = sqrt (x^2 + y^2 + z^2)
nomra3D' a b   = a `norma3D` b
Se a notação infixa só pode ser usada com funções de dois argumentos, por que a definição de norma3D' é válida? Dica: observe os tipos de cada uma das funções e lembre-se de currying.