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

Tema 8: Razonamiento sobre programas

Índice

1. Razonamiento ecuacional

1.1. Cálculo con longitud

  • Definición de longitud

    longitud []     = 0                 -- longitud.1
    longitud (_:xs) = 1 + longitud xs   -- longitud.2
    
  • Propiedad: longitud [2,3,1] = 3
  • Demostración:

    longitud [2,3,1]
    = 1 + longitud [2,3]          [por longitud.2]
    = 1 + (1 + longitud [3])      [por longitud.2]
    = 1 + (1 + (1 + longitud [])) [por longitud.2]
    = 1 + (1 + (1 + 0)            [por longitud.1]
    = 3
    

1.2. Propiedad de intercambia

  • Definición de intercambia:

    intercambia :: (a,b) -> (b,a)
    intercambia (x,y) = (y,x)       -- intercambia
    
  • Propiedad: intercambia (intercambia (x,y)) = (x,y)
  • Demostración:

    intercambia (intercambia (x,y))
    = intercambia (y,x)      [por intercambia]
    = (x,y)                  [por intercambia]
    
  • Propiedad en QuickCheck:

    prop_intercambia :: Eq a => a -> a -> Bool
    prop_intercambia x y = intercambia (intercambia (x,y)) == (x,y)
    
  • Comprobación:

    λ> quickCheck prop_intercambia
    +++ OK, passed 100 tests.
    
  • Nota: Para usar la librería QuickCheck hay que importarla escribiendo al principio del fichero

    import Test.QuickCheck
    

1.3. Inversa de listas unitarias

  • Inversa de una lista:

    inversa :: [a] -> [a]
    inversa []     = []                  -- inversa.1
    inversa (x:xs) = inversa xs ++ [x]   -- inversa.2
    
  • Propiedad: inversa [x] = [x]

    inversa [x]
    = inversa (x:[])      [notación de lista]
    = (inversa []) ++ [x] [inversa.2]
    = [] ++ [x]           [inversa.1]
    = [x]                 [def. de ++]
    
  • Propiedad en QuickCheck:

    prop_inversa_unitaria :: Eq a => a -> Bool
    prop_inversa_unitaria x = inversa [x] == [x]
    
  • Comprobación:

    λ> quickCheck prop_inversa_unitaria
    +++ OK, passed 100 tests.
    

1.4. Razonamiento ecuacional con análisis de casos

1.4.1. Casos sobre booleanos

  • Negación lógica:

    not :: Bool -> Bool
    not False = True
    not True  = False
    
  • Prop.: not (not x) = x
  • Demostración por casos:
    • Caso 1: x = True:

      not (not True)
      = not False      [not.2]
      = True           [not.1]
      
    • Caso 2: x = False:

      not (not False)
      = not True      [not.1]
      = False         [not.2]
      
  • Propiedad con QuickCheck:

    prop_doble_negacion :: Bool -> Bool
    prop_doble_negacion x = not (not x) == x
    
  • Comprobación:

    λ> quickCheck prop_doble_negacion
    +++ OK, passed 100 tests.
    

1.4.2. Casos sobre listas

  • Definiciones de null y (++):

    null :: [a] -> Bool
    null []           = True            -- null.1
    null (_:_)        = False           -- null.2
    
    (++) :: [a] -> [a] -> [a]
    []     ++ ys      = ys              -- (++).1
    (x:xs) ++ ys      = x : (xs ++ ys)  -- (++).2
    
  • Propiedad: null xs = null (xs ++ xs).
  • Demostración por casos según xs:
    • Caso 1: xs = []:

      null xs
      = null []          [por hipótesis]
      = null ([] ++ [])  [por (++).1]
      = null (xs ++ xs)  [por hipótesis]
      
    • Caso xs = y:ys: Reduciendo ambos lados:

      null xs
      = null (y:ys)              [por hipótesis]
      = False                    [por null.2
      = null (y:(ys ++ (y:ys))   [por null.2]
      = null ((y:ys) ++ (y:ys))  [por (++).2]
      = null (xs ++ xs)          [por hipótesis]
      

2. Razonamiento por inducción sobre los naturales

2.1. Esquema de inducción sobre los naturales

Para demostrar que todos los números naturales tienen una propiedad P basta probar:

  • Caso base n=0: P(0).
  • Caso inductivo n=(m+1): Suponiendo P(m) demostrar P(m+1).

En el caso inductivo, la propiedad P(n) se llama la hipótesis de inducción.

2.2. Ejemplo de inducción sobre los naturales

  • (replicate n x) es la lista formda por n elementos iguales a x. Por ejemplo,

    replicate 3 5  ==  [5,5,5]
    

    Su definición es

    replicate :: Int -> a -> [a]
    replicate 0 _ = []
    replicate n x = x : replicate (n-1) x
    
  • Prop.: length (replicate n x) = n
  • Demostración por inducción en n:
    • Caso base (n=0):

      length (replicate 0 x)
      = length []            [por replicate.1]
      = 0                    [por def. length]
      
    • Caso inductivo (n=m+1):

      length (replicate (m+1) x)
      = length (x:(replicate m x))  [por replicate.2]
      = 1 + length (replicate m x)  [por def. length]
      = 1 + m                       [por hip. ind.]
      = m + 1                       [por conmutativa de +]
      
  • Propiedad en QuickCheck:

    prop_length_replicate :: Int -> Int -> Bool
    prop_length_replicate n xs =
      length (replicate m xs) == m
      where m = abs n
    
  • Comprobación de la propiedad:

    λ> quickCheck prop_length_replicate
    OK, passed 100 tests.
    

3. Razonamiento por inducción sobre listas

3.1. Esquema de inducción sobre listas

Para demostrar que todas las listas finitas tienen una propiedad P basta probar:

  • Caso base xs=[]: P([]).
  • Caso inductivo xs=(y:ys): Suponiendo P(ys) demostrar P(y:ys).

En el caso inductivo, la propiedad P(ys) se llama la hipótesis de inducción.

3.2. Asociatividad de ++

  • Definición de ++:

    (++) :: [a] -> [a] -> [a]
    []     ++ ys = ys             -- ++.1
    (x:xs) ++ ys = x : (xs ++ ys) -- ++.2
    
  • Propiedad:

    xs ++ (ys ++ zs)=(xs ++ ys) ++ zs
    
  • Propiedad con QuickCheck:

    prop_asociativa_conc :: [Int] -> [Int] -> [Int] -> Bool
    prop_asociativa_conc xs ys zs =
        xs ++ (ys ++ zs)==(xs ++ ys) ++ zs
    
  • Comprobación:

    λ> quickCheck prop_asociatividad_conc
    OK, passed 100 tests.
    
  • Demostración por inducción en xs:
    • Caso base xs = []:

      xs ++ (ys ++ zs)
      = [] ++ (ys ++ zs)   [por hipótesis]
      = ys ++ zs           [por  ++ .1]
      = ([] ++ ys) ++ zs   [por  ++ .1]
      = (xs ++ ys) ++ zs   [por hipótesis]
      
    • Caso inductivo xs = a:as: Suponiendo la hipótesis de inducción

      as ++ (ys ++ zs) = (as ++ ys) ++ zs
      

      hay que demostrar que

      (a:as) ++ (ys ++ zs) = ((a:as) ++ ys) ++ zs
      

      La demostración es

      (a:as) ++ (ys ++ zs)
      = a:(as ++ (ys ++ zs))  [por  ++ .2]
      = a:((as ++ ys) ++ zs)  [por hip. ind.]
      = (a:(as ++ ys)) ++ zs  [por ++.2]
      = ((a:as) ++ ys) ++ zs  [por ++.2]
      

3.3. [] es la identidad para ++ por la derecha

  • Propiedad: xs ++ []=xs
  • Propiedad con QuickCheck:

    prop_identidad_concatenacion :: [Int] -> Bool
    prop_identidad_concatenacion xs = xs ++ [] == xs
    
  • Comprobación:

    λ> quickCheck prop_identidad_concatenacion
    OK, passed 100 tests.
    
  • Demostración por inducción en xs:
    • Caso base xs = []:

      xs ++ []
      = [] ++ []
      = []         [por ++.1]
      
    • Caso inductivo xs = a:as: Suponiendo la hipótesis de inducción

      as ++ [] = as
      

      hay que demostrar que

      (a:as) ++ [] = (a:as)
      

      La demostración es

      (a:as) ++ []
      = a:(as ++ [])   [por ++.2]
      = a:as           [por hip. ind.]
      

3.4. Relación entre length y ++

  • Definiciones de length y ++:

    length :: [a] -> Int
    length []     = 0                 -- length.1
    length (x:xs) = 1 + n_length xs   -- length.2
    
    (++) :: [a] -> [a] -> [a]
    []     ++ ys = ys                 -- ++.1
    (x:xs) ++ ys = x : (xs ++ ys)     -- ++.2
    
  • Propiedad: length(xs ++ ys) = (length xs)+(length ys)
  • Propiedad con QuickCheck:

    prop_length_append :: [Int] -> [Int] -> Bool
    prop_length_append xs ys =
       length(xs ++ ys)==(length xs)+(length ys)
    
  • Comprobación:

    λ> quickCheck prop_length_append
    OK, passed 100 tests.
    
  • Demostración por inducción en xs:
    • Caso base xs = []:

      length([] ++ ys)
      = length ys                [por ++.1]
      = 0+(length ys)            [por aritmética]
      = (length [])+(length ys)  [por length.1]
      
    • Caso inductivo xs = a:as: Suponiendo la hipótesis de inducción

      length(as ++ ys) = (length as)+(length ys)
      

      hay que demostrar que

      length((a:as) ++ ys) = (length (a:as))+(length ys)
      

      La demostración es

      length((a:as) ++ ys)
      = length(a:(as ++ ys))              [por ++.2]
      = 1 + length(as ++ ys)              [por length.2]
      = 1 + ((length as) + (length ys))   [por hip. ind.]
      = (1 + (length as)) + (length ys)   [por aritmética]
      = (length (a:as)) + (length ys)     [por length.2]
      

3.5. Relación entre take y drop

  • Definición de take, drop y (++):

    take :: Int -> [a] -> [a]
    take 0 _       = []                  -- take.1
    take _ []      = []                  -- take.2
    take n (x:xs)  = x : take (n-1) xs   -- take.3
    
    drop :: Int -> [a] -> [a]
    drop 0 xs      = xs                  -- drop.1
    drop _ []      = []                  -- drop,2
    drop n (_:xs)  = drop (n-1) xs       -- drop.3
    
    (++) :: [a] -> [a] -> [a]
    []     ++ ys = ys                    -- ++.1
    (x:xs) ++ ys = x : (xs ++ ys)        -- ++.2
    
  • Propiedad: take n xs ++ drop n xs = xs
  • Propiedad con QuickCheck:

    prop_take_drop :: Int -> [Int] -> Property
    prop_take_drop n xs =
      n >= 0 ==> take n xs ++ drop n xs == xs
    
  • Comprobación:

    λ> quickCheck prop_take_drop
    OK, passed 100 tests.
    
  • Demostración por inducción en n:
    • Caso base n = 0:

      take 0 xs ++ drop 0 xs
      = [] ++ xs              [por take.1 y drop.1]
      = xs                    [por ++.1]
      
  • Caso inductivo n = m+1: Suponiendo la hipótesis de inducción 1

    (∀ xs :: [a]) take m xs ++ drop m xs = xs
    

    hay que demostrar que

    (∀ xs :: [a]) take (m+1) xs ++ drop (m+1) xs = xs
    

    Lo demostraremos por inducción en xs:

    • Caso base xs = []:

      take (m+1) [] ++ drop (m+1) []
      = [] ++ []                        [por take.2 y drop.2]
      = []                              [por ++.1]
      
    • Caso inductivo xs = a:as: Suponiendo la hip. de inducción 2

      take (m+1) as ++ drop (m+1) as = as
      

      hay que demostrar que

      take (m+1) (a:as) ++ drop (m+1) (a:as) = (a:as)
      

      Demostración

      take (m+1) (a:as) ++ drop (m+1) (a:as)
      = (a:(take m as)) ++ (drop m as)     [take.3 y drop.3]
      = (a:((take m as) ++ (drop m as))    [por ++.2]
      = a:as                               [por hip. de ind. 1]
      

4. Equivalencia de funciones

  • Definiciones de inversa1 e inversa2:

    inversa1, inversa2 :: [a] -> [a]
    inversa1 []     = []                                    -- inversa1.1
    inversa1 (x:xs) = inversa1 xs ++ [x]                    -- inversa1.2
    
    inversa2 xs = inversa2Aux xs []                         -- inversa2.1
      where inversa2Aux []     ys = ys                      -- inversa2Aux.1
            inversa2Aux (x:xs) ys = inversa2Aux xs (x:ys)   -- inversa2Aux.2
    
  • Propiedad: inversa1 xs = inversa2 xs
  • Propiedad con QuickCheck:

    prop_equiv_inversa :: [Int] -> Bool
    prop_equiv_inversa xs = inversa1 xs == inversa2 xs
    
  • Demostración: Es consecuencia del siguiente lema:

    inversa1 xs ++ ys = inversa2Aux xs ys
    

    En efecto,

    inversa1 xs
    = inversa1 xs ++ []     [por identidad de ++]
    = inversa2Aux xs ++ []  [por el lema]
    = inversa2 xs           [por el inversa2.1]
    
  • Demostración del lema: Por inducción en xs:
    • Caso base xs = []:

      inversa1 [] ++ ys
      = [] ++ ys              [por inversa1.1]
      = ys                    [por ++.1]
      = inversa2Aux [] ys     [por inversa2Aux.1]
      
  • Caso inductivo xs=(a:as): La hipótesis de inducción es

    (∀ ys :: [a]) inversa1 as ++ ys = inversa2Aux as ys
    

    Hay que demostrar que

    (∀ ys :: [a]) inversa1 (a:as) ++ ys = inversa2Aux (a:as) ys
    

    Demostración

    inversa1 (a:as) ++ ys
    = (inversa1 as ++ [a]) ++ ys    [por inversa1.2]
    = (inversa1 as) ++ ([a] ++ ys)  [por asociativa de ++]
    = (inversa1 as) ++ (a:ys)       [por ley unitaria]
    = (inversa2Aux as (a:ys)        [por hip. de inducción]
    = inversa2Aux (a:as) ys         [por inversa2Aux.2]
    

5. Propiedades de funciones de orden superior

5.1. Relación entre sum y map

  • Definición de la función sum:

    sum :: [Int] -> Int
    sum []     = 0
    sum (x:xs) = x + sum xs
    
  • Propiedad: sum (map (2*) xs) = 2 * sum xs
  • Propiedad con QuickCheck:

    prop_sum_map :: [Int] -> Bool
    prop_sum_map xs = sum (map (2*) xs) == 2 * sum xs
    
  • Comprobación:

    λ> quickCheck prop_sum_map
    +++ OK, passed 100 tests.
    
  • Demostración de la propiedad por inducción en xs
    • Caso []:

      sum (map (2*) xs)
      = sum (map (2*) [])  [por hipótesis]
      = sum []             [por map.1]
      = 0                  [por sum.1]
      = 2 * 0              [por aritmética]
      = 2 * sum []         [por sum.1]
      = 2 * sum xs         [por hipótesis]
      
  • Caso xs = y:ys:

    sum (map (2*) xs)
    = sum (map (2*) (y:ys))         [por hipótesis]
    = sum ((2*) y : (map (2*) ys))  [por map.2]
    = (2*) y + (sum (map (2*) ys))  [por sum.2]
    = (2*) y + (2 * sum ys)         [por hip. de inducción]
    = (2 * y) + (2 * sum ys)        [por (2*)]
    = 2 * (y + sum ys)              [por aritmética]
    = 2 * sum (y:ys)                [por sum.2]
    = 2 * sum xs                    [por hipótesis]
    

5.2. Comprobación de propiedades con argumentos funcionales

  • La aplicación de una función a los elementos de una lista conserva su longitud:

    prop_map_length (Fun _ f) xs =
      length (map f xs) == length xs
    
  • En el inicio del fichero hay que escribir

    import Test.QuickCheck.Function
    
  • Comprobación

    λ> quickCheck prop_map_length
    +++ OK, passed 100 tests.
    

6. Material complementario

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

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

7. Bibliografía

  • H.C. Cunningham. Notes on Functional Programming with Haskell.
  • J. Fokker. Programación funcional.
  • G. Hutton. Programming in Haskell. Cambridge University Press, 2007.
    • Cap. 13: Reasoning about programs.
  • B.C. Ruiz, F. Gutiérrez, P. Guerrero y J.E. Gallardo. Razonando con Haskell. Thompson, 2004.
    • Cap. 6: Programación con listas.
  • S. Thompson. Haskell: The Craft of Functional Programming, Second Edition. Addison-Wesley, 1999.
    • Cap. 8: Reasoning about programs.
  • E.P. Wentworth. Introduction to funcional programming.

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

José A. Alonso Jiménez

Sevilla, 07 de abril del 2024

Licencia: Creative Commons.