Tabla de Contenidos

Clase 1

Introducción al uso de Hugs

Hugs es un intérprete del lenguaje funcional puro Haskell.
Durante este taller escribiremos, probaremos y utilizaremos los programas funcionales que se dan en el teórico.

A Hugs se lo invoca desde la línea de comandos o picando sobre el icono en nuestro entorno gráfico. Una vez que el intérprete está activo, la pantalla se presenta con un prompt a la espera de expresiones a ser evaluadas o comandos.

[nicolasw@azul Taller]$ hugs
__   __ __  __  ____   ___      _________________________________________
||   || ||  || ||  || ||__      Hugs 98: Based on the Haskell 98 standard
||___|| ||__|| ||__||  __||     Copyright (c) 1994-2005
||---||         ___||           World Wide Web: http://haskell.org/hugs
||   ||                         Report bugs to: hugs-bugs@haskell.org
||   || Version: March 2005     _________________________________________

Haskell 98 mode: Restart with command line option -98 to enable extensions

Type :? for help
Hugs.Base>

De este modo Hugs se convierte en una calculadora, esperando que se introduzca una expresión para evaluarla e imprimir el resultado, y luego volver a pedir una expresión (lazo leer-evaluar-imprimir).
Gracias al preludio standard tenemos muchas funciones ya definidas.

Hugs.Base> 21+21
42
Hugs.Base> True && False
False
Hugs.Base> 2*4 == 10 `div` 5 && True
False
Hugs.Base> [2,3,5,7,11]++[1,2]
[2,3,5,7,11,1,2]
Hugs.Base> reverse "dabale arroz a la zorra el abad"
"daba le arroz al a zorra elabad"

Este ciclo continúa indefinidamente hasta que pidamos la salida del intérprete con CTRL-D o con el comando

Hugs.Base> :q
[Leaving Hugs]
[nicolasw@azul Taller]$

Para poder dar nuevas definiciones y/o funciones necesitamos escribir un programa funcional o script Haskell.
Un programa funcional es un archivo con terminación .hs donde se escriben en texto plano todas las definiciones que conforman el programa funcional.

A manera de ejemplo realicemos un ciclo de creación-carga-prueba-modificación-recarga, con el Ejercicio 8.3 del apunte Extracto del Cálculo de Programas.

Para crear un script basta con invocar el comando para editar un (nuevo) archivo :e cap8.hs. Una posible solución para el problema de la función signo es la siguiente:

sgn :: Int -> Int
sgn x   | 0<x   = 1
        | x<0   = -1
        | x=0   = 0

Luego de salvar el programa y cargarlo, el intérprete indica un error pues = es el símbolo de definición, mientras que la comparación es ==.

