alto contraste

Propaganda
Lazy Evaluation (Avaliação Preguiçosa)
Leonardo Lucena – IFRN, 2011
Adaptação das Transparências de
Graham Hutton
(http://www.cs.nott.ac.uk/~gmh/book.html)
These slides may be used or modified for any educational purpose on a
non-profit-making basis, provided that I am acknowledged as the original
author.
 Até
agora não vimos em detalhes como
expressões de Scala são avaliadas
 De
fato, a avaliação pode ser feita de
duas formas
• Avalição precoce (como em Java)
• Avaliação preguiçosa
1
A
avaliação preguiçosa é uma técnica de
avalição simples que entre outras coisas:
• Evita a avaliação desnecessária
• Permite programas mais modulares
• Permite escrever listas infinitas
 Linguagens
funcionais com avaliação
preguiçosa são chamadas de linguagens
funcionais preguiçosas
2
 Expressões
são avaliadas, ou reduzidas,
sucessivamente aplicando definições até
que nenhuma outra simplificação for
possível.
 Por exemplo
def quadrado(n: int) = n*n
3
A
expressão quadrado(3+4) pode ser
avaliada usando a seguinte sequência de
reduções
=
=
=
quadrado (3+4)
quadrado(7)
7*7
49
4
 Outra
=
=
=
forma de redução é a seguinte:
quadrado(3 + 4)
(3 + 4) * (3 + 4)
7 * (3 + 4)  Desta
7 * 7
=
49
vez aplicamos
quadrado antes de
efetuar a adição, mas
o resultado é o
mesmo
5
A
cada passo durante a avaliação de uma
expressão podemos ter várias possíveis
sub-expressões que podem ser
reduzidas.
 Há duas estratégias para decidir qual
sub-expressão reduzível escolher:
• Redução mais interna
 Uma sub-expressão mais interna
• Redução mais externa
 Uma sub-expressão mais externa
 Como
comparar as duas estratégias?
6
 Seja
a seguinte definição
def loop: List[Int] = loop.tail
 Vamos
avaliar a expressão
primeiro(1, loop)
usando as duas estratégias de redução.
7
1.
Redução mais Interna
=
=
=
primeiro(1, loop)
primeiro(1, loop.tail)
primeiro(1, (loop.tail).tail)
primeiro(1, ((loop.tail).tail).tail)
=
…
A estratégia não termina
8
2.
Redução mais externa
=
primeiro(1, loop)
1
A estratégia dá o resultado em apenas
um passo
9
A
redução mais externa pode dar um
resultado nos casos em que a redução
mais interna falha em terminar;
 Para
uma dada expressão, se existir
alguma sequência de reduções que
termina, então a redução mais externa
também termina e com o mesmo
resultado.
10
MAIS INTERNO
MAIS EXTERNO
=
quadrado(3 + 4)
=
quadrado(7)
=
(3 + 4) * (3 + 4)
=
7 * 7
=
7 * (3 + 4)
49
= 7 * 7
=
quadrado(3 + 4)
49

A versão mais externa é
ineficiente, a expressão
3+4 é avaliada duas vezes

Fato: Redução mais externa
pode exigir mais passos
que a redução mais interna
11
O
problema pode ser resolvido usando
ponteiros para indicar compartilhamento
de expressões durante a avaliação:
=
=
=
quadrado(3 + 4)
( • * • )
(3 + 4)
7 * 7
49
12
 Assim, temos
uma nova estratégia de
redução
• Avaliação Preguiçosa =
Redução mais externa + Compartilhamento
 Fatos
• Avaliação preguiçosa nunca requer mais passos
de redução do que a redução mais interna.
• Scala permite usar avaliação preguiçosa
13
 Avaliação
precoce (mais interna)
def quadrado(n: Int) = n*n
 Avaliação
Passa o valor
por nome
preguiçosa
def quadrado(n: => Int) = {
lazy val x = n
x * x
}
Só avalia n quando
for necessário
14
 Só
avalia o argumento a
def primeiro[T](a: => T, b: =>T) = a
 Se
... Então .. Se não ...
def se[T](b: Boolean, entao: => T, senao: => T) = {
if (b) entao else senao
}
> se(true, println(“OK”), loop);
OK
> se(false, println(“OK”), loop);
Exception in thread "main" java.lang.StackOverflowError
15
 Além
das vantagens de terminação, o uso
de avaliação preguiçosa permite
programar com listas infinitas de valores
def uns: List[Int] = 1 :: uns
> Uns
1 :: 1 :: 1 :: 1 :: ...
 Scala
usa Stream para listas preguiçosas
def uns:Stream[Int]=Stream.cons(1,uns)
16
 Redução
interna
uns.head =
=
=
=
 Avaliação
(1::uns).head
(1::1::uns).head
(1::1::1::uns).head
...
Preguiçosa
uns.head = (1::uns).head
= 1
17
 Em
geral:
• Usando avaliação preguiçosa, expressões só são
avaliadas à medida que são exigidas para
produzir o resultado final
> def uns:Stream[Int]=Stream.cons(1,uns)
> println(uns)
Stream(1, ?)
> println(uns.take(5).sum)
5
> println(uns.sum)
java.lang.OutOfMemoryError: Java heap space
18
 Podemos
gerar listas finitas pegando
elementos de listas infinitas.
> uns.take(5)
List(1,1,1,1,1)
 Avaliação
Preguiçosa permite criar
programas mais modulares separando os
controle dos dados
dados
uns.take(5)
controle
19
 Um
procedimento simples para gerar a
lista infinita de todos os números primos
é:
1. Escreva a lista 2, 3, 4, ... ;
2. Marque o primeiro valor p na lista de primos;
3. Apague todos os múltiplos de p da lista;
4. 2Retorne
3 4 ao5passo
6 2.
7 8
3
5
7
5
7
7
9 10 11 12 ...
9
11
...
11
...
11
...
11
...
20
 Este
procedimento é conhecido como
Crivo de Aristóteles.
object Primos extends App {
1
def primos = crivo(Stream from 2)
def crivo(a:Stream[Int]):Stream[Int]={
val (p, xs) = (a.head, a.tail)
val ys = xs.filter( _ % p != 0) 3
Stream.cons(p,2 crivo(ys)) 4
}
println(primos.take(5).force)
}
21
 Liberando
a geração de primos da
restrição de finitude, obtemos uma
definição modular na qual diferentes
condições de contorno podem ser
impostas em diferentes situações.
• Seleção dos primeiros primos
primos.take(10)
• Seleção do primos menores do que 15
primos.takeWhile(_ < 15)
22
1.
Defina um programa contendo a função
def fibs: Stream[Int] = …
que gera a série infinita de Finonacci
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …
usando o seguinte procedimento:
a. os dois primeiros números são 0 e 1;
b. o próximo é a soma dos dois anteriores;
c. retorne para o passo b.
2.
Defina a função
def fib(n: Int): Int = …
que calcula o n-ésimo número de Fibonacci.
23
Download