Upload
phamque
View
284
Download
0
Embed Size (px)
Citation preview
Funções
Relembrando um dos objetivos do paradigma de programação funcional: Escrever funções “puras” Na prática, uma função pura apresenta as seguintes
características: Possui um ou mais parâmetros; Realiza cálculos utilizando apenas os parâmetros recebidos; Retorna um valor; Sempre retorna o mesmo valor para os mesmos parâmetros
de entrada; Não utiliza ou modifica dados fora da função (efeito
colateral); Não é afetada por alterações de dados que ocorrem fora da
função (idem).
Funções
Funções “puras”
Equivalente às funções definidas na matemática.
São funções sem estado (stateless).
Facilitam a execução de programas concorrentes.
Entretanto,...
É difícil programar funções que não alteram estado.
Precisamos ler arquivos, consultar bancos de dados, imprimir o resultado na console, etc.
Funções
Em Scala, funções possuem equivalência com a definição de um método.
A definição pode ocorrer em qualquer parte do código.
Sintaxe geral:
def <nome>(<parâmetro>:<tipo>[,...]):<tipo>=<expressão>
Funções
Exemplos de definições de funções
O corpo da função é composto por uma ou mais expressões.
A última linha executada denota o retorno da função.
def multiplica(x: Int, y: Int): Int = { x * y }
…
var m = multiplica(5,10) //retorna 50
Funções
Exemplos de definições de funções
É opcional a declaração do tipo de retorno da função (inferência de tipos)
def max(x: Int, y: Int): Int = {
if (x > y) x
else y
}
OU
// se a função possui apenas um comando
def max2(x: Int, y: Int) = if (x > y) x else y
Funções
Observação importante
Na definição de funções recursivas é obrigatória a definição do tipo de função.
Exemplo:
def potencia(x: Int, n: Int): Long = {
if (n >= 1) x * potencia(x, n-1)
else 1
}
Funções
Funções podem ser aninhadas
Exemplo
def E(x: Float) = {
def F(y: Float) = x + y
F(2*x)
}
...
println(E(5))
> 15.0
Funções
Alguns cuidados com funções aninhadas O parâmetro y não é visível no corpo da função E.
Visível apenas dentro de F!
def E(x: Float) = {
def F(y: Float) = x + y
F(2*x)
}
Funções
Alguns cuidados com funções aninhadas
O que ocorre quando temos conflito de nomes em diferentes escopos?
var a: Int = 1;
def P = {
var b = 1;
def Q = {
var b = " b "; var c = 4;
println(a+b+c)
}
Q
println(a+b)
}
P
Funções
Alguns cuidados com funções aninhadas
Declaração de funções aninhadas com o mesmo nome
def max(a: Int, b: Int, c: Int) = {
def max(x: Int, y: Int) = {
if (x > y) x
else y
}
max(a, max(b, c))
}
...
var m = max(42, 181, 19) // retorna 181
Funções
Procedures
Funções que não possuem um valor de retorno.
A última linha executada é um comando e não uma expressão.
Retorna o tipo Unit.
Exemplos:
def imprimeSoma(x:Int, y:Int) = println(“Soma=“+(x+y))
...
// Mais de uma linha , obrigatório declarar Unit
def imprimeSoma2(x:Int, y:Int):Unit = {
println(“Primeira linha”)
println(“Soma=“+(x+y))
}
Exercício em Laboratório
P1 – 2013.2
5ª Questão (2,0 pontos): Dada a seguinte função A(m,n):
válida para valores inteiros não negativos de m e n, implemente uma versão recursiva da função em Scala e mostre o resultado da execução de A(1, 2).
Exercício 11
Reescrever os exercícios 4 e 5, usando funções.
Criar obrigatoriamente as versões recursivasdos problemas propostos.
Exercício 12
Escreva uma função recursiva em Scala que imprime os valores de 5 a 50, sem utilizar loops.
Funções
Mecanismos de definição de funções
Em linguagens funcionais, existem normalmente dois mecanismos para definição de funções.
O mecanismo tradicional (visto anteriormente)
Funções anônimas (derivadas do cálculo Lambda),
Funções
Mecanismos de definição de funções
Definição formal:
Seja λx.E uma expressão onde x é um identificador e Euma expressão que pode conter x (por exemplo, E -> x+1).
λ é uma abstração que define uma função anônima, ou seja, a função pode ser aplicada à expressão F, denotada como (λx.E)F
Como ocorre? Cada ocorrência de x em E é substituída por F e as operações restantes são realizadas.
Funções
Exemplo de função anônima com Lambda
Calcular o lambda (λx.3 · x +1)4
λx é mapeado como F(4)
O E da função é a expressão 3.4+1.
O valor final do lambda é 13
Funções
Definindo funções anônimas em Scala Outro nome para funções anônimas: funções literais
Exemplo: Função para incrementar o valor de uma variável var inc = (x:Int) => x+1
Note que não usamos a palavra reservada def, assumindo que inc seja uma variável.
x é equivalente ao identificador na expressão lambda. O operador ”=>” faz o papel do ponto na expressão. A expressão após ”=>” será avaliada sempre que um valor de função for aplicado.
Funções
Funções literais com mais de um argumento
Exemplo: Função que multiplica dois argumentos
var mul = (x: Int) => (y:Int) => x*y
println(mul(3)(4))
> 12
mul é chamada também de função de alta ordem.
Funções
Construção de funções de alta ordem em Scala Considere a função mul do slide anterior.
mul pode receber apenas um argumento n e retorna uma função, a qual multiplica o valor por n.
Exemplo:
var mul = (x: Int) => (y:Int) => x*y
var mulNovo = mul(3)
println(mulNovo(4))
> 12
Exercício 14
Escreva uma função anônima que calcula o máximo de três números inteiros.
Atenção: evite código imperativo para o problema.
Pense em uma solução funcional!
Funções Anônimas
Relembrando as características de funções
Funções podem ser aninhadas dentro de outras funções;
Funções podem ser atribuídas a uma variável;
Funções podem ser passadas como parâmetros de outras funções.
Passando funções como parâmetros
var quadrado = (x:Int) => x*x
var somaInteiroQuadrado = (x:Int) => (quadrado: Int => Int) => x + quadrado(x)
println(somaInteiroQuadrado(10)(quadrado))
...
>110
A função somaInteiroQuadrado possui dois parâmetros: um inteiro e uma função
Passando funções como parâmetros
var quadrado = (x:Int) => x*x
var somaInteiroQuadrado = (x:Int) => (quadrado: Int => Int) => x + quadrado(x)
var f = somaInteiroQuadrado(10)
println(f(quadrado))
...
>110
O retorno da função parcial é outra função!
Funções como parâmetros "just-in-time"
var f1 = (x:Int) => x*x
var f2 = (x:Int) => x+1
var funcaoAnonima = (x:Int) => (y: Int => Int) => x + y(x)
println(funcaoAnonima(10)(f1))
println(funcaoAnonima(10)(f2))
...>110>21
y é um parâmetro que representa uma função anônimaqualquer. funcaoAnonima possui um comportamento quepode mudar de acordo com os seus parâmetros!
Exercício em Laboratório
P1 – 2015.1
3ª Questão (2,0 pontos): A função somaInt, listada abaixo, calcula a soma dos inteiros entre a e b. A função
quadrado, também listada abaixo, retorna o quadrado de um número inteiro a. Crie uma função anônima em
Scala que calcula a soma dos quadrados dos inteiros entre a e b. Você deverá, obrigatoriamente, utilizar as funções
somaInt e quadrado na sua resposta.
def somaInt(a: Int, b: Int): Int = if (a > b) 0 else a + somaInt(a + 1, b) def quadrado(a: Int) = a * a
Currificação de Funções
Em Scala, podemos usar o conceito de currificaçãode funções
Transforma uma função que recebe mais de um parâmetro em uma função que recebe uma lista de parâmetros.
Currificação de Funções
Exemplo: f(a: Int, b: Int, c: Int) { … }
Invocando a função: f(a,b,c)
Currificando f:
f(a: Int)(b: Int)(c: Int) { … }
Invocando a função: f(a)(b)(c)
Vantagens Legibilidade do código
Possibilidade de usar funções parciais (passar apenasparte dos parâmetros)
Currificação de Funções Exemplo:// soma simples
def soma(x: Int, y: Int) = x + y
// com currificação
def somaCurrificada(x: Int)(y: Int) = x + y
println(soma(1, 2))
println(somaCurrificada(1)(2))
...
>3
>3
Currificação de Funções Exemplo:// Usando funções parciais
def somaCurrificada(x: Int)(y: Int) = x + y
val incrementa = somaCurrificada (1)_
var x = incrementa(2)
...
>3
Função parcial
Aplicação Parcial de Funções Outro Exemplo:
// n é divisível por m?
def nDivideM(m: Int)(n: Int) = (n%m==0)
val ehPar = nDivideM(2)_
println(ehPar(4))
println(ehPar(5))
...
>true
>false
Aplicação Parcial de Funções Mais um exemplo:
def ehMultiplo(x: Int, y: Int) = y % x == 0
val f = ehMultiplo _
val x = f(7, 20)
> x: Boolean = false
// Aplicando funções parciais com alta ordem
val ehMultiploDe3 = ehMultiplo (3, _: Int)
val y = ehMultiploDe3(78)
> y: Boolean = true
Recursão de calda
Um tipo especial de recursão é chamado de tail recursion (recursão de calda).
Ocorre quando uma função chama ela mesma na última operação do corpo da função, sem expressões adicionais.
Os compiladores conseguem otimizar as chamadas de funções com recursão em calda
Uso da anotação @tailrec
Recursão de calda
Exemplo prático
Fatorial recursivo (sem tail recursion)def factorial(i: BigInt): BigInt = {
if (i == 1) i
else i * factorial(i - 1)
}
A última operação é uma multiplicação. Primeiro, o compiladorchama factorial e depois realiza a multiplicação. Logo, nãopodemos aplicar a recursão de calda.
Recursão de calda
Alterando o código para usar recursão de calda
import scala.annotation.tailrec
def factorial(i: BigInt): BigInt = {
@tailrec
def fact(i: BigInt, accumulator: BigInt): BigInt =
if (i == 1) accumulator
else fact(i - 1, i * accumulator)
fact(i, 1)
}
Agora, a última operação da função fact é somente achamada recursiva.
Recursão de calda
Outro exemplo
Cálculo recursivo da potência
def potencia(x: Int, n: Int): Long = {
if (n >= 1) x * potencia(x, n-1)
else 1
}
A última operação é uma multiplicação. Não podemos aplicar arecursão de calda.
Recursão de calda
Solução com recursão de caldaimport scala.annotation.tailrec
@tailrec
def potencia(x: Int, n: Int, t: Int = 1): Int = {
if (n < 1) t
else potencia(x, n-1, x*t)
}
Agora, a última operação da função potencia é somente achamada recursiva.
Exercício 15
Escreva uma função de alta ordem que recebe um inteiro (por exemplo, y:Int) e retorna uma função. A função retornada deverá receber um inteiro (porexemplo, x:Int) e retornar a multiplicação de x e y.