Hugs.Base> :e cap8.hs
Hugs.Base> :l cap8.hs
ERROR "cap8.hs":4 - Syntax error in input (unexpected `=')

Las traducciones son más o menos directas, de todas formas preparamos una tabla de Traducción de "Cálculo de Programas" a Haskell.

Volvemos a editar el script con :e (el nombre ya no lo necesitamos ya que tenemos cargado este script) y corregimos x=0 por x==0.
Podemos probar la nueva función con casos de test para ganar confianza en su corrección.

Main> sgn 1
1
Main> sgn 0
0
Main> sgn -1
ERROR - Cannot infer instance
*** Instance   : Num (Int -> Int)
*** Expression : sgn - 1

Main> sgn (-1)
-1
Main> sgn 123123123123

Program error: arithmetic overflow

Main> sgn 123123123
1
Main> sgn 1.1
ERROR - Cannot infer instance
*** Instance   : Fractional Int
*** Expression : sgn 1.1

Main> sgn (-1
ERROR - Syntax error in expression (unexpected end of input)

Main> sgn "hola"
ERROR - Type error in application
*** Expression     : sgn "hola"
*** Term           : "hola"
*** Type           : String
*** Does not match : Int

Main> map sgn [-10..10]
[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1]

Notamos los distintos tipos de errores que se producen por problemas de

Inferencia de tipos

Hugs tiene una maquinaria para inferir tipos, tanto los declarados

Main> :t sgn
sgn :: Int -> Int

Como expresiones en general

Main> :t 1 + 2.1
1 + 2.1 :: Fractional a => a

Main> :t 1+ sqrt 64
1 + sqrt 64 :: Floating a => a

Main> :t 1+ "a"
ERROR - Cannot infer instance
*** Instance   : Num [Char]
*** Expression : 1 + "a"

Main> :t "hola" ++ "que" ++ "tal"
"hola" ++ "que" ++ "tal" :: [Char]

Main> :t reverse
reverse :: [a] -> [a]

Main> :t map
map :: (a -> b) -> [a] -> [b]

Esta maquinaria impide que escribamos cualquier expresión que esté mal tipada.

Tuplas

Haskell maneja n-uplas de manera directa. Incorporamos a cap8.hs las siguientes definiciones:

suma3upla :: (Int,Int,Int) -> Int
suma3upla (x,y,z) = x+y+z

sumaYResta :: Int -> Int -> (Int,Int)
sumaYResta x y = (x+y, x-y)

Y las probamos desde el prompt.

Main> suma3upla (2,3,4)
9
Main> sumaYResta 2 3
(5,-1)

Ejemplo: la función bisiesto

A manera de ejemplo veamos el ejercicio 8.7 del apunte, donde tenemos que definir una función muy útil para cualquier aparato que maneje un calendario (relojes, celulares, PDAs, computadoras, DVD-R, etc.). La signatura 1) es bisiesto: Int → Bool, y es un predicado que devuelve true si el año es bisiesto y false en caso contrario. Recordemos cuando un año es bisiesto:

La regla para los años bisiestos según el calendario gregoriano es:
Un año es bisiesto si es divisible por 4, excepto los principios de siglo (aquellos divisibles por 100),
que para ser bisiestos, también deben ser divisibles por 400. 

Una definición matemática concisa sería bisiesto n = 4|n /\ (100|n ⇒ 400|n).

Entonces podemos seguir agregando definiciones de funciones a nuestro archivo cap8.hs con el comando :e. Veamos tres versiones distintas 2).

La del viejo programadora/or

bisiesto'' :: Int -> Bool
bisiesto'' n = if  n `mod` 4 /= 0 then False
                else if n `mod` 100 /= 0 then True
                        else if n `mod` 400 == 0 then True
                                else False

El que está aprendiendo Haskell

bisiesto' :: Int -> Bool
bisiesto' n     | n `mod` 4 /= 0 = False
                | n `mod` 4 == 0 = n `mod` 100 /= 0 || n `mod` 400 == 0

El que cursó Introducción a los Algoritmos ;-)

bisiesto :: Int -> Bool
bisiesto n = n `mod` 4 == 0 && (n `mod` 100 /= 0 || n `mod` 400 == 0)

Podemos poner las tres versiones en nuestro script y probarlas rápidamente usando la función filter para que obtener solo los años bisiestos desde 1945 al 2006.

Main> filter bisiesto [1945..2006]
[1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004]
Main> filter bisiesto' [1945..2006]
[1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004]
Main> filter bisiesto'' [1945..2006]
[1948,1952,1956,1960,1964,1968,1972,1976,1980,1984,1988,1992,1996,2000,2004]

Vemos que las tres funciones operan correctamente en el rango de números dados 3).

Ejercicios

Para realizar en lo que resta de la clase.

probar con (1,2) y (1,2), (1,4) y (1,4).
probar con (0,1), (2,2), (3,1).
probar con 5 y 9, con -8 y 9, con -10 y -1, con 0 y 0 y con 0 y 3

Definir la función edad : (Int, Int, Int) → (Int, Int, Int) → Int que dadas dos fechas indica los años transcurridos entre ellas. Por ejemplo edad.(20,10,1968).(30,4,1987) = 18. Suponer que las fechas están siempre bien formadas y que la primera es menor o igual a la segunda.

probar con (16,4,1980) y (17,5,1992), (16,4,1980) y (14,5,1992), (16,4,1980) y (15,4,1992) y con (16,4,1980) y (17,5,1972).

En un prisma rectangular, llamemos h a la altura, b al ancho y d a la profundidad. Completar la siguiente definición del área del prisma:
area.h.b.d = 2 ∗ frente + 2 ∗ lado + 2 ∗ arriba
|[ …aca va la definicion… ]|
donde frente, lado y arriba son las caras frontal, lateral y superior del prisma respectivamente.

Completar la función area.h.b.d area : Int → Int → Int → Int que calcula el área de un prisma rectangular:

    area :: Int -> Int -> Int -> Int   
    area.h.b.d = 2*frente + 2*lado + 2*tapa
         where
            frente = ...
            lado   = ...
            tapa   = ...
1)
Signatura es el nombre de la función junto al tipo de sus parámetros y resultado
2)
Esto es una mala copia de The Evolution of a Haskell Programmer
3)
No queremos decir que sean correctas en su totalidad, solo decimos que en ese rango no tienen fallas