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 | editar código-fonte]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:
- , ou;
- .
A notação usada em Haskell se assemelha com a primeira opção:
\
é usada para representar o ;->
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 em14
.
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:
|
Operadores
[editar | editar código-fonte]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 | editar código-fonte]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 | editar código-fonte]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
- 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")
- 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.