Anda di halaman 1dari 7

29/11/2014 UnaintroduccinagradableaHaskell:Entrada/Salida

UnaIntroduccinagradableaHaskell
anteriorsiguienteinicio

7 Entrada/Salida
Elsistemadeentrada/salida(E/S)deHaskellesfuncionalpuro,yadems,proporcionatodalaexpresividad
quepresentanloslenguajesdeprogramacinimperativosconvencionales.Enloslenguajesimperativos,los
programasestnformadosporaccionesqueexaminanymodificanelestadoactualdelsistema.Algunas
accionestpicassonlalecturaymodificacindevariablesglobales,laescrituraenficheros,lalecturadedatos
yelmanejodeventanasenentornosgrficos.Haskellpermiteutilizaraccionesdeestetipo,aunqueestn
claramenteseparadasdelncleopuramentefuncionaldellenguaje.

ElsistemadeE/SdeHaskellestbasadoenunfundamentomatemticoquepuedeasustaraprimeravista:
lasmnadas.Sinembargo,noesnecesarioconocerlateorademnadassubyacenteparaprogramar
usandoelsistemadeE/S.Msbien,lasmnadassonsimplementeunaestructuraconceptualenlasquelas
E/Sencaja.NoesnecesarioconocerlateorademnadasparatrabajarconoperacionesdeE/SenHaskell
delmismomodoquenoesnecesarioconocerlateoradegruposparatrabajarconoperacionesaritmticas
simples.Unaexplicacinenprofundidaddelasmnadaspuedeencontrarseenlaseccin9.

LosoperadoresmondicosenlosqueelsistemadeE/Sestbasadosonusadostambinparaotros
propsitosprofundizaremosenlasmnadasposteriormente.Porahora,evitaremoseltrminomnadaynos
concentraremosenelusodelsistemadeE/S.EsmejorpensarenlamnadadeE/Scomountipoabstracto.

Lasaccionessondefinidas,peronoinvocadas,alniveldelamquinadeevaluacinquedirigelaejecucin
deunprogramaHaskell.Laevaluacindeladefinicindeunaaccinnohacequelaaccinsearealizada.
Msbien,laejecucindeaccionesesefectuadaenunniveldistintoalaevaluacindeexpresionesque
hemosconsideradohastaestemomento.

Lasaccionessonatmicas,comoelcasodelasdefinidascomoprimitivasdelsistema,oseobtienendela
composicinsecuencialdeotrasacciones.LamnadadeE/Scontieneprimitivasquepermitenconstruir
accionescompuestas,unprocesosimilaralquerealizanalgunoslenguajesimperativosalunirsentenciasdeun
modosecuencialusando";".Estamnadaactacomounpegamentoqueunelasaccionesqueformanparte
deunprograma.

7.1 OperacionesdeE/Sbsicas

CadaaccindeE/Sdevuelveunvalor.Aniveldelsistemadetipos,elvalordevueltoesanotadoconuntipo
IO.Estodistinguelasaccionesdeotrosvalores.Porejemplo,eltipodelafuncingetChares:

getChar :: IO Char

EltipoIO CharindicaquegetChar,cuandoseaejecutado,realizarunaaccinquedevolveruncarcter.
Lasaccionescuyovalordevueltonoesinteresanteusaneltipounitario().Porejemplo,lafuncinputChar:

putChar :: Char -> IO ()

tomauncarctercomoargumentoperonodevuelvenadatil.Eltipounitarioessimilaraltipovoidenotros
lenguajes.

http://www.lcc.uma.es/~blas/pfHaskell/gentle/io.html 1/7
29/11/2014 UnaintroduccinagradableaHaskell:Entrada/Salida

Lasaccionessoncombinadassecuencialmenteusandounoperadorcuyonombreesuntantocrptico:>>=(o
"bind").Envezdeusaresteoperadordirectamente,usaremosunasintxismsclara,lanotacindo,para
ocultarelusodelosoperadoresdesecuenciacingraciasalusodeunasintaxisquerecuerdaaladelos
lenguajesimperativosconvencionales.Lanotacindopuedesertraducida,deunmodotrivial,afunciones
Haskellnormales,comosedescribeen3.14.

