Los programas por lote no interactúan con los usuarios durante su ejecución.
Los programas interactivos durante su ejecución pueden leer datos del teclado y escribir resultados en la pantalla.
Ejemplo de programa interactivo
Especificación: El programa pide una cadena y dice el número de caracteres que tiene.
Ejemplo de sesión:
ghci> longitudCadena
Escribe una cadena: "Hoy es lunes"
La cadena tiene 14 caracteres
longitudCadena :: IO ()
longitudCadena = do
putStr "Escribe una cadena: "
xs <- getLine
putStr "La cadena tiene "
putStr (show (length xs))
putStrLn " caracteres"
En Haskell se pueden escribir programas interactivos usando tipos que distingan las expresiones puras de las acciones impuras que tienen efectos laterales.
IO a
es el tipo de las acciones que devuelven un valor del tipo a
.
IO Char
es el tipo de las acciones que devuelven un carácter.IO ()
es el tipo de las acciones que no devuelven ningún valor.getChar :: IO Char
La acción getChar
lee un carácter del teclado, lo muestra en la pantalla y lo devuelve como valor.
putChar :: c -> IO ()
La acción putChar c
escribe el carácter c
en la pantalla y no devuelve ningún valor.
return a -> IO a
La acción return c
devuelve el valor c
sin ninguna interacción.
Ejemplo:
ghci> putChar 'b'
bghci> it
()
Una sucesión de acciones puede combinarse en una acción compuesta mediante expresiones do
.
Ejemplo: El procedimiento ejSecuenciacion
lee dos caracteres y devuelve el par formado por ellos. Por ejemplo,
ghci> ejSecuenciacion
b f
('b','f')
ejSecuenciacion :: IO (Char,Char)
ejSecuenciacion = do
x <- getChar
getChar
y <- getChar
return (x,y)
getLine :: IO String
getLine = do x <- getChar
if x == '\n' then return []
else do xs <- getLine
return (x:xs)
putStr :: String -> IO ()
putStr [] = return ()
putStr (x:xs) = do putChar x
putStr xs
putStrLn :: String -> IO ()
putStrLn xs = do putStr xs
putChar '\n'
ghci> sequence_ [putStrLn "uno", putStrLn "dos"]
uno
dos
ghci> it
()
sequence_ :: [IO a] -> IO ()
sequence_ [] = return ()
sequence_ (a:as) = do a
sequence_ as
Ejemplo de programa con primitivas derivadas
Especificación: El programa pide una cadena y dice el número de caracteres que tiene.
Ejemplo de sesión:
ghci> longitudCadena
Escribe una cadena: "Hoy es lunes"
La cadena tiene 14 caracteres
longitudCadena :: IO ()
longitudCadena = do
putStr "Escribe una cadena: "
xs <- getLine
putStr "La cadena tiene "
putStr (show (length xs))
putStrLn " caracteres"
Descripción: El programa le pide al jugador humano que piense un número entre 1 y 100 y trata de adivinar el número que ha pensado planteándole conjeturas a las que el jugador humano responde con mayor, menor o exacto según que el número pensado sea mayor, menor o igual que el número conjeturado por la máquina.
Ejemplo de sesión:
Main> juego
Piensa un numero entre el 1 y el 100.
Es 50? [mayor/menor/exacto] mayor
Es 75? [mayor/menor/exacto] menor
Es 62? [mayor/menor/exacto] mayor
Es 68? [mayor/menor/exacto] exacto
Fin del juego
juego :: IO ()
juego =
do putStrLn "Piensa un numero entre el 1 y el 100."
adivina 1 100
putStrLn "Fin del juego"
adivina :: Int -> Int -> IO ()
adivina a b =
do putStr ("Es " ++ show conjetura ++ "? [mayor/menor/exacto] ")
s <- getLine
case s of
"mayor" -> adivina (conjetura+1) b
"menor" -> adivina a (conjetura-1)
"exacto" -> return ()
_ -> adivina a b
where
conjetura = (a+b) `div` 2
Descripción: En el segundo juego la máquina genera un número aleatorio entre 1 y 100 y le pide al jugador humano que adivine el número que ha pensado planteándole conjeturas a las que la máquina responde con mayor, menor o exacto según que el número pensado sea mayor, menor o igual que el número conjeturado por el jugador humano.
Ejemplo de sesión:
Main> juego2
Tienes que adivinar un numero entre 1 y 100
Escribe un numero: 50
es bajo.
Escribe un numero: 75
es alto.
Escribe un numero: 62
Exactamente
import System.Random (randomRIO)
juego2 :: IO ()
juego2 = do n <- randomRIO (1::Int,100)
putStrLn "Tienes que adivinar un numero entre 1 y 100"
adivina' n
adivina' :: Int -> IO ()
adivina' n =
do putStr "Escribe un numero: "
c <- getLine
let x = read c
case (compare x n) of
LT -> do putStrLn " es bajo."
adivina' n
GT -> do putStrLn " es alto."
adivina' n
EQ -> putStrLn " Exactamente"
Acciones auxiliares
import I1M.Analizador
import System.IO
getCh :: IO Char
getCh = do hSetEcho stdin False
c <- getChar
hSetEcho stdin True
return c
limpiaPantalla :: IO ()
limpiaPantalla = putStr "\ESC[2J"
type Pos = (Int,Int)
irA :: Pos -> IO ()
irA (x,y) = putStr ("\ESC[" ++
show y ++ ";" ++ show x ++
"H")
escribeEn :: Pos -> String -> IO ()
escribeEn p xs = do irA p
putStr xs
Calculadora
calculadora :: IO ()
calculadora = do limpiaPantalla
escribeCalculadora
limpiar
escribeCalculadora :: IO ()
escribeCalculadora =
do limpiaPantalla
sequence_ [escribeEn (1,y) xs
| (y,xs) <- zip [1..13] imagenCalculadora]
putStrLn ""
imagenCalculadora :: [String]
imagenCalculadora = ["+---------------+",
"| |",
"+---+---+---+---+",
"| q | c | d | = |",
"+---+---+---+---+",
"| 1 | 2 | 3 | + |",
"+---+---+---+---+",
"| 4 | 5 | 6 | - |",
"+---+---+---+---+",
"| 7 | 8 | 9 | * |",
"+---+---+---+---+",
"| 0 | ( | ) | / |",
"+---+---+---+---+"]
Los primeros cuatro botones permiten escribir las órdenes:
Los restantes botones permiten escribir las expresiones.
limpiar :: IO ()
limpiar = calc ""
calc :: String -> IO ()
calc xs = do escribeEnPantalla xs
c <- getCh
if elem c botones
then procesa c xs
else do calc xs
escribeEnPantalla xs =
do escribeEn (3,2) " "
escribeEn (3,2) (reverse (take 13 (reverse xs)))
botones :: String
botones = standard ++ extra
where
standard = "qcd=123+456-789*0()/"
extra = "QCD \ESC\BS\DEL\n"
procesa :: Char -> String -> IO ()
procesa c xs
| elem c "qQ\ESC" = salir
| elem c "dD\BS\DEL" = borrar xs
| elem c "=\n" = evaluar xs
| elem c "cC" = limpiar
| otherwise = agregar c xs
salir :: IO ()
salir = irA (1,14)
borrar :: String -> IO ()
borrar "" = calc ""
borrar xs = calc (init xs)
evaluar :: String -> IO ()
evaluar xs = case analiza expr xs of
[(n,"")] -> calc (show n)
_ -> do calc xs
agregar :: Char -> String -> IO ()
agregar c xs = calc (xs ++ [c])
Descripción del juego de la vida
El tablero del juego de la vida es una malla formada por cuadrados ("células") que se pliega en todas las direcciones.
Cada célula tiene 8 células vecinas, que son las que están próximas a ella, incluso en las diagonales.
Las células tienen dos estados: están "vivas" o "muertas".
El estado del tablero evoluciona a lo largo de unidades de tiempo discretas.
Funciones anteriores
import Data.List (nub)
type Pos = (Int,Int)
irA :: Pos -> IO ()
irA (x,y) = putStr ("\ESC[" ++ show y ++ ";" ++ show x ++ "H")
escribeEn :: Pos -> String -> IO ()
escribeEn p xs = do irA p
putStr xs
limpiaPantalla:: IO ()
limpiaPantalla= putStr "\ESC[2J"
El tablero del juego de la vida
type Tablero = [Pos]
ancho :: Int
ancho = 5
alto :: Int
alto = 5
El juego de la vida
ejTablero :: Tablero
ejTablero = [(2,3),(3,4),(4,2),(4,3),(4,4)]
1234
1
2 O
3 O O
4 OO
(vida n t)
simula el juego de la vida a partir del tablero t
con un tiempo entre generaciones proporcional a n
. Por ejemplo,vida 100000 ejTablero
vida :: Int -> Tablero -> IO ()
vida n t = do limpiaPantalla
escribeTablero t
espera n
vida n (siguienteGeneracion t)
escribeTablero :: Tablero -> IO ()
escribeTablero t = sequence_ [escribeEn p "O" | p <- t]
espera :: Int -> IO ()
espera n = sequence_ [return () | _ <- [1..n]]
siguienteGeneracion t)
es el tablero de la siguiente generación al tablero t
. Por ejemplo,ghci> siguienteGeneracion ejTablero
[(4,3),(3,4),(4,4),(3,2),(5,3)]
siguienteGeneracion :: Tablero -> Tablero
siguienteGeneracion t = supervivientes t ++ nacimientos t
(supervivientes t)
es la listas de posiciones de t
que sobreviven; i.e. posiciones con 2 ó 3 vecinos vivos. Por ejemplo,supervivientes ejTablero == [(4,3),(3,4),(4,4)]
supervivientes :: Tablero -> [Pos]
supervivientes t = [p | p <- t,
elem (nVecinosVivos t p) [2,3]]
(nVecinosVivos t c)
es el número de vecinos vivos de la célula c
en el tablero t
. Por ejemplo,nVecinosVivos ejTablero (3,3) == 5
nVecinosVivos ejTablero (3,4) == 3
nVecinosVivos :: Tablero -> Pos -> Int
nVecinosVivos t = length . filter (tieneVida t) . vecinos
(vecinos p)
es la lista de los vecinos de la célula en la posición p
. Por ejemplo,vecinos (2,3) == [(1,2),(2,2),(3,2),(1,3),(3,3),(1,4),(2,4),(3,4)]
vecinos (1,2) == [(5,1),(1,1),(2,1),(5,2),(2,2),(5,3),(1,3),(2,3)]
vecinos (5,2) == [(4,1),(5,1),(1,1),(4,2),(1,2),(4,3),(5,3),(1,3)]
vecinos (2,1) == [(1,5),(2,5),(3,5),(1,1),(3,1),(1,2),(2,2),(3,2)]
vecinos (2,5) == [(1,4),(2,4),(3,4),(1,5),(3,5),(1,1),(2,1),(3,1)]
vecinos (1,1) == [(5,5),(1,5),(2,5),(5,1),(2,1),(5,2),(1,2),(2,2)]
vecinos (5,5) == [(4,4),(5,4),(1,4),(4,5),(1,5),(4,1),(5,1),(1,1)]
vecinos :: Pos -> [Pos]
vecinos (x,y) = map modular [(x-1,y-1), (x,y-1), (x+1,y-1),
(x-1,y), (x+1,y),
(x-1,y+1), (x,y+1), (x+1,y+1)]
(modular p)
es la posición correspondiente a p
en el tablero considerando los plegados. Por ejemplo,modular (6,3) == (1,3)
modular (0,3) == (5,3)
modular (3,6) == (3,1)
modular (3,0) == (3,5)
modular :: Pos -> Pos
modular (x,y) = (1 + (x-1) `mod` ancho,
1 + (y-1) `mod` alto)
(tieneVida t p)
se verifica si la posición p
del tablero t
tiene vida. Por ejemplo,tieneVida ejTablero (1,1) == False
tieneVida ejTablero (2,3) == True
tieneVida :: Tablero -> Pos -> Bool
tieneVida t p = elem p t
(noTieneVida t p)
se verifica si la posición p
del tablero t
no tiene vida. Por ejemplo,noTieneVida ejTablero (1,1) == True
noTieneVida ejTablero (2,3) == False
noTieneVida :: Tablero -> Pos -> Bool
noTieneVida t p = not (tieneVida t p)
(nacimientos t)
es la lista de los nacimientos de tablero t
; i.e. las posiciones sin vida con 3 vecinos vivos. Por ejemplo,nacimientos ejTablero == [(3,2),(5,3)]
nacimientos' :: Tablero -> [Pos]
nacimientos' t = [(x,y) | x <- [1..ancho],
y <- [1..alto],
noTieneVida t (x,y),
nVecinosVivos t (x,y) == 3]
nacimientos
nacimientos :: Tablero -> [Pos]
nacimientos t = [p | p <- nub (concatMap vecinos t),
noTieneVida t p,
nVecinosVivos t p == 3]
Instalación de programas
cabal install gnuplot
Uso de gnuplot
import Graphics.Gnuplot.Simple
Representación de funciones con plotFunc
dib0 :: IO ()
dib0 = plotFunc [] [0,0.01..10 :: Double] cos
Rango lineal
xRango 5 (0,10) == [0.0,2.0,4.0,6.0,8.0,10.0]
xRango :: Integer -> (Double,Double) -> [Double]
xRango n i = linearScale n i
Atributos de los gráficos
dib1a =
plotFunc [EPS "ej.eps"] (xRango 500 (-10,10)) sin
dib1b =
plotFunc [Grid (Just ["x"])] (xRango 500 (-10,10)) sin
dib1c =
plotFunc [Grid (Just ["y"])] (xRango 500 (-10,10)) sin
dib1d =
plotFunc [Grid (Just [])] (xRango 500 (-10,10)) sin
dib1e =
plotFunc [Title "La funcion seno", Key Nothing]
(xRango 1000 (-10,10)) sin
dib1f =
plotFunc [XLabel "Eje horizontal"] (xRango 1000 (-10,10)) sin
Dibujo de listas de puntos con plotList
dib2 =
plotList [] (take 30 fibs)
where fibs :: [Double]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
Gráficas conjuntas
dib3 =
plotFuncs [] (xRango 1000 (-10,10)) [sin, cos]
Gráfico de curvas paramétricas
dib4 =
plotParamFunc [Key Nothing]
(xRango 1000 (0,2*pi))
(\t -> (12*sin(t)-4*sin(3*t),
13*cos(t)-5*cos(2*t)-2*cos(3*t)-cos(4*t)))
Representación de superficies con plotFunc3d
dib5 =
plotFunc3d [] [] xs xs (\x y -> exp(-(x*x+y*y)))
where xs = [-2,-1.8..2::Double]
Escritura en ficheros
(tabla n)
crea el fichero cuadrados.txt y escribe la tabla de cuadrados. Por ejemplo, el contenido al evaluar (tabla 5)
es(1,1)
(2,4)
(3,9)
(4,16)
(5,25)
El programa es
import System.IO
tabla :: Int -> IO ()
tabla n = do
a <- openFile "cuadrados.txt" WriteMode
mapM_ (hPrint a) [(x,x^2) | x <- [1..n]]
hClose a
Lectura y escritura en ficheros
El procedimiento (aMayucula f1 f2)
lee el contenido del dichero f1 y escribe su contenido en mayúscula en el fichero f2. Por ejemplo, si el contenido de frase.txt
es "En todo la medida" al evaluar (aMayuscula "frase.txt" "frase2.txt")
crea el fichero frase2.txt
con el contenido "EN TODO LA MEDIDA".
1ª definición
import System.IO
import Data.Char
aMayuscula f1 f2 = do
a1 <- openFile f1 ReadMode
a2 <- openFile f2 WriteMode
contenido <- hGetContents a1
hPutStr a2 (map toUpper contenido)
hClose a1
hClose a2
import System.IO
import Data.Char
aMayuscula f1 f2 = do
contenido <- readFile f1
writeFile f2 (map toUpper contenido)
Wikipedia ANSI escape code.