| Inicial | Temas | Manuales | Ejercicios | Exámenes | Documentación

Tema 10: Evaluación perezosa

Índice

1. Estrategias de evaluación

1.1. Estrategias de evaluación

  • Para los ejemplos se considera la función

    mult :: (Int,Int) -> Int
    mult (x,y) = x*y
    
  • Evaluación mediante paso de parámetros por valor (o por más internos):

    mult (1+2,2+3)
    = mult (3,5)        [por def. de +]
    = 3*5               [por def. de mult]
    = 15                [por def. de *]
    
  • Evaluación mediante paso de parámetros por nombre (o por más externos):

    mult (1+2,2+3)
    = (1+2)*(3+5)       [por def. de mult]
    = 3*5               [por def. de +]
    = 15                [por def. de *]
    

1.2. Evaluación con lambda expresiones

  • Se considera la función

    mult' :: Int -> Int -> Int
    mult' x = \y -> x*y
    
  • Evaluación:

    mult' (1+2) (2+3)
    = mult' 3 (2+3)      [por def. de +]
    = (\y -> 3*y) (2+3)  [por def. de mult']
    = (\y -> 3*y) 5      [por def. de +]
    = 3*5                [por def. de +]
    = 15                 [por def. de *]
    

2. Terminación

2.1. Procesamiento con el infinito

  • Definición de infinito

    inf :: Int
    inf = 1 + inf
    
  • Evaluación de infinito en Haskell:

    λ> inf
      C-c C-c Interrupted.
    
  • Evaluación de infinito:

    inf
    = 1 + inf             [por def. inf]
    = 1 + (1 + inf)       [por def. inf]
    = 1 + (1 + (1 + inf)) [por def. inf]
    = ...
    
  • Evaluación mediante paso de parámetros por valor:

    fst (0,inf)
    = fst (0,1 + inf)             [por def. inf]
    = fst (0,1 + (1 + inf))       [por def. inf]
    = fst (0,1 + (1 + (1 + inf))) [por def. inf]
    = ...
    
  • Evaluación mediante paso de parámetros por nombre:

    fst (0,inf)
    = 0           [por def. fst]
    
  • Evaluación Haskell con infinito:

    λ> fst (0,inf)
    0
    

3. Número de reducciones

3.1. Número de reducciones según las estrategias

  • Para los ejemplos se considera la función

    cuadrado :: Int -> Int
    cuadrado n = n * n
    
  • Evaluación mediante paso de parámetros por valor:

    cuadrado (1+2)
    = cuadrado 3      [por def. +]
    = 3*3             [por def. cuadrado]
    = 9               [por def. de *]
    
  • Evaluación mediante paso de parámetros por nombre:

    cuadrado (1+2)
    = (1+2)*(1+2)        [por def. cuadrado]
    = 3*(1+2)            [por def. de +]
    = 3*3                [por def. de +]
    = 9                  [por def. de *]
    

3.2. Evaluación perezosa e impaciente

  • En la evaluación mediante paso de parámetros por nombre los argumentos pueden evaluarse más veces que en el paso por valor.
  • Se puede usar punteros para compartir valores de expresiones.
  • La evaluación mediante paso de parámetros por nombre usando punteros para compartir valores de expresiones se llama evaluación perezosa.
  • La evaluación mediante paso de parámetros por valor se llama evaluación impaciente.
  • Evaluación perezosa del ejemplo anterior:

    cuadrado (1+2)
    = x*x con x = 1+2      [por def. cuadrado]
    = 3*3                  [por def. de +]
    = 9                    [por def. de *]
    
  • Haskell usa evaluación perezosa.

4. Estructuras infinitas

4.1. Programación con estructuras infinitas

  • unos es una lista infinita de unos.

    unos :: [Int]
    unos = 1 : unos
    
  • Evaluación:

    unos
    = 1 : unos                [por def. unos]
    = 1 : (1 : unos)          [por def. unos]
    = 1 : (1 : (1 : unos))    [por def. unos]
    = ...
    
  • Evaluación en Haskell:

    λ> unos
    [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,...
    

4.2. Evaluación con estructuras infinitas

  • Evaluación impaciente:

    head unos
    = head (1 : unos)                [por def. unos]
    = head (1 : (1 : unos))          [por def. unos]
    = head (1 : (1 : (1 : unos)))    [por def. unos]
    = ...
    
  • Evaluación perezosa:

    head unos
    = head (1 : unos)   [por def. unos]
    = 1                 [por def. head]
    
  • Evaluación Haskell:

    λ> head unos
    1
    

5. Programación modular

5.1. Programación modular

  • La evaluación perezosa permite separar el control de los datos.
  • Para los ejemplos se considera la función

    take :: Int -> [a] -> [a]
    take n _  | n <= 0  = []
    take _ []           = []
    take n (x:xs)       = x : take (n-1) xs
    
  • Ejemplo de separación del control (tomar 2 elementos) de los datos (una lista infinita de unos):

    take 2 unos
    = take 2 (1 : unos)         [por def. unos]
    = 1 : (take 1 unos)         [por def. take]
    = 1 : (take 1 (1 : unos))   [por def. unos]
    = 1 : (1 : (take 0 unos))   [por def. take]
    = 1 : (1 : [])              [por def. take]
    = [1,1]                     [por notación de listas]
    

5.2. Terminación de evaluaciones con estructuras infinitas

  • Ejemplo de no terminación:

    λ> [1..]
    [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,...
    
  • Ejemplo de terminación:

    λ> take 3 [1..]
    [1,2,3]
    
  • Ejemplo de no terminación:

    λ> filter (<=3) [1..]
    [1,2,3  C-c C-c Interrupted.
    
  • Ejemplo de no terminación:

    λ> takeWhile (<=3) [1..]
    [1,2,3]
    

5.3. La criba de Erastótenes

  • Ejemplo con la criba de Erastótenes

    2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
       3     5     7     9      11      13      15  ...
             5     7            11      13          ...
                   7            11      13          ...
                                11      13          ...
                                        13          ...
    
  • Definición

    primos :: [Int ]
    primos = criba [2..]
    
    criba :: [Int] -> [Int]
    criba (p:xs) = p : criba [x | x <- xs, x `mod` p /= 0]
    
  • Evaluación:

    take 15 primos == [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47]
    
  • Cálculo:

    primos
    = criba [2..]
    = criba (2 : [3..])
    = 2 : (criba [x | x <- [3..], x `mod` 2 /= 0])
    = 2 : (criba (3 : [x | x <- [4..], x `mod` 2 /= 0]))
    = 2 : 3 : (criba [x | x <- [4..], x `mod` 2 /= 0,
                                      x `mod` 3 /= 0])
    = 2 : 3 : (criba (5 : [x | x <- [6..], x `mod` 2 /= 0,
                                           x `mod` 3 /= 0]))
    = 2 : 3 : 5 : (criba ([x | x <- [6..], x `mod` 2 /= 0,
                                           x `mod` 3 /= 0,
                                           x `mod` 5 /= 0]))
    = ...
    

6. Aplicación estricta

6.1. Ejemplo de programa sin aplicación estricta

  • (sumaNE xs) es la suma de los números de xs. Por ejemplo,

    sumaNE [2,3,5]  ==  10
    

    La definición es

    sumaNE :: [Int] -> Int
    sumaNE xs = sumaNE' 0 xs
    
    sumaNE' :: Int -> [Int] -> Int
    sumaNE' v []     = v
    sumaNE' v (x:xs) = sumaNE' (v+x) xs
    
  • Evaluación:

    sumaNE [2,3,5]
    = sumaNE' 0 [2,3,5]           [por def. sumaNE]
    = sumaNE' (0+2) [3,5]         [por def. sumaNE']
    = sumaNE' ((0+2)+3) [5]       [por def. sumaNE']
    = sumaNE' (((0+2)+3)+5) []    [por def. sumaNE']
    = ((0+2)+3)+5                 [por def. sumaNE']
    = (2+3)+5                     [por def. +]
    = 5+5                         [por def. +]
    = 10                          [por def. +]
    

Ejemplo de programa con aplicación estricta

  • (sumaE xs) es la suma de los números de xs. Por ejemplo,

    sumaE [2,3,5]  ==  10
    

    La definición es

    sumaE :: [Int] -> Int
    sumaE xs = sumaE' 0 xs
    
    sumaE' :: Int -> [Int] -> Int
    sumaE' v []     = v
    sumaE' v (x:xs) = (sumaE' $! (v+x)) xs
    
  • Evaluación:

    sumaE [2,3,5]
    = sumaE' 0 [2,3,5]          [por def. sumaE]
    = (sumaE' $! (0+2)) [3,5]   [por def. sumaE']
    = sumaE' 2 [3,5]            [por aplicación de $!]
    = (sumaE' $! (2+3)) [5]     [por def. sumaE']
    = sumaE' 5 [5]              [por aplicación de $!]
    = (sumaE' $! (5+5)) []      [por def. sumaE']
    = sumaE' 10 []              [por aplicación de $!]
    = 10                        [por def. sumaE']
    

6.2. Comparación de consumo de memoria

  • La comparación es

    λ> sumaNE [1..1000000]
    *** Exception: stack overflow
    λ> sumaE [1..1000000]
    1784293664
    λ> :set +s
    λ> sumaE [1..1000000]
    1784293664
    (2.16 secs, 145435772 bytes)
    

6.3. Plegado estricto

  • Versión estricta de foldl en Data.List

    foldl' :: (a -> b -> a) -> a -> [b] -> a
    foldl' f a []     = a
    foldl' f a (x:xs) = (foldl' f $! f a x) xs
    
  • Comparación de plegado y plegado estricto:

    λ> foldl (+) 0 [2,3,5]
    10
    λ> foldl' (+) 0 [2,3,5]
    10
    λ> foldl (+) 0 [1..1000000]
    *** Exception: stack overflow
    λ> foldl' (+) 0 [1..1000000]
    500000500000
    

7. Material complementario

El código del tema se encuentra en este enlace.

Este tema también se encuentra en los siguientes formatos:

8. Bibliografía

  • R. Bird. Introducción a la programación funcional con Haskell. Prentice Hall, 2000.
    • Cap. Cap. 7: Eficiencia.
  • G. Hutton. Programming in Haskell. Cambridge University Press, 2007.
    • Cap. 12: Lazy evaluation.
  • B. O'Sullivan, D. Stewart y J. Goerzen. Real World Haskell. O'Reilly, 2008.
    • Cap. 2: Types and functions.
  • B.C. Ruiz, F. Gutiérrez, P. Guerrero y J.E. Gallardo. Razonando con Haskell. Thompson, 2004.
    • Cap. 2: Introducción a Haskell.
    • Cap. 8: Evaluación perezosa. Redes de procesos.
  • S. Thompson. Haskell: The Craft of Functional Programming, Second Edition. Addison-Wesley, 1999.
    • Cap. 17: Lazy programming.

| Inicial | Temas | Manuales | Ejercicios | Exámenes | Documentación

José A. Alonso Jiménez

Sevilla, 07 de abril del 2024

Licencia: Creative Commons.