Lapalabraclavedoiniciaunasecuenciadesentenciasquesernejecutadasenorden.Unasentenciaeso
bienunaaccin,ounpatrnasociadoalresultadodeunaaccinusando<-,ounadefinicinlocaldefinida
mediantelet.Lanotacindousalaindentacindelprogramadelmismomodoquelasdefinicioneslocales
introducidasconletowhere,demodoquepodemosomitirlasllavesylospuntosycomausandoun
sangradoadecuado.Elsiguienteejemploesunprogramaqueleeyescribeuncarcter:

main :: IO ()
main = do c <- getChar
putChar c

Elusodelnombremainesimportante:maineslaexpresinprincipaldeunprogramaHaskell(deunmodo
similaralafuncinmaindeunprogramaenC),ydebeteneruntipoIOm,usualmenteIO ().(Elnombre
mainesespecialtansoloenelmduloMainhablaremosdelosmdulosposteriormente).Esteprograma
ejecutadosaccionesensecuencia:primeroleeuncarcterdelteclado,ligandoelresultadoconlavariablec,
yacontinuacinimprimeelcarcter.Adiferenciadelasexpresionesletparalasquelasvariables
introducidasestnenelmbitodetodaslasdefiniciones,lasvariablesdefinidascon<-soloestnenel
mbitodelassentenciasposteriores.

Todavafaltaalgo.Podemosejecutaraccionesyexaminarsusresultadosusandolanotacindo,perocmo
devolvemosunvalordesdeunasecuenciadeacciones?Porejemplo,considreselafuncinreadyquelee
uncarcterydevuelveTruesidichocarcteres`y':

ready :: IO Bool
ready = do c <- getChar
c == 'y' -- Mal !!!

Ladefinicinanteriornoescorrectaporquelasegundasentenciaesunvalorbooleano,ynounaaccin.Es
necesariocrearunaaccinquedevuelvaunbooleanocomoresultado.Lafuncinreturn hace
precisamenteesto:

return :: a -> IO a

Lafuncinreturncompletaelconjuntodeprimitivas.Laltimalneadeladefinicindereadydeberaser
return (c == 'y').

AhorapodemosverejemplosdeE/Smscomplicados.Enprimerlugar,lafuncingetLine:

getLine :: IO String
getLine = do c <- getChar
if c == '\n'
then return ""
else do l <- getLine
return (c:l)

Obsrveseelsegundodoenlaparteelse.Cadadoiniciaunasecuenciadesentencias.Cualquierotra
construccinqueformepartedelafuncin,comoifenelejemplo,debeusarunnuevodoparainiciarotras
http://www.lcc.uma.es/~blas/pfHaskell/gentle/io.html 2/7
29/11/2014 UnaintroduccinagradableaHaskell:Entrada/Salida

secuenciasdeacciones.

LafuncinreturndaentradaaunvaloreneldominiodelasaccionesdeE/S.Pero,qupasaconla
direccininversa?esposibleinvocaralgunaaccindeE/Sdesdeunaexpresinnormal?Porejemplo,
cmopodemosexpresarx + print yenunaexpresindemodoqueyseaimprimidaalevaluarla
expresin?Larespuestaesqueestonoesposible.Noesposibleadentrarseeneluniversoimperativoen
mitaddecdigofuncionalpuro.Cualquiervalor`infectado'poraccionesimperativasdebeseretiquetado
comotal(usandoeltipoIO).Unafuncincomo

f :: Int -> Int -> Int

nopuederealizarningunaE/SyaqueIOnoformapartedeltipodevuelto.Estehechoeshabitualmente
desconsoladorparaprogramadoresacostumbradosacolocarsentenciastipoprintliberalmentealolargodel
cdigodeunprogramadurantelafasededepuracin.Realmente,existenfuncionespeligrosas(rompenel
carcterpurodellenguaje)paraestoscasos,aunqueesmejorreservarelusodestasaprogramadores
avanzados.Lospaquetesdedepuracinsuelenhacerunusoliberaldeestas"funcionesprohibidas"deun
modototalmenteseguro.

