Una fletxa, concepte desenvolupat per John Hughes, és una generalització d'una funció (amb un sol tipus d'entrada i un de sortida) on el resultat pot dependre d'efectes col·laterals o bé ser independent de l'entrada.[1]
Generalitza el concepte de Mònada, d'encadenament d'accions amb efecte col·lateral. Les mònades en són un cas particular anomenat ArrowApply. A més incorpora més informació de tipus que no el del resultat a les mònades, facilitant la composició i permetent evitar fuites d'ús de memòria.
L'abstracció Fletxa va ser descrita per primer cop per John Hughes al seu document "Generalising Monads to Arrows"[2][3] com a solució als problemes d'un analitzador sintàctic. Einar Karttunen en va escriure una implementació anomenada PArrows que aproxima les prestacions de l'actual biblioteca d'anàlisi sintàctic de Haskell anomenada Parsec.[3]
Aquí presentem un aspecte pràctic al Haskell. Per una versió amb fonaments teòrics consulteu la versió anglesa o completeu-ne, qui en sàpiga més, l'exposició.
Generalització de la Mònada - Fletxes de Kleisli
Seguint el document[2] i partint de l'encadenament de resultats de les computacions mònada
classMonadmwherereturn::a->ma-- generador de la mònada(>>=)::ma->(a->mb)->mb-- encadenament amb una funció del resultat de la precedent...-- es poden definir computacions encadenades Fletxa, a partir de les operacionsclassArrowefectewherearr::(b->c)->efectebc-- generador elevant una funció a la categoria d'efecte Fletxa(>>>)::efectebc->efectecd->efectebd-- combinació / encadenament d'efectes Fletxa-- prenem les computacions amb encadenament de la mònadaf::(a->mb)-- i en definim un tipusnewtypeKleislimab=Kleisli(a->mb)instance(Monadm)=>Arrow(Kleislim)where-- genera una fletxa elevant una funció-- arr :: (b -> c) -> (Kelisli m) b carrf=Kleisli(\x->return(fx))-- combinació de fletxes(Kleislif)>>>(Kleislig)=Kleisli(\x->(fx)>>=g)-- f,g :: (a -> m b)
Això mostra que les fletxes generalitzen les mònades. Per cada tipus mònada hi ha un tipus fletxa corresponent. (Del doc. "Generalizing Monads to Arrows".[2]
Fletxes i Parells
A les mònades les operacions return i (>>=) són suficients per efectuar càlculs com ara la suma de resultats de computacions.[2]
sumaM::Monadefecte=>efecteInt->efecteInt->efecteIntsumaMm_xm_y=m_x>>=(\x->m_y>>=(\y->return(x+y)))-- els parèntesis hi són per ressaltar
A les fletxes, arr i (>>>) no són suficients per efectuar el mateix càlcul. Cal trobar una manera de combinar el resultat de la primera computació amb el resultat de la segona per tenir-los a punt per al càlcul. Cal introduir un altre combinador (first: converteix una fletxa en una altra sobre parells, actuant sobre el primer element).[2]
classeArrowefectewherearr::(b->c)->efectebc-- eleva una funció a la categoria de l'efecte(>>>)::efectebc->efectecd->efectebd-- encadenament d'efectes Fletxafirst::efectebc->efecte(b,d)(c,d)-- converteix un efecte Fletxa en un sobre parells, actuant sobre el primer element.instance(Monadm)=>Arrow(Kleislim)where-- modifica el primer del parell amb el resultat de la computació ''f x'', on "f :: b -> m c"first(Kleislif)=Kleisli(\(x,y)->fx>>=(\z->return(z,y)))
El nou operador first converteix una fletxa de b a c en una fletxa sobre un parell, transformant-ne el primer element.[2]
-- ''second'' transforma el segon del parell; es defineix en termes de ''first''secondf=arrswap>>>firstf>>>arrswap-- (***) combina dues fletxes en una fletxa de parells, aplicant-les als diferents elements(***)::Arrowefecte=>efectebc->efectede->efecte(b,d)(c,e)f***g=firstf>>>secondg-- (&&&) obté un parell de l'aplicació de dues fletxes sobre una mateixa entrada(&&&)::Arrowefecte=>efectebc->efectebd->efecteb(c,d)f&&&g=arr(\b->(b,b))>>>(f***g)
Amb aquestes definicions la suma de resultats de computacions fletxa queda completament definida.[2]
A banda de la descripció precedent que correspon a un document de l'any 1998,[2] l'actual definició a GHC de la classe Arrow[4] deriva de la classe Category[5] que generalitza les funcions, amb les operacions identitat i composició.
La definició actual de (>>>), seqüenciació temporal, és igual a la composició de funcions (classe Category).
La classe Arrow (derivada de Category) requereix només la definició mínima de arr i first, incorporant a més, dins la classe, les operacions second, (***) i (&&&) corresponent a les descrites més amunt del document de John Hughes de 1998.[2]
combinació de Fletxes i sintaxi específica
Podem combinar les fletxes aparellant entrades i sortides
en tipus producte Tupla2 (classe Arrow)
(***): combina aparellant entrades i sortides
(&&&): mateix tipus d'entrada, aparella sortides
en tipus suma Either (classe ArrowChoice)
(+++): suma de tipus a l'entrada i a la sortida
(|||): suma de tipus a l'entrada, mateix tipus de sortida
La clàusula "proc" permet definir un filtre partint d'altres filtres. Exemple:[6]
-- combinació de dos filtres ''fletxa'' f i g resultant un altre filtre{-# LANGUAGE Arrows #-}importControl.ArrowaddA::Arrowefecte=>efectebInt->efectebInt->efectebInt-- x (entrada del filtre) és del tipus 'b', segon element de la terna de la Fletxa resultant.addAfg=procx->doy<-f-<x-- executa l'acció/efecte d'aplicar 'f' a 'x' obtenint el resultat 'y'z<-g-<x-- executa l'acció/efecte d'aplicar 'g' a 'x' obtenint el resultat 'z'returnA-<y+z-- aplica (y + z) al filtre de sortida ('returnA'), com a resultat del bloc 'proc'
Equivalent de la fletxa addA expressada en operacions canòniques del tipus[7]
addAfg=(f&&&g)>>>arr(\(y,z)->y+z)
alternatives ArrowChoice
La classe ArrowChoice ofereix un tractament d'entrades de tipus Either a b permetent tractaments alternatius segons siguin Left o Right.
Millorant els comentaris del mòdul Control.Arrow:
classArrowefecte=>ArrowChoiceefectewhere-- ''left f'' passa el contingut de les entrades etiquetades Left per f -- re-etiquetant el resultat, mentre que les Right passen inalterades left::efectebc->efecte(Eitherbd)(Eithercd)-- ''right f'' passa el contingut de les entrades etiquetades Right per f -- re-etiquetant el resultat, mentre que les Left passen inalteradesright::efectebc->efecte(Eitherdb)(Eitherdc)-- ''f ||| g'' passa les Left per f i les Right per g(|||)::efectebd->efectecd->efecte(Eitherbc)d-- ''f +++ g'' passa les Left per f i les Right per g--, re-etiquetant el resultat amb Left o Right.(+++)::efectebc->efecteb'c'->efecte(Eitherbb')(Eithercc')-- és el cas quef+++g≡leftf>>>rightgleftf≡f+++arridrightf≡arrid+++f
Exemples
Ent./Sort. amb fletxes de Kleisli (mònades elevades a fletxes)
Partint de la definició esmentada més amunt, lectura d'any, mes i dia amb comprovació.
{-# LANGUAGE Arrows #-}importControl.ArrowimportSystem.IO(hFlush,stdout)importData.List(intercalate)importData.Char(toLower)importData.Bits((.&.))-- bits And-- llegeix un valor enterllegeixDia,llegeixAny::()->IOIntllegeixDia()=putStrLn"Entreu dia (dd):">>hFlushstdout>>getLine>>=(return.read)llegeixAny()=putStrLn"Entreu any (aaaa):">>hFlushstdout>>getLine>>=(return.read)-- retorna mes en minúscules, limitat a 3 lletresllegeixMes()=putStrLn"Entreu mes (mmm):">>hFlushstdout>>getLine>>=(return.(maptoLower).take3)-- :: IO StringfletxaLlegirDia=Kleisli{runKleisli=llegeixDia}-- inicialització per camps, :: Kleisli IO () IntfletxaLlegirMes=KleislillegeixMes-- inicialització posicional, :: Kleisli IO () StringfletxaLlegirAny=KleislillegeixAny-- :: Kleisli IO () Int-- fletxaLlegeixIComprovaDia :: Kleisli IO (Int, String) (Either IOError (Int, String, Int))fletxaLlegeixIComprovaDia=proc(iAny,strMes)->doiDia<-fletxaLlegirDia-<()letésAnyDeTraspàs=let(iSegle,iAnyDelSegle)=iAny`divMod`100inifiAnyDelSegle==0-- comprova bits menorstheniSegle.&.0x03==0-- segle és múltiple de 4elseiAnyDelSegle.&.0x03==0-- any del segle és múltiple de 4 i (/= 0)letésDiaIŀlegal=(iDia<=0)||(strMes=="feb"&&((ésAnyDeTraspàs&&iDia>29)||(notésAnyDeTraspàs&&iDia>28)))||((strMes`elem`["abr","jun","set","nov"])&&(iDia>30))||iDia>31ifésDiaIŀlegalthenreturnA-<Left$userError"dia iŀlegal"elsereturnA-<Right(iAny,strMes,iDia)fletxaComposaData=proc(iAny,strMes,iDia)->doreturnA-<intercalate"/"[showiDia,strMes,showiAny]main=do-- ''f &&& g'' aplica la mateixa entrada a ''f'' i a ''g'', i n'aparella els resultatsletfletxa=(fletxaLlegirAny&&&fletxaLlegirMes)>>>fletxaLlegeixIComprovaDia>>>rightfletxaComposaData-- processa els x tals que Right x (vegeu ArrowChoice)eitherData<-runKleislifletxa()caseeitherDataofLefterr->ioErrorerr-- dispara excep.RightstrData->putStrLn$"correcte: "++strData
runhaskell provaFletxa
Entreu any (aaaa):
1904
Entreu mes (mmm):
feb
Entreu dia (dd):
29
correcte: 29/feb/1904
Fletxes amb resultats múltiples - La biblioteca HXT
La biblioteca HXT (Haskell XML Toolbox) tracta els arbres XML amb filtres fletxa. Vegeu.[8][9][10]
Per evitar l'ús d'excepcions, HXT dona sempre els múltiples resultats (subarbres) dels filtres en una llista, amb zero o més elements, segons l'èxit i el nombre dels elements resultants.
-- tipus de la fletxa ListArrow de HXTnewtypeLAab=LA{runLA::a->[b]}-- generador de fletxes partint d'un filtre d'un sol resultat embolcallant-lo en una llista.instanceArrowLAwherearrf=LA$\x->[fx]-- cas de f :: a -> b...-- generador de fletxes partint d'un filtre que ja ofereix els resultats en una llista.instanceArrowListLAwherearrLf=LAf-- cas de f :: a -> [b]-- composició de fletxes hxt-- aplica el filtre de la fletxa subseqüent a cadascun dels elements -- de la llista de resultats de la primera i en concatena les llistes de resultatsinstanceCategoryLAwhere...LAg.LAf=LA$concatMapg.f-- Definició de (>>>) a Control.Arrow de la biblioteca de GHC basada en (.) de Category(>>>)::Categorycat=>catab->catbc->catacf>>>g=g.f
Aquí la majoria dels filtres Fletxa que es veuen són del de tipus
:: (ArrowXML efecte) => efecte XmlTree c -- i la sortida és de tipus c (si c és llista) o, altrament, [c]
, el resultat de la seva aplicació serà, excepte al darrer pas, la llista dels subArbres resultants del filtre, i a cadascun dels quals s'aplicarà el filtre següent.
-- fitxer llista-llibres.hs -- Extreure el títol i l'autor dels llibres de l'XML d'entrada{-# LANGUAGE Arrows, PackageImports, RecordWildCards #-}-- pragma amb extensions del llenguatge,-- equivalent a -X<opcio> a línia d'ordres de compilaciómoduleMain(main)whereimportqualifiedControl.MonadasMonadimportSystem.Environment(getArgs)importSystem.Exit(exitSuccess,exitWith,ExitCode(..))importControl.Arrowimport"hxt"Text.XML.HXT.Coreimport"hxt"Text.XML.HXT.DOM.XmlKeywordsimport"hxt-xpath"Text.XML.HXT.XPath.Arrows(getXPathTrees)-- per a documentació (però ja queda importat per Text.XML.HXT.Core) :-- import "hxt" Text.XML.HXT.Arrow.ReadDocument (readDocument)dataTLlibre=Llibre{-- registre per a les dades que interessenllibTitol::String,llibAutor::String}deriving(Eq)instanceShowTLlibrewhere-- personalitzant-ne la impressióshow(Llibre{..})="\nTítol: "++llibTitol++"\nAutor: "++llibAutorobtenirText::(ArrowXmlefecte)=>efecteXmlTreeString-- genera un filtre Arrow de XmlTree a String, -- (la composició de filtres ArrowXml concatena (++) les sortides en una sola tira)obtenirText=getChildren>>>getTextllibre_del_subarbreElementXml_Llibre::(ArrowXmlefecte)=>efecteXmlTreeTLlibre-- genera un filtre Arrow de XmlTree a TLlibre llibre_del_subarbreElementXml_Llibre=procxmlTree_llibre->dotítol<-obtenirText<<<getXPathTrees"/llibre/títol"-<xmlTree_llibreautor<-obtenirText<<<getXPathTrees"/llibre/autor"-<xmlTree_llibrereturnA-<Llibre{llibTitol=títol,llibAutor=autor}processa::String->IO[TLlibre]processanomFitxerXml=doletfletxa_Llibres=readDocument[withValidateno]nomFitxerXml>>>getXPathTrees"//llibre">>>llibre_del_subarbreElementXml_Llibre-- runX aplica sobre un XmlTree buit inicial definit internament, el filtre fletxa de l'expressióllibres<-runXfletxa_Llibresreturnllibresmain=doargs<-getArgscaseargsof[]->doputStrLn"afegiu el fitxer xml dels llibres"exitWith$ExitFailure1(arg:_)->dollibres<-processaargMonad.mapM_printllibresexitSuccess
compilació i execució (compilat amb ghc 7.6.3, hxt-9.3.1.3 i hxt-xpath-9.1.2.1):