7.2 Programandoconacciones

LasaccionesdeE/SsonvaloresnormalesenHaskell:puedenserpasadoscomoargumentosafunciones,
puedenformarpartedeestructuras,yengeneral,serusadoscomocualquierotrovalor.Considreseesta
listadeacciones:

todoList :: [IO ()]

todoList = [putChar 'a',


do putChar 'b'
putChar 'c',
do c <- getChar
putChar c]

Estalistanoejecutaningunaaccinsimplementelasalmacena.Paraenlazarestasaccionesydarlugarauna
nicaaccin,esnecesariaunafuncincomosequence_:

sequence_ :: [IO ()] -> IO ()


sequence_ [] = return ()
sequence_ (a:as) = do a
sequence_ as

Estadefincinpuedesersimplificadasiobservamosquelaexpresindo x;yestraducidaax >> y.Este


patrnderecursineseldefinidoporlafuncinfoldrunadefinicinmejordesequence_es:

sequence_ :: [IO ()] -> IO ()


sequence_ = foldr (>>) (return ())

Lanotacindoestilperoenestecasoeloperadormondicosubyacente,>>,esmsapropiado.El
conocimientodelosoperadoresutilizadosparatraducirlanotacindopuedesermuytilparael
programador.

Lafuncinsequence_puedeserusadaparaconstruirputStrapartirdeputChar:

http://www.lcc.uma.es/~blas/pfHaskell/gentle/io.html 3/7
29/11/2014 UnaintroduccinagradableaHaskell:Entrada/Salida

putStr :: String -> IO ()


putStr s = sequence_ (map putChar s)

UnadelasdiferenciasentreHaskellylaprogramacinimperativaconvencionalpuedeverseenladefinicin
deputStr.Enunlenguajeimperativo,laaplicacindeunaversinimperativadeputCharsobreunacadena
decaracteresdaralugaraquestaseimprimiese.EnHaskell,lafuncinmapnoproduceefectoalguno.
Simplementecreaunalistadeacciones,unaporcadacarcterenlacadena.Laoperacindeplegadoenla
funcinsequence usaeloperador>>paracombinartodaslasaccionesindividualesdandolugarauna
accinnica.Laexpresinreturn ()usadaestotalmentenecesariafoldrnecesitaunaaccinnulaal
finaldelacadenadeaccionesquecrea(sobretodosilacadenadecaracteresestvaca!).

ElPreludeylasbibliotecasdellenguajecontienenvariasfuncionestilesparacombinaraccionesdeE/S.La
mayoradeestasfuncionesestngenrizadasamnadasarbitrariascualquierfuncinqueincluyaelcontexto
Monad m => ensutipopuedeserusadaconeltipoIO(yaquesteesunainstanciadelaclaseMonad).

7.3 Tratamientodeexcepciones

Hastaahora,hemosevitadoeltratamientodeexcepcionesenlasoperacionesdeE/S.Pero,quocurrirasi
getCharseencontraseelfinaldeunfichero?(Usaremoseltrminoerrorparadenotarelvalor_|_:unerror
norecuperablecomolanoterminacinounerrordepatrones.Lasexcepciones,porotrolado,puedenser
capturadasytratadasdentrodelamnadadeE/S.)Unmecanismodetratamiento,parecidoaldeStandard
ML,esusadoparatratarexcepcionestalescomo"ficheronoexistente".Noseusaningunasintaxiso
semnticaespecialeltratamientodeexcepcionesespartedeladefinicindelasoperacionesdeE/S.

LoserroressoncodificadosusandountipodedatosdenominadoIOError.Estetiporepresentatodaslas
posiblesexcepcionesquepuedenocurriralejecutaroperacionesdelamnadadeE/S.Eltipoesabstracto:
losconstructoresnoestndisponiblesparaelusuario.Algunospredicadospermiteninspeccionardatosdel
tipoIOError.Porejemplo,lafuncin

isEOFError :: IOError -> Bool

determinasielerrorquetomacomoargumentofuecausadoporunacondicindefinaldefichero.Alserel
tipoabstracto,losdiseadoresdellenguajepuedenaadirnuevostiposdeerroresalsistemasinnotofocarel
cambioenlaimplementacindeltipo.

UnmanejadordeexcepcionestienecomotipoIOError -> IO a.Lafuncincatchasociaunmanejador


deexcepcionesconunaaccinounconjuntodestas:

catch :: IO a -> (IOError -> IO a) -> IO a

Losargumentosdecatchsonunaaccinyelcorrespondientemanejador.Silaaccinconcluyesinerror,el
resultadodestaesdevueltosininvocaralmanejador.Enotrocaso,siseproduceunerror,steespasado
almanejadorcomounvalordetipoIOErrorylaaccinasociadaconelmanejadoresinvocada.Por
ejemplo,lasiguienteversindegetChardevuelveelcarctercorrespondientealsaltodelneacuandose
produceunerror:

getChar' :: IO Char
getChar' = getChar `catch` (\e -> return '\n')

Estoesbastantetoscoyaqueelmanejadortratacualquiererrordelmismomodo.Sisolosequierentratar
loserroresprovocadosporelfinaldeunfichero,elerrordebeserexaminado:

http://www.lcc.uma.es/~blas/pfHaskell/gentle/io.html 4/7
29/11/2014 UnaintroduccinagradableaHaskell:Entrada/Salida

getChar' :: IO Char
getChar' = getChar `catch` eofHandler where
eofHandler e = if isEofError e then return '\n' else ioError e

LafuncinioErrorelevaunaexcepcin,quepodrsertratadaporotromanejador.EltipodeioErrores

ioError :: IOError -> IO a

Esparecidaareturnperohacequeelcontrolsetransfieraalmanejadordeexcepcionesenvezdeala
siguienteoperacindeE/S.Elusoanidadodecatchestpermitido,ydalugaramanejadoresde
excepcionesanidados.

UsandogetChar',podemosredefinirgetLinecomoejemplodelusodemanejadoresanidados:

getLine' :: IO String
getLine' = catch getLine'' (\err -> return ("Error: " ++ show err)) where
getLine'' = do c <- getChar'
if c == '\n' then return ""
else do l <- getLine'
return (c:l)

ElmanejadordeexcepcionesanidadopermitequegetChar'trateloserroresdefindefichero,mientrasque
otroserroreshacenquegetLine'devuelvaunacadenadecaracterescomenzadopor"Error: ".

Haskelldefineunmanejadordeexcepcionespordefectoenelnivelmsexternodeunprogramacuyo
comportamientoconsisteenimprimirunmensajeconlaexcepcinproducidayfinalizarelprograma.

7.4 Ficheros,CanalesyManejadores

ApartedelusodelamnadadeE/Sydelmecanismodeexcepciones,elconjuntodeoperacionesdeE/S
proporcionadasporHaskellesmuyparecidoaldeotroslenguajes.Muchasdeestasoperacionesestn
definidasenlabibliotecaIOyporellodebenimportarseexplcitamenteparaserusadas(losmdulosyla
importacindeelementossonestudiadosenlaseccin11).Adems,muchasdeestasfuncionesson
descritasenelinformedelasbibliotecasdellenguajeenvezdeenelinformedellenguaje.

Laaperturadeunficheropermiteobtenerunmanejador(handle),contipoHandle,quepuedeserusado
paraoperacionesdeE/S.Alcerrarelmanejador,secierraelficheroasociado:

type FilePath = String -- nombres de ficheros


openFile :: FilePath -> IOMode -> IO Handle
hClose :: Handle -> IO ()
data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode

Losmanejadorestambinpuedenserasociadosconcanales(channels):puertosdecomunicacinqueno
estnconectadosdirectamenteconficheros.Algunosmanejadoresdeficherosestnpredefinidos,comopor
ejemplostdin(laentradaestndar),stdout(lasalidaestndar),ystderr(elcanaldeerroresestndar).
DosoperacionesdeE/SaniveldecaracteressonhGetCharyhPutChar,quetomanunmanejadorcomo
argumento.LafuncingetCharusadapreviamentepuedeserdefinidadelsiguientemodo:

getChar = hGetChar stdin

Haskelltambinpermiteleerelcontenidocompletodeunficheroocanalcomounacadenadecaracteres:

http://www.lcc.uma.es/~blas/pfHaskell/gentle/io.html 5/7
29/11/2014 UnaintroduccinagradableaHaskell:Entrada/Salida
getContents :: Handle -> String

PuedeparecerquegetContentsdebeleerinmediatamentetodoelcontenidodelficheroocanal,dando
lugaraunrendimientopobredesdeelpuntodevistadelespaciodememoriaytiempoutilizados.Sin
embargo,estonoesloqueocurre.EstosedebeaquegetContentsdevuelveunalistadecaracteres
"perezosa"(recurdesequelascadenasdecaracteressonlistasdecaracteresenHaskell),cuyoselementos
sonledosdelfichero"bajodemanda"(delmismomodoquesongeneradaslaslistasnormales).Puede
esperarsequelasimplementacionesdeestafuncinleanunoporunoloscaracteresdelficherosegnson
necesitadosparaproseguirelcmputorealizadoconlacadena.

Enelsiguienteejemplo,secopiaelcontenidodeunficheroenotro:

main = do fromHandle <- getAndOpenFile "Copy from: " ReadMode


toHandle <- getAndOpenFile "Copy to: " WriteMode
contents <- getContents fromHandle
hPutStr toHandle contents
hClose toHandle
putStr "Done."

getAndOpenFile :: String -> IOMode -> IO Handle


getAndOpenFile prompt mode =
do putStr prompt
name <- getLine
catch (openFile mode name)
(\_ -> do putStrLn "(Cannot open "++ name ++ "\n")
getAndOpenFile prompt mode)

AlusarlafuncinperezosagetContents,noesnecesarioleertodoelcontenidodelficheroenmemoriade
unavez.SihPutStrutilizaseunbufferparaescribirlacadenaenbloquesdetamaofijo,solounodeestos
bloquesdecaractereshadepermanecerenmemoriasimultneamente.Elficherodeentradaescerradode
modoimplcitocuandoelltimocarcteresledo.

7.5 Haskellylaprogramacinimperativa

LaprogramacindeoperacionesdeE/Spuededarlugaralasiguientereflexin:elestiloutilizadoseparece
sospechosamentealdelaprogramacinimperativa.Porejemplo,lafuncingetLine:

getLine = do c <- getChar


if c == '\n'
then return ""
else do l <- getLine
return (c:l)

guardaunasimilitudestrechaconelsiguientecdigoimperativo(quenoestescritoenningnlenguaje
concreto):

function getLine() {
c := getChar();

http://www.lcc.uma.es/~blas/pfHaskell/gentle/io.html 6/7
29/11/2014 UnaintroduccinagradableaHaskell:Entrada/Salida

if c == `\n` then return ""


else {l := getLine();
return c:l}}

As,despusdetodo,hareinventadoHaskellsimplementelaruedaimperativa?

Enciertosentido,larespuestaess.LamnadadeE/Sconstituyeunpequeosublenguajeimperativo
dentrodeHaskell,demodoquelacomponentedeE/Sdeunprogramapuedeparecersimilaralcdigode
unlenguajeimperativohabitual.Peroexisteunadiferenciaimportante:noesnecesariaunasemnticaespecial
paraello.Concretamente,elrazonamientoecuacionalnodejadeservlido.Laaparienciaimperativadel
cdigomondiconodenigraelaspectofuncionaldeHaskell.Unprogramadorfuncionalexpertodebeser
capazdeminimizarlacomponenteimperativadeunprograma,usandonicamentelamnadadeE/Senuna
partemnimadelprograma.Lamnadaclaramenteseparalascomponentesimperativayfuncionaldel
programa.Porelcontrario,loslenguajesimperativosconsubconjuntosfuncionalesnoestablecenunabarrera
biendefinidaentrelapartefuncionalpuraylaparteimperativadelprograma.

UnaIntroduccinagradableaHaskell
anteriorsiguienteinicio

http://www.lcc.uma.es/~blas/pfHaskell/gentle/io.html 7/7

Anda mungkin juga menyukai