Característiques del llenguatge Haskell

Característiques del llenguatge de programació Haskell

Fonamentat en el càlcul lambda modifica

El càlcul lambda es basa (entre d'altres regles) en la reducció d'expressions, per l'aplicació sistemàtica d'una funció al primer paràmetre, obtenint una funció dels paràmetres que li manquen.

Això facilita l'ús d'aplicacions parcials (de menys paràmetres del que correspon a la seva aritat), en funcions d'ordre superior.

-- (->) indica retorn
f :: a -> r -- indica el tipus d'una funció f amb paràm. de tipus 'a' que retorna un tipus 'r'
 -- els tipus en minúscula són variables, 
 -- quan es repeteix una variable de tipus en una signatura, 
 -- indica que el tipus ha de coincidir amb el de la primera posició

-- (->) és associatiu per la dreta:
-- l'aplicació parcial del primer paràmetre, retorna una funció dels paràmetres restants
(a -> b -> c)  (a -> (b -> c))

separació de codi funcional pur i codi amb efectes col·laterals modifica

L'assignació de tipus específics al codi impur (amb efectes sobre l'entorn: ent./sort., canvis d'estat, ...) facilita la distinció del codi pur (el que només depèn dels paràmetres) per a millores de rendiment mitjançant el paral·lelisme (procés en paral·lel).

codi pur (el resultat només depèn dels paràmetres)
  • L'ordre de les operacions és irrellevant.
  • l'ordre d'execució dels operadors en una expressió queda a discreció de l'optimitzador del compilador, per tant serà indeterminat.
-- codi pur

f x y = (x * 2) + (y * 4)
codi impur (modifica o depèn de l'estat de l'entorn)
  • Una acció (com ara: getLine) produeix un resultat. Un bloc d'accions pot expressar-se de diverses maneres.
  • L'ordre és rellevant (el resultat de la seq. d'efectes coŀlaterals depèn de l'ordre d'execució)
  • El tipus de l'acció/efecte és el de l'entorn que l'acció modifica parametritzat pel tipus del resultat
-- codi impur

acció :: IO Int   -- tipus de l'entorn, aplicat al tipus del resultat
acció = do
 putStrLn "entreu nombre enter"
 hFlush stdout
 str <- getLine -- (resultat <- acció)
 let { x = read str :: Int -- el bloc 'let' facilita definicions basades en resultats precedents
     ; y = x * 2 }
 return y -- ''return'' (o bé ''pure'') és el generador d'una acció simple 
-- que ofereix el paràmetre com a resultat en l'entorn del context

Les accions es poden compondre

  • per encadenament del resultat (serialització temporal)
-- composició monàdica (>>=)
bloc :: IO ()
bloc = getLine >>= \x -> putStrLn x -- imprimeix el resultat de l'entrada
  • per combinació dels resultats (les accions poden ser simultànies)
-- composició aplicativa (<*>)
bloc :: IO (String, String)    -- retorna un parell de pàgines
bloc = runConcurrently $       -- fem ús de la biblio async
 (,) <$> Concurrently (getURL "url1")  -- aplica el combinador (,) al resultat de la primera acció
 <*> Concurrently (getURL "url2") -- quina aplicació parcial s'aplica al resultat de l'acció següent

separació d'especificació i execució modifica

El codi d'un mòdul no s'executa en l'ordre d'especificació, sinó mitjançant l'avaluació no-estricta, partint de l'expressió arrel main del mòdul Main que processa els arguments de la crida des de la consola de comandes.

avaluació no-estricta, normalment tardana - Thunks modifica

En avaluació tardana les expressions, definides en variables i paràmetres, no es calculen quan es formulen sinó en el moment que se'n demana el valor en una altra expressió. Correspon a l'expressió anglesa lazy evaluation.

Haskell és d'avaluació no-estricta, que vol dir que l'avaluació va de l'arrel a les branques en l'arbre d'operacions de l'expressió. L'avaluació comença per l'expressió definida a la clàusula main. En en cas de (b * c + a) primer s'avalua el (+) i després el (*), al revés dels llenguatges estrictes.[1] Les altres definicions s'avaluen sota comanda (avaluació tardana). D'aquesta manera només s'avaluen les subexpressions que facin falta, estalviant càlculs.

A la pràctica Haskell no és un llenguatge purament tardà. L'encaix de patrons és, normalment, estricte —almenys s'avalua si tindrà èxit—, i un analitzador d' estrictesa[2] determina quan un terme serà sempre requerit en les expressions, i converteix la seva avaluació en primerenca (ang.: eager evaluation) .[1] El programador també pot forçar l'estrictesa com s'explica més avall.

Thunk és el nom de la càpsula de codi corresponent a cada expressió pendent d'avaluar.[3]

separació d'estructura i operacions (comparat amb la POO) modifica

Haskell. a diferència dels lleng. amb orientació a objectes separa estructures de dades i comportament.

Els tipus de dades no impliquen operacions. Si són simples (ex.: Int, Double), defineixen el conjunt de valors, i si són compostos (tipus producte o tipus suma (unió discriminada de tipus)) defineixen l'estructura (clàusula data).

Les operacions sobre el tipus poden agrupar-se en "classes de tipus", equivalents en la orientació a objectes a les interfícies, i són com mòduls abstractes parametritzats pel tipus. Per fer-ne ús cal generar una instància de la classe genèrica per al tipus concret, aportant la implementació.

-- signatura
class CForma t where -- parametritzada per a un tipus t
 perímetre :: t -> Double -- les variables de tipus als mètodes coincidents a la declaració de classe
 àrea :: t -> Double -- es refereixen a aquells tipus que implementin la classe.

type TCostat = Double -- ''type'' dona un àlies a una expressió de tipus

-- tipus de l'estructura, amb ''constructor'' seguit de components
data TRectangle = Rectangle TCostat TCostat -- habitualment es fa servir el mateix nom per al tipus i el constructor (són espais de noms diferents)

-- implementació de la signatura
-- generem una instància del genèric abstracte CForma per al tipus TRectangle 
-- que haurà d'estar visible en l'àmbit de les expressions que en facin ús
instance CForma TRectangle where 
 perímetre (Rectangle x y) = 2 * (x + y)
 àrea (Rectangle x y) = x * y
{-# LANGUAGE NamedFieldPuns #-} -- sintaxi de literals de registres simplificada

data TPersona = Persona {nom::String, edat::Int}
 deriving (Eq, Show) -- demana al compilador que derivi instàncies de classes bàsiques
 -- partint de les representacions internes del tipus

-- enumeració: unió de 'constructors' d'aritat 0 (sense paràmetres)
data TLlenguatge = Lleng_FORTRAN | Lleng_JAVA | Lleng_HASKELL 
 deriving (Eq, Show, Ord, Enum)

data TProgramador = Programador { persona::TPersona, llenguatges::[TLlenguatge] } 
 deriving (Eq, Show)

class CPersona t where -- un tipus t que implementi CPersona haurà de proveir les funcions ..
 fer_anys :: t -> t

-- defineix la classe CProgramador per aquells tipus t
-- tals que (CPersona t) -- que implementin CPersona
-- requereix la visibilitat, en el context d'ús, d'una instància de CPersona per al tipus involucrat.

class (CPersona t) => CProgramador t where
 aprendre_llenguatge :: TLlenguatge -> t -> t

-- generem una instància de CPersona per al tipus TPersona implementant la signatura de la classe

-- L'operador (@) (variable @ patró), anomenat ''com el patró'', unifica la variable amb el valor encaixat al patró

-- El lligam dels literals de camps dels registres "{camp = expressió, ...}" al constructor o variable precedent
-- té major precedència que l'aplicació o que cap dels operadors, per això no cal agrupar-los entre parèntesis.

instance CPersona TPersona where
 fer_anys p @ Persona {edat} = -- {edat} equival a {edat = edat} per l'extensió NamedFieldPuns
 p {edat = edat +1} -- el primer és el nom del camp, el segon és la variable del patró.

instance CPersona TProgramador where
 fer_anys prog @ Programador {persona} =
 prog {persona = fer_anys persona}

instance CProgramador TProgramador where
 aprendre_llenguatge llenguatge_nou prog @ Programador {llenguatges} =
 if not (llenguatge_nou `elem` llenguatges)
 then prog {llenguatges = llenguatge_nou : llenguatges}
 else prog

-- valors

joan_persona = Persona { nom="Joan", edat=50}

joan_programador = Programador { persona = joan_persona, 
 llenguatges = [Lleng_FORTRAN, Lleng_JAVA] }

-- l'operador (.) de composició de funcions permet especificar la composició que s'aplicarà de dreta cap a l'esquerra
fer_més_savi = fer_anys. (aprendre_llenguatge Lleng_HASKELL) -- el punt (composició) ha d'ésser envoltat d'espais

-- l'operador (>>>) de Control.Category permet especificar la composició de funcions d'esquerra a dreta
fer_més_savi = (aprendre_llenguatge Lleng_HASKELL) >>> fer_anys

joan_més_savi = fer_més_savi joan_programador

main = print joan_més_savi

Per ser un llenguatge d'avaluació no-estricta, l'execució comença per l'avaluació de la clàusula d'engegada main, calculant les expressions que calguin quan se'n demana el valor (avaluació tardana) i no quan es defineix l'expressió (optimitzacions a banda).

associació única de valors a identificadors modifica

No es poden fer dues associacions al mateix identificador en el mateix àmbit (a nivell de mòdul, let o where)

Composició de tipus modifica

Els tipus (conjunts de valors) es poden compondre, partint dels tipus primitius i enumeracions, en tipus producte (etiquetats: els registres; anònims: les tuples) i tipus suma (cas de variants no-parametritzades: les enumeracions; variants parametritzades: unió discriminada), distingint-los amb un constructor que ens permetrà discriminar-los en l'encaix de patrons. Vegeu #tipus algebraics.

Les famílies de tipus permeten generalitzar els tipus basant-se en un o més índexs i concretar-ne instàncies de diferent estructura.

Composició de comportament modifica

Les classes designen la signatura (operacions) referides a variables de tipus i no a tipus concrets (com a les signatures del ML Estàndard o cas dels interface del Java referits al tipus que els implementi). Les classes poden requerir operacions d'altres classes que cal esmentar com a requeriment de context (requeriment de visibilitat d'una instància de la classe per al tipus en el context d'ús) (en el Java s'esmentarien a les clàusules extends o bé implements). Vegeu #Encapsulament estil O.O..

Els tipus poden aportar implementacions específiques de la signatura d'una classe o incorporar les implementacions per defecte definides a la classe.

No hi ha l'herència pare/fill com entre les classes de la O.O. estalviant el temps esmerçat en el despatx per taula de mètodes virtuals (vtable) i els conflictes de l'herència.

Les implementacions poden establir requeriments de context per a les variables de tipus, addicionals als establerts a la signatura.

Vegeu exemple

Col·leccions heterogènies modifica

Les llistes són de tipus homogenis. Per encabir elements de tipus divers per un tractament comú, cal caracteritzar-los amb un tipus existencial que admeti components d' aquells tipus que implementin la interfície que inclogui l'operació que s'hi vol aplicar.

Variables modifica

Haskell disposa de variables globals IORef's similars a les ref del ML Estàndard, i variables STRef's d'àmbit local per encapsular actualitzacions destructives.

Per al cas de sincronització blocant, Haskell fa servir com a variables de modificació sincronitzada el mecanisme de comunicació per bústies on una MVar és una bústia d'un sol element. Les operacions sobre la bústia són posar i treure. Al retirar un element de la bústia, la gestió de memòria de l'element queda en l'àmbit del procés que l'obté. Per consultar-ne el contingut l'has de treure, deixant la bústia buida. Per al cas de comunicació amb encuament, Chan i BoundedChan són bústies amb encuament i blocatge.

Per la concurrència amb memòria transaccional per programari (STM), hi ha les variables transaccionals TVar, TMVar (MVar's protegides per transaccions) i bústies TChan.

Efectes col·laterals modifica

Una acció amb efecte (un canvi) pot produir, a més, un resultat computable (per ex. l'entrada de consola: getline). El tipus de l'efecte vindrà parametritzat pel tipus del resultat.

El més emprat és l'efecte global IO (engloba entrada/sortida, variables globals IORef's i tractament d'excepcions) i el tipus d'una acció d'efecte global serà (IO tipusDelResultat).

Vegeu #Efectes

Composició d'efectes modifica

Per a les mònades vegeu Transformadors de mònades.

L'equivalent per a les fletxes són els Arrow Functors. Vegeu doc. "Generalizing Monads to Arrows".[4]

Lèxic modifica

Identificadors modifica

Un identificador consisteix en una lletra seguida de zero o més {lletra, dígit, guió baix, o bé apòstrof}. L'especificació no fa esment de límits en la llargada dels identificadors.[5]

Haskell98 admet caràcters no anglosaxons als identificadors segons la codificació Unicode.[6] S'admeten caràcters accentuats, la ç, l'apòstrof, el caràcter l· (ela geminada), si el podeu compondre (AltGr+L al Linux), però no el punt volat (ang:mid dot) (·) considerat com a separador.

L'apòstrof també pot formar part del nom, per a un ús amb estil matemàtic

v, v', v''

, però també com a part interna d'un identificador.

A l'intèrpret GHCi sobre Linux/Ubuntu amb LANG=ca_ES.UTF-8:

 ghci # a l'intèrpret les definicions van precedides de ''let''
 # igual que al codi dins els blocs ''do''
 # distingint-les de les expressions a avaluar
 # Si la versió < 7.4, no admet declaracions de tipus (cal carregar-les d'un fitxer).
 # Ajuda teclejant '':help''

 Prelude> let alçada = 1.5
 Prelude> alçada
 1.5
 Prelude> let opinió = 2
 Prelude> let funcPrima' x = x +1
 Prelude> let l'internauta=123
 Prelude> let {funcSegona'' :: Int -> Int; funcSegona'' x = x * 2}
 Prelude> let col·legi = "abc" -- amb ela geminada Unicode (AltGr+l al Linux)

La pragma {-# LANGUAGE UnicodeSyntax #-} permet la substitució de determinades seqüències per caràcters Unicode equivalents.[7]

ent/sort. amb caràcters no anglosaxons
A partir de la versió 6.12.1, el compilador GHC incorporarà la codificació de caràcters del sistema subjacent per al tractament d'ent./sort. de les tires de caràcters.
Si no disposem d'aquesta versió podem tractar fitxers UTF-8 amb el paquet utf8-string.

Grafia modifica

començant per lletra modifica

  • cas de primera lletra minúscula, indica variable (de valor o de tipus segons el context)
  • cas de primera lletra majúscula, indica constructor (de valor o de tipus segons el context)

començant per símbol modifica

cas de guió baix
  • patró comodí '_': patró irrefutable quin valor encaixat no interessa per al càlcul
  • patró comodí amb identificador _abc123 : quan una variable no es fa servir però s'hi manté per documentar, el guió baix precedint la variable no utilitzada evita que el compilador mostri un missatge d'atenció (Warning) per inútil.[8]
començant per ':' (caràcter dos-punts)
constructors no alfanumèrics binaris definits per a ser emprats en posició infix segons la definició de sintaxi del Haskell,[9][10] per exemple:
  • (:) constructor cons de llistes (ex.: cap : cua)
  • (:+) constructor de nombres complexos (ex.: part_real :+ part_imag)
  • (:.) constructor de dimensions i d'índexs per a vectors pluridimensionals (ex.matriu 3x3: (Z:. 3 :. 3)) al paquet Repa
començant per '?'
variables que es passen com a paràmetres implícits (associables a l'àmbit de la funció que ha fet la crida, semblant al pas de paràmetres per referència del lleng. C) amb l'extensió ImplicitParams[11]
començant per altres símbols
operadors

Sintaxi modifica

Vegeu. produccions sintàctiques del Haskell2010.[12]

comentaris modifica

 {-
 comentari multilínia
 {- comentari niuat
 -}
 -}
 -- comentari fins a fi de línia

comentaris d'autodocumentació modifica

Comentaris[13] per ésser extractats a un resum mitjançant el programa haddock[14] amb comentaris de blocs entre delimitadors {-| -} i comentaris de línia precedits per -- <comanda>

Els comentaris admeten llenguatge de marcatge (els delimitadors en determinen el tractament):

'identificador_enllaçat' 
/èmfasi/ 
__ressaltat__ 
@monoespaiat@ 
<http://domini.com/camí-al-document etiqueta de la URL>
<<fitxer_imatge.png etiqueta de la imatge>>
{-|
Module : W
Description : Short description
Copyright : (c) Some Guy, 2013
 Someone Else, 2014
License : GPL-3
Maintainer : sample@email.com
Stability : experimental
Portability : POSIX

Descripció llarga del mòdul que pot contenir llenguatge de marcatge.
-}
module W where

-- == capçalera html h2
-- === capçalera html h3

{-| documentació en bloc que precedeix una declaració
-}

-- | documentació en línies que precedeix una declaració
-- continuació de la documentació

arrel2 = sqrt -- ^ documentació a posteriori

-- ^ documentació a posteriori, referida a la definició precedent
  • invocació per generar documentació (per la sortida en html: opció -h):
haddock -h fitxers.hs -o docdir

En el cas molt probable que hi hagi símbols enllaçats definits en altres paquets sortiran missatges de manca d'enllaç amb la documentació dels altres paquets

"Warning: Haddock could not find link destinations for ..(paquets)"

Per evitar-los cal generar enllaços a la doc. dels altres paquets, esmentant per cada paquet l'opció --read-interface (-i) amb el camí de la doc i el fitxer d'interfície "haddock" (extensió: .haddock) (que es pot generar amb l'opció --dump-interface) per exemple:

-i /usr/share/doc/ghc6-doc/html/libraries/base-4.2.0.0,/usr/lib/ghc-6.12.1/haddock/base-4.2.0.0/base.haddock

Espais de noms modifica

Hi ha sis menes de noms que constitueixen espais independents (es pot fer servir el mateix identificador per al que convingui en cada espai)[15]

  • variables
  • constructors de dades (els de la dreta de l'assignació en una instrucció data)
  • variables de tipus
  • noms (constructors) de tipus
  • noms de classes de tipus
  • noms de mòdul

Un mateix identificador no pot ser emprat com a constructor de tipus i com a classe en el mateix àmbit.[15]

Exemples de possible confusió:

  • L'identificador ArithException és un constructor (dins el tipus Exception de H98) i també un nom de tipus ArithException

Vegeu-ho aquí

És habitual fer coincidir el nom del tipus i el del constructor, en una clàusula data d'un únic constructor, o bé en una clàusula newtype.

definició de blocs modifica

De dues maneres possibles:

  1. La compacta, blocs definits per claus {} i separant les instruccions per punt-i-coma
    class Eq t where { (==) :: t -> t -> Bool ; (/=) :: t -> t -> Bool }
    
    -- a l'intèrpret ghci
    Prelude> let { incr :: Int -> Int ; incr x = x + 1}
    
  2. L'elegant, blocs definits per la profunditat del sagnat (marge esquerre).[16] Els punt-i-coma al final de línia es poden estalviar. Substituint les claus '{', '}' de l'exemple anterior per salts de línia amb augment o disminució del sagnat:
    class Eq t where
     (==) :: t -> t -> Bool
     (/=) :: t -> t -> Bool
    
    -- a l'intèrpret ghci, amb el mode multilínia :{ :}
    Prelude> :{
    Prelude| let incr :: Int -> Int
    Prelude| incr x = x + 1 -- cal alinear els blocs 'let' al primer identificador
    Prelude| :}
    Prelude>
    

Per fer macros de CPP convé fer servir la sintaxi de claus, doncs les macros no generen salts de línia.

Funcions binàries en posició infix en expressions modifica

Qualsevol funció de dos o més operands es pot emprar en posició infix (entremig) si posem el seu nom entre cometes revesses entre el primer i el segon, fetes amb la tecla de l'accent greu seguida de la tecla espai. En anglès s'hi refereixen per backquotes

 afegint x y = x + y

 valor = 4 `afegint` 3 -- l'ús com a operador infix li atorga característiques dels operadors.
 -- per defecte: associativitat per l'esquerre i precedència màxima

Els noms en infix admeten declaracions d'associativitat i precedència, per defecte màxima precedència i associativitat per l'esquerre. Els noms d'operacions habituals usats en infix que no tenen altre operador (`div`, `mod`, ...) tenen assignades l'associativitat i precedència del grup d'operacions al qual pertanyen.[17]

operadors d'aplicació ($) i (&) modifica

L'operador d'aplicació ($) és molt freqüent i estalvia parèntesis en aplicar una funció a una expressió de més d'un terme.

  • excepte quan el paràmetre és una funció polimòrfica sense declaració de tipus, perquè ($) és una funció amb polimorfisme de rang 1 i no admet funcions polimòrfiques com a paràmetres, com s'explica a la ref.[18]
  • ($) té la menor de les precedències dels operadors i associativitat per la dreta.[17]
f $ x  f x -- ($) infixr 0

f $ g z $ x + y  f (g z (x + y)) -- excepte si g no té declaració de tipus

Quan el nombre d'aplicacions amb ($) passa de dues es complica copsar el sentit de l'expressió i és preferible utilitzar l'aplicació cap enrere (&) obtenint un estil de navegació de dades.

import Data.Function ((&)) -- aplic. cap enrere, des de GHC 7.10.1

-- x & f ≡ f x -- associativitat i precedència: (infixl 1)

-- imprimir els dobles dels parells de llista amb aplicació cap enrere (&)
v = llista & filter ésParell -- filtra
 & map doblar -- aplica funció als elements
 & show -- textualitza a String
 & putStrLn -- imprimeix a consola

-- (&) té major precedència que ($) que és (infixr 0): x & f $ y ≡ (x & f) $ y
-- a ghci:
Prelude Data.Function> 2 & (/) $ 4 -- ≡ (/) 2 4
0.5
  • Hi ha un altre ús de $ com a prefix a GHC (prefixant l'identificador com $identif, o bé prefixant el parèntesi com $(expr)) per avaluar un identificador o bé una expressió en temps de compilació.

Tipus i classes modifica

El tipus indica únicament el domini.

Les classes de tipus designen un grup d'operacions que un tipus pot implementar. La paraula reservada class introdueix un mòdul genèric de signatures d'operacions, indexat pel paràmetre de tipus, amb possible implementació per defecte de les operacions.

Les classes de tipus només poden contenir signatures i implementacions de funcions que facin ús del paràmetre de la classe.

Les classes de tipus s'assemblen als mòduls genèrics de l'Ada amb un paràmetre de tipus, amb l'exclusió de tot allò que no faci ús del paràmetre de tipus del genèric (la classe); també es poden assimilar als Interface de Java assimilant el paràmetre de tipus al tipus de l'objecte Java que els ha d'implementar.

class Eq t where
 (==) :: t -> t -> Bool
 (/=) :: t -> t -> Bool

 -- implementació per defecte
 x /= y = not (x == y)
 x == y = not (x /= y)
 -- estan definides circularment, caldrà implementar-ne només una, en definir-ne una instància

 {-# MINIMAL (==) | (/=) #-} -- indicació de quins mètodes cal implementar com a mínim

classes i clàusula deriving modifica

La clàusula deriving demana al compilador que derivi instàncies (implementacions)[19][20] de classes bàsiques[21] a partir de les representacions internes dels tipus. Aquestes classes són:

classe requeriment
de context
(requereix visibilitat d'instàncies
de les classes esmentades
en l'àmbit d'ús)
descripció operacions afegides
Eq α[22] Igualable (==) (/=)
Ord α[23] Eq α Ordenable (<), (<=), ..., (compare: amb resultat ternari Ordering), (max), (min)
Enum α[24] Enumerable (succ: successor) (pred: predecessor)
(fromEnum α: enter ordinal corresponent)
(toEnum n :: TipusEnumerat: enumerat corresponent a l'enter ordinal, la restricció de tipus indica el domini, defineix una Successió)
Bounded α[25] Acotat (minBound::TipusAcotat: cota inferior, la restricció de tipus indica el domini) (maxBound::TipusAcotat: cota superior)
Ix α[26] Ord α Indexador (index: índex basat en 0), (inRange), (rangeSize), (range: llista compresos)

Classes {Show, Read} per la representació textual de dades numèriques:

classe descripció operacions
Show α[27] Textualitzable a String (show), (showList), (showsPrec).
Read α[28] Llegible des de String (readList), (readsPrec)
  • Els caràcters no ASCII (quina repr. depèn de la codificació) en un tipus String es mostren amb un codi numèric:
$ ghci
Prelude> "alçada" -- sortida via 'show'
"al\231ada"
Prelude> putStrLn "alçada" -- sortida via System.IO (codificada segons el sistema subjacent)
alçada
  • Per la manipulació de texts no anglosaxona, cal fer servir el tipus Text del paquet del mateix nom (encapsula un vector de caràcters codificat segons UTF-16).[29]
  • el mòdul Numèric proporciona un ventall més ampli de conversions entre text i nombres.[30]

Classes de tipus numèrics:

classe requeriment
de context
descripció operacions afegides
Num α[31] Numèric, estructura d'Anell unitari op. binaris: (+), (-), (*)
literals elements neutres: 0, 1
op. unaris: (negate: invers de la suma), (abs), (signum: retorna {-1,0,1});
llei: {abs x * signum x == x}
(fromInteger: per decodificar literals enters o convertir de precisió il·limitada)
Bits α[32] Num α Adreçable als bits (.&. :i), (.|. :o), xor, complement,
shift, rotate, ...
Real α[33] (Num α, Ord α) Estructura d'Anell unitari ordenat (toRational: obtenció de l'aproximat Racional,
ex.: toRational $ sqrt 2)
Integral α[34] (Real α, Enum α) Íntegre; Estructura d'Anell unitari ordenat i euclidià {(div), (mod), (divMod)} -- divisió partint cap a menys infinit euclidiana
{(quot: quocient), (rem: romanent), (quotRem)} -- divisió partint cap a zero NO euclidiana
(toInteger: a sencer de precisió il·limitada)
  • Classes per al suport de racionals i reals.
classe requeriment
de context
descripció operacions afegides
Fractional α[35] Num α Fraccionable
(estructura de cos)
(recip: recíproc, invers del producte), (/),
(fromRational: per decodificar literals amb part decimal o convertir de racionals de precisió il·limitada)
RealFrac α[36] (Real α, Fractional α) estructura de cos ordenat /
info sobre fraccions
(truncate), (round),
(ceiling: (sostre) menor sencer superior), (floor: (terra) major sencer inferior),
(properFraction: fracció pròpia)
Floating α[37] Fractional α estructura de cos
amb càlculs en coma flotant, logaritmes i trigonometria
(sqrt), (**: exponenciació), (exp), (log: logaritme natural), (logBase),
(pi), (sin), (cos), (tan), (asin), (sinh), (asinh), ...
RealFloat α[38] (RealFrac α, Floating α) estructura de cos ordenat /
info dels reals en coma flotant
(significand: signe * valor de la mantissa (interval obert [0.5 .. 1))),
(exponent: segons l'equació: r == (significand r) * (2 ^^ (exponent r)));
1 == 0.1base_2 * 2^^1
-- no correspon a l'exponent de la notació científica!!
(isNaN: és No-Numèric?), (isInfinite: resultat de sobreiximent?),
(isDenormalized: és nombre subnormal (d'exponent inferior al mínim)?), ...

Les operacions dels racionals, per la seva especificitat, no estan definides com a classe. Són en un mòdul a banda.[39] El tipus està parametritzat pel tipus dels components del parell (numerador, denominador).

  • (Ratio a): Ops. dels racionals: (%: op. generador) (numerator), (denominator), ..

Els reals de Coma fixa estan definits al mòdul Data.Fixed[40]

GHC estén el mecanisme de derivació d'instàncies a algunes classes més. Vegeu ref.[41]

Tipus modifica

bàsics
tipus constructors / =expr mòdul descripció
() -- Unit
-- tipus d'un únic valor
() Prelude resultat buit en efectes col·laterals (ex.: print "abc" :: IO ())
equival al "void" o buit del llenguatge C
Bool[42] True | False Data.Bool Nombres booleans (Cert | Fals)
Ordering[23] LT | EQ | GT Data.Ord (Menor | Igual | Major) resultat ternari de (compare)
Char[43] Data.Char caràcters de 32 bits
amb codificació Unicode
String[44] = [Char] Data.Char
Data.String
llista de caràcters

El tipus String no és el millor per al tractament de text: show string mostra els caràcters no anglosaxons imprimint el codi numèric, cosa que s'utilitza per a la serialització de dades i la seva recuperació gràcies a la representació unívoca independent de la codificació. Les ops. d'Entrada/sortida sí que n'ofereixen la representació segons la codificació del sistema subjacent.

El tipus Text del paquet text[45] (ve amb les biblios de la Plataforma Haskell), està implementat com a vector de caràcters UTF-16 i té millor suport per la manipulació de textos i l'entrada/sortida que es mostra segons la codificació del sistema subjacent.

numèrics
tipus requeriment
de context
constructors / =expr mòdul descripció
Int, Int<N> N∈{8,16,32,64}[46] Data.Int Sencers (aritmètica del "Complement a dos")
(cas de Int: rang mínim [-2^29 .. 2^29-1])[47]
Word, Word<N> N∈{8,16,32,64}[48] Data.Word Paraula de bits: Adreça de memòria o valor natural, segons amplada paraula ordinador, (aritmètica modular, mòdul 2^nombre_de_bits)
-- cas concret: op. (negate) vàlida (Word instancia Num); al ghci:
Prelude> Numeric.showHex (-(2::Word)) ""
"fffffffffffffffe"
Integer Prelude Sencer de precisió il·limitada
Natural[49] Numeric.Natural Natural de precisió il·limitada
-- cas curiós: op. (negate) vàlida (Natural instancia Num); al ghci:
Prelude Numeric.Natural> -(10::Natural)
*** Exception: arithmetic underflow
Float, Double Prelude Coma flotant
Ratio t[39] Integral t Generador: (%)
numerador % denominador
Data.Ratio Racionals genèrics (t * t): parells (numerador, denom.),
per a tipus t amb estructura d'anell íntegre euclidià[50]
Rational[39] = Ratio Integer Data.Ratio Racionals (Integer * Integer)
Uni, Deci, Centi,
Milli, Micro,
Nano, Pico[40]
Data.Fixed Coma fixa de representació sencera
valor = representació * resolució del tipus
Complex t[51] RealFloat t = !t :+ !t Data.Complex amb constructor infix (:+)
i prefix '!' d'avaluació estricta als components
efectes
tipus requeriment
de context
constructors / =expr mòdul descripció
Maybe t Nothing | Just x Data.Maybe * efecte fallada (resultat opcional en operacions parcialment definides)
* paràm. opcionals
Either tipError tipResultat Left error | Right resultat Data.Either * efecte fallada amb indicació de l'error
* paràm. dual Right (dreta o correcte) o Left (esquerre o mancat)
* Èxit o fracàs de l'avaluació amb try
de codi que pot llançar excepcions
capturant l'excepció que retorna amb Left o el resultat amb Right.
[] tipElement [], (x : xs), [x], [1,2,3], [1..7],
[1,3..7] (==[1,3,5,7])
-- llistes infinites:
[1..], [1,3..]
Data.List * llistes
* efecte múltiple (resultat múltiple en operacions)
IO tipResultat System.IO,
Data.IORef,
Control.Exception
* operacions d'entrada/sortida,
* lectura/escriptura de variables globals IORef (cicle de vida no acotat),
* tractament d'excepcions.
ST s tipResultat Data.STRef
Control.Monad.ST
* lectura/escriptura de variables amb vida limitada a un àmbit
* encapsulament d'actualitzacions destructives de les variables STRef
avaluables en un espai s local (runST) o bé global (stToIO)
excepcions
tipus requeriment
de context
constructors / =expr mòdul descripció
IOError System.IO.Error Excepció en expressions IO
Exception Control.OldException tipus algebraic d'error del Haskell98
obsolet—la nova classe Exception permet construir
excepcions amb tipus definits per l'usuari.
SomeException Exception e SomeException e Control.Exception nou tipus genèric de les excepcions
definit existencialment com
un tipus d'aquells que implementen la classe Exception
altres
tipus constructors / exemples mòdul descripció
(t1,t2,...,tn) (,) t1 t2
(,,) t1 t2 t3
(,,...,) t1 t2 .. tn
Data.Tuple tipus producte anònim (tupla)
data nomDeTipus Constructor t1 t2 .. tn
deriving <classes a instanciar automàticament>
tipus producte amb nom
la clàusula "deriving (Eq, Show, ..)"
demana al compilador que generi instàncies de les classes bàsiques esmentades partint de la representació interna
Dl | Dt | Dc | Dj | Dv | Ds | Dg deriving (Eq, Show, Ord, Enum, Bounded) Enumeració (de constructors d'aritat 0)
Constructor1 t11 t12 .. t1n
| Constructor2 t21 t22 .. t2m
| ... deriving ...
tipus suma (Unió discriminada)
Constructor {camp1 :: t1, camp2,camp3 :: t2i3, ...} Registres (tipus producte amb accessors)
newtype nomDeTipusDerivat Constructor nomDeTipusBase deriving <classes preservades>
o bé: Constructor {accessor :: nomDeTipusBase} deriving ...
tipus derivats d'un tipus base,
* el Constructor constitueix un morfisme del tipus base al derivat,
mantenint l'estructura de les classes esmentades a la clàusula deriving
quines instàncies hereta el tipus derivat
* l'accessor, si hi és, constitueix el morfisme invers del Constructor
  • classes que implementen (a banda de Eq,[22] Show[27] i Read)[28]
bàsics
Ord [23] Enum [24] Ix [26] Bounded[25]
ordenable enumerable indexador acotat
() -- Unit
-- equival al void del C
Bool
Ordering
Char
numèrics
Ord [23] Enum [24] Ix [26] Bounded [25] Num [31] Bits [32] Real [33] Integral [34] Fractional [35] RealFrac [36] Floating [37] RealFloat[38]
requereix Eq Ord Num Num, Ord Real, Enum Num Real, Fractional Fractional RealFrac, Floating
descripció ordenable enumerable indexador acotat Numèric adreçable
als bits
Real íntegre amb divisió euclidiana fraccionable infos. de
fraccionables
càlculs i
trigonometria
infos. de
coma flotant
estructura ordre total Successió Successió Ordre total finit anell unitari vector de bits anell unitari ordenat anell unitari ordenat euclidià estructura algebraica de cos estruct. de cos ordenat estruct. de cos estruct. de cos ordenat
Int, Int<N>
aritmètica del
complement a dos
No
Word, Word<N>
aritmètica modular
No
-- precisió il·limitada
Integer, Natural
No No
-- coma flotant
Float, Double
No No No No
Rational No No No No No
-- coma fixa
Uni, Deci, Centi,
Milli, Micro,
Nano, Pico
No No No No No
Complex t No No No No No No No No No

Vegeu també:

El cas dels tres operadors d'exponenciació modifica

El domini de l'exponent determina el context del domini d'aplicació per les operacions requerides:

-- exponent natural => cal que el domini ''a'' de la base implementi el producte (definit a Num: estructura d'anell)
(^) :: (Num a, Integral ex) => a -> ex -> a -- dispara excepció si l'exponent és negatiu

-- exponent enter => cal que el domini de la base implementi, a més a més, l'invers del producte (''recip'': recíproc) (definit a Fractional: estructura de cos)
(^^) :: (Fractional a, Integral ex) => a -> ex -> a

-- exponent real en coma flotant => cal que el domini de la base implementi l'exponenciació (''exp'' i ''log'': logaritme natural), definits a Floating
(**) :: (Floating a, Floating ex) => a -> ex -> a
literals modifica

Els literals numèrics no s'associen a tipus. Sense punt decimal requereixen que el tipus tingui estructura d'anell unitari (implementi la classe Num), mentre que els nombres amb part fraccionaria o notació exponencial (ex.: 5E2) requereixen que el tipus tingui estructura algebraica de cos (implementi la classe Fractional).

El signe menys no forma part del literal, s'interpreta com a operador (unari si és al capdavant de l'expressió; binari si la posició és infix): (-1).[52] Això no permet especificar els nombres negatius sense corresponent positiu: (-128)::Int8. L'extensió NegativeLiterals hi posa remei.

Amb l'intèrpret GHCi podem consultar el tipus d'una expressió amb

Prelude> :type 1
1 :: (Num t) => t -- tipus t tal que implementa la signatura de ''Num'' 
 -- un anell unitari en àlgebra: (+), (-), (*), 0: neutre de (+), 1: neutre de (*)

Prelude> :type 1.5
1.5 :: (Fractional t) => t -- tipus t tal que implementa la signatura de ''Fractional'' (requereix Num)
 -- estructura algebraica de cos: anell amb invers del producte
 -- implementada per Float, Double, Rational, Uni, Deci, Centi, Milli, Micro, Nano, Pico

 0x41 -- hexadecimal :: (Num t) => t
 0o377 -- octal :: (Num t) => t
 0b11001101 -- binari :: (Num t) => t -- (des de GHC 7.10 amb l'extensió BinaryLiterals)

 1E10 -- (1E10) :: (Fractional t) => t

 -- caràcters: 'A', notació decimal: '\65', hexadec: '\x41', apòstrof: '\''

El tipus concret es determina:

  • per l'operació on intervé, si el tipus del paràmetre és fix (en majúscula) o bé si és una variable de tipus en posició repetida
 -- l'operació (+) :: (Num a) => a -> a -> a
 -- el tipus del 2n paràmetre i el del resultat venen determinats pel tipus del primer paràmetre
  • explícitament, (1::Double)
  • provant una seqüència de tipus (clàusula default)[53]
-- la clàusula "default" és una manera de resoldre les ambiguïtats dels literals
-- reduint les assignacions de tipus explícites
 default (Int, Double) -- seqüència de tipus a provar per desambiguar els literals
literals amb notació científica modifica

Des de GHC 7.8 els sencers poden tenir literals amb notació científica. Cal l'extensió de llenguatge NumDecimal

{-# LANGUAGE NumDecimal #-}

n = 1.2E4 :: Int
literals de llistes modifica
[] -- Nil (llista buida)
[1,2,3] -- valors separats per comes
[1..3] ['a'..'z'] -- seqüència correlativa, tradueix a (enumFromTo 1 3) de la classe ''Enum''
[1..] ['a'..] -- tradueix a (enumFrom 1) de Enum
[1,3..10] -- (== [1,3,5,7,9]) seqüència incremental, tradueix a (enumFromThenTo 1 3 10) de Enum, també admet ['a','c'..'z']
[1,3..] -- tradueix a (enumFromThen 1 3) de Enum
conversions (casting de tipus) modifica

Per la conversió, el tipus resultant serà el requerit per l'avaluació de l'expressió, exigible si cal mitjançant una restricció de tipus.

Les conversions funcionen com una caracterització de tipus (ang: cast) i no disparen excepcions si la precisió del tipus de destinació és menor que la d'origen.[54]

Les conversions entre Int i Word preserven la representació (no pas el signe).[55]

  • conversions des de tipus amb precisió il·limitada o indefinida (per ex. literals amb seqüència de dígits de llargada indeterminada):
-- per la captura de literals enters a Numèric (anell), o convertir el tipus Integer de precisió il·limitada
fromInteger :: Num a => Integer -> a 

-- per la captura de literals amb part decimal, de precisió indefinida a Fractional (estructura de cos)
fromRational :: Fractional a => Rational -> a -- des de racional (type Rational = Ratio Integer)
  • conversions
fromIntegral :: (Integral a, Num b) => a -> b -- de Íntegre (Anell ordenat euclidià) a Numèric (anell)

realToFrac :: (Real a, Fractional b) => a -> b -- de Real (Anell ordenat) a Fraccionable (estructura de Cos, exclou enters)

Exemples:

Prelude> fromIntegral (5::Int) :: Integer -- a sencer de precisió il·limitada
5
Prelude> fromIntegral (5::Integer) :: Int
5
Prelude> fromIntegral (5::Int) :: Float
5.0
Prelude> fromIntegral (5::Int) :: Double
5.0
Prelude> import Data.Int -- importa tipus IntN de precisió concreta, per a N pertanyent a {8,16,32,64}

Prelude Data.Int> fromIntegral (0x7FFF :: Int16) :: Int8 -- fromIntegral NO dispara excepcions per sobreeiximent !!
-1
  • Des de Real (anell ordenat): són instància de Real els Float, Double, Int, IntN, Integer, Natural, Word, WordN, Racionals i els de Coma fixa.[57]
Prelude> realToFrac (1.0::Float) :: Double
1.0
Prelude> realToFrac (1.1234567890::Double) :: Float
1.1234568
# incorporem el mòdul de coma fixa Data.Fixed
Prelude> :m +Data.Fixed
Prelude Data.Fixed> realToFrac (1.5 ::Float) :: Centi
1.50
  • Amb Racionals : (% és el constructor dels racionals. Ha d'anar entre espais, però la viquipèdia el retalla si va precedit d'un nombre)

fromRational és membre de la classe Fractional (Fraccionable, estruct. de Cos).

# incorporem el mòdul dels racionals
Prelude> :m +Data.Ratio
Prelude Data.Ratio> realToFrac 1.50 :: Rational
3% 2
Prelude Data.Ratio> fromRational (2% 3) :: Float -- '%' és el generador infix dels racionals
0.6666667

tipus algebraics modifica

Creats amb la clàusula data: Enumeracions, tipus producte, tipus suma, ...

enumeracions modifica

Unió de constructors d'aritat 0 (sense paràmetres) quina definició estableix un conjunt amb un ordre seqüencial. El compilador facilita la derivació de classes específiques si les explicitem a la clàusula deriving:

  • Show: per a la conversió a String
  • Eq: per a la distinció
  • Ord: relació d'ordre
  • Enum: {(succ: successor) (pred: predecessor) (fromEnum: enter ordinal corresp.) (toEnum: enumerat corresp. a l'enter ordinal)}
  • Bounded: cotes inferior i superior
 data DiaSetm = Dl | Dm | Dc | Dj | Dv | Ds | Dg
 deriving (Show, Eq, Ord, Enum, Bounded) -- demana al compilador que derivi instàncies
 -- de classes bàsiques partint de la representació interna del tipus
Tipus producte modifica
data TRectangle = Rectangle Int Int -- (Int * Int) amb constructor Rectangle
  • El constructor equival a un morfisme del producte dels components (per ex.: Int * Int) al tipus nou.
  • De fet, a GHC, l'extensió GADTs, permet descriure els constructors com a funcions:
{-# LANGUAGE GADTs #-}

data TRectangle where
 Rectangle :: Int -> Int -> TRectangle

Encaix dels paràmetres discriminant pel constructor si n'hi ha diversos.

 perímetre :: TRectangle -> Int
 perímetre (Rectangle x y) = 2 * (x + y)
Registres modifica

Augmenta el tipus producte, especificant noms de camps com a funcions accessores als components. No hi ha registres anònims (cal distingir-los pel constructor).

Els registres corresponen en la P.O.O als objectes i els seus camps, mentre que el comportament es materialitza en les instàncies (implementacions) de les classes de tipus (interfícies).

  • Definició: data Tipus = CRec {camp1 :: tipus1, camp2, camp3 :: tipus2i3}
  • Inicialització de valors: Cas de fer servir el literal de camps, aquests hi han de ser tots. També podem utilitzar el constructor com al tipus producte, sense els noms de camp, amb els components en l'ordre especificat a la definició.
 -- per evitar col·lisions en l'espai de noms, convé afegir un prefix als noms de camps
 data TPersona = Persona {pNom::String, pEdat::Int, pAlçada::Float} deriving (Show, Eq)
 data TQuisso = Quisso {qNom::String, qEdat::Int, qAlçada::Float} deriving (Show, Eq)

 persona = Persona {pNom="Joan", pEdat=33, pAlçada=1.50}

 -- equivalent amb inicialització posicional
 persona = Persona "Joan" 33 1.50

 -- accedint per nom de camp, el tipus de l'accessor pNom :: TPersona -> String
 nom = pNom persona

 -- encaix posicional
 Persona nom edat alçada = persona
  • Literal dels camps als patrons (només cal especificar els parells (camp = patró) que interessin):
getCampX CRec {campX = vCampX} = vCampX

-- amb l'extensió de sintaxi "NamedFieldPuns", ens estalviem repeticions
aniversari p @ Persona {pEdat} = -- {pEdat} equival a {pEdat = pEdat} (ext. NamedFieldPuns),
 -- aquí el pEdat visible és la variable del patró, en comptes de la funció accessora al camp
 p { pEdat = pEdat +1}

-- amb l'extensió de sintaxi "RecordWildcards", incorpora la resta de camps a l'àmbit, evitant mencionar-los tots
getCombinaCamps CRec {campX = v1, ..} = (v1, campY, campZ) -- l'el·lipsi ", .." (ext. RecordWildcards) incorpora a l'espai de noms la resta de camps

-- el literal de camps pot ser buit quan al patró només interessa el constructor
f CRec {} = ....
  • Literal dels camps a l'actualització
setCampX nouX reg = reg {campX = nouX}
-- si fem servir el nom del camp com a var.
setCampX campX reg = reg {campX} -- l'ext. NamedFieldPuns pren "campX" com a "campX = campX" també a l'actualització
  • El literal dels camps "{campA=x}" està lligat sintàcticament al terme precedent amb major precedència que l'operació d'aplicació, tant en patrons com en cas d'actualització. No caldrà agrupar-los:
$ ghci
Prelude> data TRec = Rec {campA::Int}
Prelude> campA Rec {campA=2} -- no cal agrupar amb parèntesis el constructor amb el literal dels camps
2
  • Espai de noms: Els noms dels camps pertanyen a l'espai de noms del mòdul i convé afegir-los-hi un prefix per evitar col·lisions. L'extensió DuplicateRecordFields (GHC 8.0) permet la coincidència de noms de camps entre registres d'un mateix mòdul, sempre que el seu ús sigui inambigu (fent servir restriccions de tipus quan calgui), i la seva exportació es faci com a símbol intern del tipus.[58]

Per la sintaxi simplificada a GHC vegeu[59]

Cas de registres niuats o bé camps estructurals, les referències funcionals anomenades lents ofereixen la possibilitat de consulta i modificació d'estructures complexes

Reversibilitat de tipus en registres d'un sol component modifica

És una construcció força utilitzada per què en aquests registres, el constructor i l'accessor són funcions inverses l'una de l'altra.

data NouTipus a = Constructor { accessor :: a}

-- Constructor :: a -> NouTipus a
-- accessor :: NouTipus a -> a

-- (accessor. Constructor) == id

Vegeu també més avall #Tipus derivats -- newtype

Tuples modifica

tipus producte anònim amb constructors com: (,); (,,); ....[60] Ops. definides a Data.Tuple[61]

 () -- la tupla buida és un valor del tipus Unit (tipus d'un sol valor) 
 -- i també designa el tipus esmentat (emprat per al resultat buit en efectes (IO ()))

 parell = (1,"abc") -- valor
 (Int, String) -- tipus

 fst parell -- funció primer (fst) i segon (snd) (només implementades per a tupla2),
 -- no definit a tupla3.. i següents
 snd parell -- segon
 (,) -- constructor de tupla2: (,) a b == (a,b)
 (,,) -- constructor de tupla3: (,,) a b c == (a,b,c)
 (,,,) -- constructor de tupla4

 -- encaix
 (primer, segon) = parell
tipus suma modifica

Unió de tipus discriminada per etiquetes (constructors), seguits dels tipus dels components

 -- en aquest exemple l'etiqueta o ''constructor'' coincideix amb el nom del tipus que el segueix
 -- en diverses ocasions però és vàlid perquè són espais de noms diferents

data Exception -- aquest tipus del H98 va passar al mòdul Control.OldException, que ha sigut eliminat de GHC
 = IOException IOException -- IO exceptions
 | ArithException ArithException -- Arithmetic exceptions
 | ArrayException ArrayException -- Array-related exceptions
 | ErrorCall String -- Calls to 'error'
 | ExitException ExitCode -- Calls to 'System.exitWith'
 | NonTermination -- Program is in an infinite loop
 | UserError String -- General purpose exception
 ...

type Coord = Double

-- estil algebraic
data Punt = Punt2D Coord Coord
 | Punt3D Coord Coord Coord
 deriving (Eq, Show)

-- estil GADTs (algebraic generalitzat) -- requereix pragma: {-# LANGUAGE GADTs #-}
-- per cadascun dels constructors se'n descriu el tipus com si fos una funció:
data Punt where
 Punt2D :: Coord -> Coord -> Punt
 Punt3D :: Coord -> Coord -> Coord -> Punt
 deriving (Eq, Show)

p2D = Punt2D 2 4
p3D = Punt3D 4 6 8

-- encaix discriminant pel ''constructor''

mòdul :: Punt -> Coord
mòdul (Punt2D x y) = sqrt $ x^2 + y^2
mòdul (Punt3D x y z) = sqrt $ x^2 + y^2 + z^2
Registres variants i accessors parcials modifica

Els accessors no definits per tots els constructors, són funcions parcials (poden fer petar el programa)

data Punt = Punt2D {x,y :: Coord}
 | Punt3D {x,y,z :: Coord} -- compte! l'accessor (z :: Punt -> Coord) és parcial
  • Una alternativa és no fer servir els noms de camp com a accessors, sinó només en patrons, però els resultats incerts dels camps no comuns mostren que no és un bon disseny.
{-# LANGUAGE NamedFieldPuns #-}

-- l'obtenció i modificació de la coord. Z és incerta en aquest disseny

getZ :: Punt -> Maybe Coord -- Potser tindrà èxit, potser no !! 
getZ Punt3D {z} = Just z
getZ Punt2D {} = Nothing

setZ :: Coord -> Punt -> Maybe Punt -- Potser tindrà èxit, potser no !! Massa incerteses !!
setZ z pt @ Punt3D {} = Just pt {z}
setZ z pt @ Punt2D {} = Nothing
  • És millor fer-ne tipus diferents per cada cas, definint setZ només per al tipus 3D. Per un tractament comú dels camps comuns caldrà definir getters/setters en classes i instanciar-les per a ambdós tipus.
data Punt2D = Punt2D {x,y :: Coord}
data Punt3D = Punt3D {x,y,z :: Coord}

class GetXY t where
 getXY :: t -> (Coord, Coord)

instance GetXY Punt2D where { getXY Punt2D {x, y} = (x, y) } 
instance GetXY Punt3D where { getXY Punt3D {x, y} = (x, y) }
Polimorfisme de registres amb camps específics - la classe HasField modifica
Des de GHC 8.2 ja no caldrà classes "getters" per aconseguir polimorfisme sobre els accessors comuns.[62]
  • requeriment d'existència d'accessor de nom "camp": HasField "camp" tipusRegistre tipusCamp
{-# LANGUAGE DuplicateRecordFields, TypeApplications, DataKinds, FlexibleContexts #-} 

module Lib1 (
 prova1 
 {-, prova2 GHC >= 9.2-}
) where

import GHC.Records (HasField(getField {- GHC >= 8.2 -}
 {-, hasField // pròximament a GHC >= 9.2 -}
))
type Coord = Double

data Punt2D = Punt2D {x,y :: Coord} deriving (Show)
data Punt3D = Punt3D {x,y,z :: Coord} deriving (Show)

p2D = Punt2D 1 2
p3D = Punt3D 3 4 5

-- getter desde GHC 8.2
prova1 :: (Coord, Coord)
prova1 = (getField @"x" p2D, getField @"x" p3D)

{-
-- parell (getter, setter) pròximament a GHC 9.2
prova2 :: (Coord, Coord, Punt2D, Punt3D)
prova2 = (getX p2D, getX p3D, setX p2D 0, setX p3D 0)
 where (getX, setX) = hasField @"x" 
-}
tipus polimòrfics modifica

Amb variables de tipus (els identificadors que comencen per minúscula).

  • Maybe (paràmetres i resultats opcionals).[63]
  • Either (èxit o fracàs de l'avaluació d'una acció que llança excepcions, retornant Left amb l'excepció o bé Right amb el resultat).[64][65]
 -- el tipus Maybe : paràmetres opcionals o resultats de funcions definides parcialment
 data Maybe a = Nothing | Just a deriving (Eq, Ord, Read, Show)

 -- el tipus Either: domini dual, emprat en l'avaluació amb 'try' d'una acció que llança excepcions
 data Either a b = Left a {- error -} | Right b {- resultat -}

 -- tipus recursius
 data [a] = [] | a : [a] -- tipus llista; (:) és un constructor infix 
 -- per definició de la sintaxi, tots els operadors q. comencen per ':' són infix

 data Arbre a = Fulla a | Branca (Arbre a) a (Arbre a)

Els tipus que no inclouen variables de tipus s'anomenen monomòrfics.

sinònims de tipus modifica

Els sinònims de tipus són com macros al nivell de tipus.[66]

type String = [Char] -- el tipus String (cat: enfilall o tira) està definit com a llista de caràcters (seqüència d'accés lineal)
type LaMevaEstructura = (String, Int, Double)
sinònims paramètrics modifica

El següent sinònim inclou paràmetres de tipus.

type LlistaNumerada a = [(Int, a)]

llistes modifica

El tipus Llista (seqüència d'accés lineal), amb constructors "[]" (llista buida, anomenat nil)[67] i ":" (tip_elem * llista_elem, anomenat cons)[67] Operacions al mòdul Data.List[68]

 data [a] = [] {- "nil" -} | a : [a] -- ':' és el constructor "cons" (infix, per def. sintàctica)

 [a] -- en una decl. de tipus, llista d'elements de tipus a

 [] -- llista buida
 elem_cap : llista_cua

 [1,2,3] -- els valors han de ser homogenis (del mateix tipus)
 [1,3..10] == [1, 3, 5, 7, 9]

 -- consultes
 null xs -- és buida?
 length xs -- mida
 xs !! posició -- el primer és llista!!0 -- funció parcial !!
 elem x xs -- pertinença a la llista
 ésFeiner dia = dia `elem` [Dl, Dm, Dc, Dj, Dv] -- `elem` en posició infix per les cometes revesses

 -- llistes infinites
 [1..]
 [1,3..] == [1, 3, 5, 7, 9, ...
 iterate f x == [x, f x, f (f x), ...] -- seqüència d'aplicacions reiterades d'una funció
 repeat x -- llista per repetició del valor x
 cycle xs -- converteix la llista NO-BUIDA xs en circular -- funció parcial !!

Més operacions a contenidors

llista per comprensió modifica

Especifica l'expressió objectiu, obtenint-ne els valors de les variables d'una seqüència de generadors i filtres separats per comes

 s :: [Int]
 s = [ x+y | x <- [0, 2..], y <- [1, 3..20], x^2 > 3, y `mod` 9 /= 0 ]

 print $ take 5 s -- imprimeix els primers
llista per comprensió monàdica modifica

La llista per comprensió precedent es pot expressar amb un bloc monàdic

s :: [Int]
s = do
 x <- [0, 2..]
 y <- [1, 3..20]
 guard $ x^2 > 3
 guard $ y `mod` 9 /= 0
 return $ x+y

Vegeu-ho a Mònada (programació funcional)#Llista per comprensió monàdica

llistes per comprensió a l'estil de SQL modifica

L'extensió de GHC TransformListComp permet incorporar-hi transformacions.[69] Vegeu exemple.[70]

tires de text modifica

-- el tipus String (cat: corda o enfilall d'objectes, indica seqüència, aquí en direm tira com les tires de números consecutius de les rifes) és una llista de caràcters i és el tipus de les tires incloses al codi. String no és definit com a estructura (data) sinó com a àlies o sinònim (type) de l'expressió:

type String = [Char]
-- literals: "" "abc" "\"abc\""
  • Llegiu l'article "How to pick your string library in Haskell"[71]

L'extensió OverloadedStrings permet fer servir literals de tipus String com a valors de tipus que n'implementin la conversió, instanciant la classe IsString.

Tires de text implementades amb vectors - El tipus Text modifica

Tires com a seqüències d'accés aleatori, a diferència de String definit com a llista (seqüència d'accés lineal)

  • amb un tipus text que empaqueta els caràcters en un vector de UTF-16, definit a la biblioteca "text"[72]

Estructura del tipus Text que és un tipus opac (l'estructura és només d'ús intern):[73]

-- | A space efficient, packed, unboxed Unicode text type.
data Text = Text
 {-# UNPACK #-} !A.Array -- payload (Word16 elements)
 {-# UNPACK #-} !Int -- offset (units of Word16, not Char)
 {-# UNPACK #-} !Int -- length (units of Word16, not Char)
 deriving (Typeable)

Exemple d'ús:

#if __GLASGOW_HASKELL__>=612 /* GHC >= 6.12.0 */

 import System.IO (stdout, hFlush)

 -- amaguem les funcions predefinides per a String que volem Text
 -- cal haver instal·lat el paquet "text"

 import Prelude hiding (putStr, putStrLn, getLine)
 import Data.Text.IO (putStr, putStrLn, getLine)
 import Data.Text (pack, unpack, -- conversió
 empty, singleton, cons, -- generadors
 uncons)

 text1 = pack "Entreu lletres tot seguit: " -- pack :: String -> Text

 main = do
 putStr text1
 hFlush stdout

 x <- getLine
 case uncons x of -- uncons :: Text -> Maybe (Char, Text)
 Just (lletra, _resta) -> do
 putStr $ pack "comença per "
 putStrLn $ singleton lletra -- singleton :: Char -> Text

 Nothing -> putStrLn $ pack "entrada buida"
#else
 #error "no provat en versions anteriors"
#endif

compilant amb preprocés (opcions -cpp o bé -XCPP o bé {-# LANGUAGE CPP #-} al codi)

 runhaskell -cpp prova.hs
Sobrecàrrega de literals String i llista modifica

L'extensió de llenguatge OverloadedStrings facilita que, de manera similar als literals numèrics, els literals de tires de caràcters admetin també l'assignació del tipus requerit per l'operador, sempre que el tipus de l'operand proporcioni la conversió, implementant fromString de la classe IsString.[74]

{-# LANGUAGE OverloadedStrings #-}
import Data.Text (Text) -- el tipus Text instancia la classe IsString
import qualified Data.Text as T

-- T.take :: Int -> Text -> Text

abc = T.take 3 "abcdef" -- el literal "abcdef" pren tipus Text en comptes de String

De manera similar, des de GHC 7.8 amb l'extensió OverloadedLists, els literals llista també poden prendre tipus d'altres col·leccions que n'implementin la conversió instanciant la classe IsList. Vegeu Compilador Haskell de Glasgow#Sobrecàrrega de literals Llista

literals i decodificació
literal classe funció decodificadora
o de conversió
extensió de llenguatge
requerida
exemples
999
(* sense parts decimal/exp. *)
0xFF, 0o377
Num fromInteger
999[.99] [E[±]999]
(* notació científica *)
Fractional fromRational 2.5E-2 :: Rational
2.5E-2 :: Float
999[.99] [E[±]999]
(* notació cient.
avaluable a enter *)
Num fromInteger NumDecimals 2.5E2 :: Int
0b11001100 Num fromInteger BinaryLiterals
"tira de caràcters" IsString[44] fromString OverloadedStrings[74] "abc" :: Text
'[' 1,2,3 ']'
(* seq. de valors *)
IsList[75] fromList OverloadedLists[76] [1, 3..9] :: Set Int
[(1,"a"), (2,"b")] :: Map Int String

Expressions modifica

funcions modifica

A la declaració cal especificar els tipus dels paràmetres separats per fletxes; si son genèrics en minúscula

-- (->) indica retorn
f :: a -> r -- indica el tipus d'una funció f amb paràm. de tipus 'a' que retorna un tipus 'r'
  • la repetició d'una variable de tipus a la signatura, indica que el tipus corresponent a les aparicions repetides d'una mateixa variable ha de coincidir amb el tipus encaixat en primera posició.
ésApreciable :: (Ord a) => a -> a -> Bool 
ésApreciable llindar valor = valor >= llindar

-- l'ús de (>=) requereix que el tipus a implementi Ordenable 
-- i cal especificar-ho com a requeriment de context (abans de "=>")
-- indicant que cal la visibilitat d'una instància de Ordenable en el context
-- d'ús de la funció, per al tipus que prengui la variable ''a''.

Qualsevol importació d'un mòdul importa totes les instàncies exportades (visibles a nivell de mòdul)

Funcions anònimes anomenades Expressions lambda modifica

La barra invertida, caràcter del teclat que més s'assembla a la lletra grega lambda (per referència a l'abstracció lambda del càlcul lambda, fonament de la programació funcional), introdueix una funció anònima com a expressió.

 \ x y -> x + y

és equivalent a

 \ x -> \ y -> x + y

És una notació importada del càlcul lambda on   s'abreuja com a  

Cal tenir en compte que el cos de la lambda abasta el màxim cap a la dreta

(\ x -> M N)  (\ x -> (M N)) -- com al càlcul lambda
  • L'extensió de llenguatge LambdaCase permet obviar la variable quan introdueix un case.[77]
{-# LANGUAGE LambdaCase #-}

\ case { patró1 -> expr1; ...; patN -> expN }

-- equival a
\ x -> case x of { patró1 -> expr1; ...; patN -> expN }

Aplicació parcial modifica

Vegeu ref.[78]

L'aplicació parcial d'un paràmetre actual a una funció causa la substitució de la variable pel paràmetre en l'expressió, resultant una funció de les variables restants.

(\ x y -> E) {- equival a -} \ x -> (\ y -> E)

-- aplicació d'una lambda a un paràmetre és l'expressió beta-reduïble
(\x -> E) E'

Per la reducció beta del càlcul lambda   és la substitució  .

L'aplicació és associativa per l'esquerra. f x y ≡ (f x) y

L'operador de funció (->) "retorna" és associatiu per la dreta:

(a -> b -> c)  (a -> (b -> c)) -- en aplicar el primer paràmetre s'obté una funció dels paràmetres restants.

Pas de paràmetres modifica

Per defecte: per necessitat (ang: lazy)[79] = avaluació tardana (quan se'n requereix l'ús) amb substitució de l'avaluació del thunk de l'argument, i memoïtzació del resultat de l'avaluació de l'argument.

El mode d'avaluació del paràmetre es pot alterar amb un prefix al patró segons la taula següent:

extensió prefix als patrons
dels paràmetres formals
avaluació comentaris
-- per defecte patró lazy (avaluació per necessitat call by need)
~patró aval. tardana i irrefutable de l'encaix Vegeu Avaluació tardana explícita de l'encaix
BangPatterns
-- afegeix sintaxi prefix '!'
!patró estricta, per valor
Strict[80]
-- modifica la semàntica
patró per defecte: estricta i per valor
~patró lazy (avaluació per necessitat call by need)

Vegeu extensions BangPatterns i Strict[81]

Pas de paràmetres per valor modifica

Vegeu #Avaluació estricta explícita, passar per referència.

L'especificació d'estrictesa als paràm. formals requereix l'extensió BangPatterns o bé la Strict.[81]

-- amb indicació d'avaluació estricta (!) als patrons

{-# LANGUAGE BangPatterns #-}
ident !param_formal = expressió (param_formal) -- def. de funció amb estrictesa (!) als patrons (ext. BangPatterns), avalua el paràmetre a WHNF

-- o bé amb aplicació estricta ($!)
(ident $! param_actual) -- = param_actual `seq` ident param_actual -- 'seq' avalua el primer paràmetre a HNF i retorna el segon

-- o bé amb aplicació estricta a Forma Normal ($!!), avalua en profunditat, requereix que el tipus implementi NFData
import "deepseq" Control.DeepSeq (NFData, deepseq, ($!!))
(ident $!! param_actual) -- = param_actual `deepseq` ident param_actual -- 'deepseq' avalua el primer paràmetre a Forma Normal i retorna el segon

L'extensió de llenguatge Strict de GHC 8.0 capgira el mode d'avaluació dels patrons, on per defecte l'avaluació serà estricta (excepte per als identificadors a nivell de mòdul "Top level bindings"), assenyalant el pas de paràmetres tardà (lazy) amb (~).[81]

Àmbit dinàmic - paràmetres implícits modifica

Permet lligar variables amb prefix '?' a l'àmbit del procediment que fa la crida, com el pas de paràmetres per referència del lleng. C, sempre que s'especifiqui la variable en un requeriment de context (requereix visibilitat en l'àmbit de la crida).

  • Cal esmentar-les als requisits de context per fer referència a l'àmbit d'origen.
  • Cal l'extensió de llenguatge {-# LANGUAGE ImplicitParams #-}. Vegeu ref.[82]
{-# LANGUAGE ImplicitParams #-}

-- definint la variable implícita
prova = let ?implícit = 2 -- espai local de la variable implícita
 in cridaIntermèdia

cridaIntermèdia :: (?implícit :: Int) => Int -- refereix ?implícit a l'àmbit del punt de crida
cridaIntermèdia = multiplicaAmbImplícit 5

multiplicaAmbImplícit :: (?implícit :: Int) => Int -> Int -- refereix ?implícit a l'àmbit del punt de crida
multiplicaAmbImplícit x = ?implícit * x

main = print prova

Explicitar un tipus amb el paràmetre Proxy modifica

El tipus (Proxy a) és un tipus de paràmetre que no comporta dades, i s'utilitza per fer coincidir un tipus amb el de paràmetres subseqüents.[83]

Exemple d'ús en l'entorn Servant.[84]

import Servant.Server (Application, Server, serve)

data User = User {nom :: String} deriving (Eq, Show, Generic, ToJSON)

type API = "usuaris" :> Get '[JSON] [User]

serveixUsuaris :: Server API 
serveixUsuaris = return [User "Joan", User "Josep"]

-- la signatura de `serve` assegura la coincidència de l'API del servidor amb la desitjada

-- serve :: HasServer api '[] => Proxy api -> Server api -> Application

appServidor = serve (Proxy :: Proxy API) serveixUsuaris

Funcions d'ordre superior modifica

Quan algun dels paràmetres és una funció:

-- per descriure un paràmetre funció cal acotar-lo entre parèntesis:
flip :: (a -> b -> c) -> b -> a -> c 
flip f x y = f y x

Els operadors bàsics sobre funcions són al mòdul Data.Function[85]

Al passar un paràmetre funció no se'n comprova l'aritat.

Secció (tall) d'un operador en infix modifica

La secció d'un operador en infix consisteix en convertir una expressió d'un operador binari en una funció anònima d'un dels operands, que s'omet i es tanca entre parèntesis, per exemple (< 5). Vegeu ref.[86]

-- exemple d'ús: quick-sort en llistes
import Data.List as L
qs [] = []
qs (x:xs) = qs precedents ++ [x] ++ qs no_precedents
 where 
 (precedents, no_precedents) = L.partition (< x) xs

$ ghci
Prelude> :type (2^) -- equival a l'aplic. parcial ((^) 2)
(2^) :: (Integral b, Num a) => b -> a

Prelude> :t (^2) -- equival a l'aplicació parcial (flip (^) 2)
(^2) :: Num a => a -> a

Prelude> :t (`elem` "AEIOU") -- equival a l'aplic. parcial (flip elem "AEIOU") 
(`elem` "AEIOU") :: Char -> Bool
  • excepte el cas (-1) doncs el signe menys a l'inici d'expressió es considera operador unari.[52]

Patrons i guardes modifica

casuística per encaix de patrons dels paràmetres modifica

El nom de la funció precedeix cada cas.

 -- _ és el comodí, que pot portar un sufix per a documentació (ex.: "_x", "_xs")
 llargada :: [a] -> Int
 llargada [] = 0 -- [] patró del constructor ''nil'' de llista buida.
 llargada (_:xs) = 1 + llargada xs -- (_cap : _cua) patró del constructor ''cons''.

!! GHC no avisa quan els encaixos són incomplets (no exhaustius),[87] excepte que ho demanis explícitament amb l'opció -fwarn-incomplete-patterns o -Wall

La determinació de si un encaix és viable s'avalua de manera estricta. Per a avaluar-los de manera tardana vegeu #Avaluació tardana explícita en els patrons d'encaix

l'operador (@) - assignació del valor encaixat a una variable modifica

l'operador (@), anomenat as-pattern, com a (variable @ patró) unifica la variable i el valor encaixat al patró

 descriuLlista ll @ (_:_) = "llista no buida: " ++ show ll
Signatures de tipus als patrons modifica

Totes les variables de patrons admeten signatures, quines variables de tipus han de pertànyer a l'àmbit de la definició.[88]

 f :: forall a. [a] -> (Int, [a]) -- forall a. fa visible el tipus 'a' al codi definit
 f xs = (n, zs)
 where
 (ys::[a], n) = (reverse xs, length xs) -- Correcte, restricció amb tipus a la vista
 zs::[a] = xs ++ ys -- Bé

 Just (v::b) = ... -- Incorrecte. b no està a la vista!
Definició per Guardes modifica

Definició de funcions basada en condicions

 prova x
 | x > 0 = 1
 | x == 0 = 0
 | otherwise = -1
Definició per guardes de patrons modifica

Haskell2010. (extensió de llenguatge PatternGuards) Permet condicionar una definició a una seqüència d'encaixos subordinats.[89]

Quan volem donar categoria de definició a una expressió subjecta a una cascada d'encaixos

addLookup env var1 var2 = 
 case lookup env var1 of
 Just val1 ->
 (case lookup env var2 of 
 Just val2 -> val1 + val2
 Nothing -> cas_residual
)
 Nothing -> cas_residual

podem escriure la cascada d'encaixos en seqüència com a condicionant de la definició:

addLookup env var1 var2
 | Just val1 <- lookup env var1,
 Just val2 <- lookup env var2
 = val1 + val2
addLookup _ _ _ = cas_residual
Sinònims de patrons modifica

Des de GHC 7.8.1 amb l'extensió de llenguatge PatternSynonyms.[90]

  • Per al cas de patrons niuats, per distingir els patrons segons el component pretès amb noms més entenedors
  • o també, per donar noms a valors als patrons

[91]

{-# LANGUAGE PatternSynonyms #-}

pattern Cap y <- y : _ -- amb sintaxi unidireccional (<-): ús només en patrons
pattern Cua ys <- _ : ys

obtenirElCapDeLaCuaDeLaCua :: [a] -> Maybe a
obtenirElCapDeLaCuaDeLaCua xs =
 case xs of
 Cua (Cua (Cap y)) -> Just y 
 _ -> Nothing

main = print $ obtenirElCapDeLaCuaDeLaCua [1::Int,2,3]
  • hi ha dues sintaxis, segons l'ús pretès del sinònim:[90]
  • unidireccional (<-) : circumscriu l'ús del sinònim al context de patrons.
  • bidireccional (=) : per emprar el sinònim tant en patrons i com en expressions.
  • també poden estar definits dins una classe (associats), abstractes dins la classe, amb la definició concretada a les instàncies.
Patrons de vistes modifica

S'entén per vista una funció d'un argument quin resultat es pot analitzar per patrons. La sintaxi (vista -> patró) es fa servir en el lloc d'un argument, aplicant la funció vista a l'argument i encaixant el resultat al patró. També es pot fer servir en un case o en una definició amb let. Extensió ViewPatterns.[92]

  • En una definició: f (vista -> patró) = … ≡ f v | patró <- vista v = …
  • En un case: case v of { (vista -> patró) -> e1 ; _ -> e2 } ≡ case vista v of { patró -> e1 ; _ -> e2 }

Exemple a #Tipatge dinàmic - Paràmetres de tipus divers

Definicions d'àmbit local modifica

Vegeu "Let vs Where".[93]

amb where, estil declaratiu (ús abans de la definició)
permet definir variables esmentades als patrons i guardes de la definició. Cada alternativa pot tenir el seu where.
 -- En una definició amb (=) o en una alternativa de case (amb (->))
 -- (EBNF)
 alternativa = patró, (("|", guarda, "=", expressió, -- o bé amb guardes
 { "|", guarda, "=", expressió }) -- guardes addicionals
 | ("=", expressió) -- o bé sense guardes
)
 ["where", declaracions_locals]
amb let..in, estil imperatiu (definició abans de l'ús)
cada (let .. in) estableix un àmbit on no s'hi pot redefinir un símbol, però es poden establir àmbits (let .. in) successius
-- ghci
Prelude>let x = 1; y = x in let x = y +1 in print x
2 -- resultat

Als blocs d'accions (efectes col·laterals) do, els subblocs let (que no duen la paraula reservada in al final) permeten definir expressions basades en resultats d'accions precedents

main = do
 s <- getLine -- resultat "<-" efecte
 let x = (read s :: Int)
 y = x+1
 let x = y +2
 print x

Alternatives if, case modifica

-- if del haskell98 (en una única línia)
if expr_booleana then expr1 else expr2

-- if del haskell2010, es pot partir en línies
if expr_booleana
 then expr1
 else expr2

case expr of
 patró | guardaA -> exprA
 | guardaB -> exprB
 patró2 | guarda2 -> expr2

-- alternativa múltiple
case () of
 _ | condició1 -> expr1
 | condició2 -> expr2
 | otherwise -> expr3

-- versió moderna (desde GHC v. 7.6.1 !!) requereix pragma d'extensió de llenguatge MultiWayIf:
{-# LANGUAGE MultiWayIf #-}
if | x == 0 -> ...
 | x > 1 -> ...
 | x < 0 -> ...
 | otherwise -> ...

Iteracions funcionals sense efectes col·laterals modifica

Amb recursivitat els casos queden més clars de cara a assegurar-ne la finalització. Vegeu funció recursiva

bucle params = if condició params
 then {- cas simple -} expressió
 else {- cas recursiu -} bucle $ següent params
 where
 següent params = ...

Operadors modifica

  • Associativitat especificant {infixl: per l'esquerra | infixr: per la dreta | infix: no associatiu}
  • Precedència (0..9). Vegeu taula.[17]

Per defecte: màxima precedència i associativitat per l'esquerra (infixl 9)

 (++) :: [a] -> [a] -> [a] -- concatenació de dues llistes
 infixr 5 ++

 [] ++ ys = ys -- cas simple
 (x:xs) ++ ys = x : (xs ++ ys) -- cas recursiu

operadors de funcions modifica

Vegeu ref.[85]

(.) composició:

-- ús: punt envoltat d'espais
 f. g -- un robot de la viquipèdia malinterpreta els punts com a punt de final de frase i n'elimina els espais precedents

($) aplicació normal (tardana) f $ x = f x. Permet estalviar parèntesis en aplicar operacions sobre el darrer paràmetre quan és una expressió (hi ha més d'un terme) o bé la part esquerra és una composició de funcions.

Restricció: Hi ha objeccions a la seva aplicació en cas que el paràmetre o el retorn siguin funcions polimòrfiques que no tinguin declaració de tipus.[18]
f $ g $ x + y  f (g (x + y))

f. g $ x  (f. g) x

-- exemple:
print $ "el resultat és" ++ show result
-- en comptes de
print ("el resultat és" ++ show result)

lectura de dreta a esquerra en la composició amb (.) modifica

En aplicar una composició de funcions (f. g. h) obj sobre una variable o expressió, l'entendrem més fàcilment si les analitzem tenint en compte el tipus de l'objecte (domini) de l'aplicació i llavors les funcions que s'hi apliquen, començant per l'última de la composició, en sentit de dreta cap a l'esquerra.

Es pot escriure la composició a l'estil de flux de dades (d'esquerra a dreta), amb l'operador d'aplicació cap enrere (&) definit a Data.Function desde GHC 7.10.1

import Control.Exception (assert)
import Control.Category ((>>>)) -- morfismes definits a la classe Category. Les funcions la implementen!
import Control.Monad
import Data.Function ((&)) -- (&) = flip ($) -- infixl 1 -- desde GHC 7.10.1

obj = "0"
f = (++"3")
g = (++"2")
h = (++"1")

estil0 = f (g (h obj)) -- pels aimants dels parèntesis (enyorats del LISP)
estil1 = f $ g $ h obj -- estil aplicatiu
estil2 = (f. g. h) obj -- estil funcional
estil3 = (h >>> g >>> f) obj -- estil transformacional (morfismes)
estil4 = obj & h & g & f -- estil navegació de dades (com a la O.O. obj.mètode),

estils = [estil0, estil1, estil2, estil3, estil4]

main = do
 forM_ estils print -- imprimeix el resultat de cada estil
 assert comprovació $ print "són iguals"
 where
 comprovació = all (== "0123") estils

Currificació modifica

  • curry: converteix una func. d'una Tupla2 en una que admet els paràm. separats
+ possibilita l'aplicació parcial de part dels paràmetres
  • uncurry: conv. una funció de dos param. en una que els admet aparellats en Tupla2
 curry::((a,b) -> c) -> a -> b -> c

 uncurry::(a -> b -> c) -> (a,b) -> c

 --
 f = \(x,y) -> 2*x+y
 f_currificada = curry f

 assert (f (3, 4) == f_currificada 3 4) "equivalència de currificació"

 -- aplicació parcial
 op :: a -> b -> c
 (`op` segon) :: a -> c
 (primer `op`) :: b -> c

Traçabilitat dins el codi funcional modifica

El codi funcional no permet l'ús de print (efecte col·lateral). Cal fer servir trace o bé traceStack del mòdul Debug.Trace.[94] Imprimeix el primer paràmetre, via unsafePerformIO i retorna el segon.[95]

Atenció: l'ordre d'execució de subexpressions en codi funcional és indeterminat, i igualment ho serà l'ordre d'impressió de les traces.

En codi monàdic (seqüencial), l'ordre d'impressió de les traces, està subjecte al moment de l'avaluació que, per ésser tardana, és difícil de predir amb seguretat.

-- prova.hs
import Debug.Trace (trace) -- trace :: String -> a -> a

quadrat x = trace msg resultat
 where
 msg = "traça de quadrat: x: " ++ show x ++ " resultat: " ++ show resultat
 resultat = x * x

cub x = x * quadrat x

main = putStrLn $ "cub de 2: " ++ show (cub 2)

A partir de GHC v.7.4.1 hi ha també traceStack :: String -> a -> a[96] que a més bolca la traça de crides si s'ha compilat amb opcions de perfilat.

Substituïm trace per traceStack i ...

ghc --make -prof -fprof-auto-calls prova.hs
./prova
traça de quadrat: x: 2 resultat: 4
Stack trace:
 Main.quadrat.resultat' (prova.hs:6:17-39)
 Main.cub (prova.hs:10:13-21)
 Main.cub (prova.hs:10:9-21)
 Main.main (prova.hs:12:42-46)
 Main.main (prova.hs:12:36-47)
 Main.main (prova.hs:12:19-47)
 Main.main (prova.hs:12:8-47)
 Main.CAF (<entire-module>)
cub de 2: 8

Comportament: Classes de tipus modifica

Els tipus només descriuen el domini de valors.

Les classes designen la interfície (signatures) d'un conjunt d'operacions, amb possible implementació per defecte, parametritzades pel tipus de la classe.

Les classes de tipus no són com les classes de C++, que són classes d'objectes, sinó com els Interface de Java, els genèrics de l'Ada o les signatures de ML.

Per utilitzar-les amb un tipus concret, cal generar una instància de la classe per al tipus definint-hi la implementació. Les instàncies visibles en l'àmbit, es passen implícitament a les funcions que les requereixen.

  • Les instàncies no tenen identificació, no n'hi poden haver dues de la mateixa classe per al mateix tipus al mateix àmbit. Quan s'importa un mòdul s'importen totes les instàncies.[97]
  • Les instàncies no es poden tapar amb altres instàncies.[98]
  • Les instàncies s'han de definir en un dels mòduls on hi hagi, o bé la definició de la classe, o bé la del tipus, altrament seran etiquetades com a instàncies òrfenes i rebutjades pel compilador.[97]
  • L'única manera de redefinir instàncies és fer-ho sobre una derivació del tipus amb newtype, esmentant les altres instàncies a la clàusula deriving amb el mecanisme de l'extensió GeneralizedNewtypeDeriving que estalvia la redefinició d'instàncies.[97]

Especialització, instanciació per tipus modifica

L'ús d'operacions que no són de la pròpia classe a les implementacions per defecte, fa necessari requerir la presència, en el context d'ús, d'instàncies de les classes que incorporen les operacions.

-- per aquells tipus t que implementin les classes requerides
class (ClasseRequeridaA t, ClasseRequeridaB t, ...) => ClasseNova t where ...
{-# LANGUAGE NamedFieldPuns #-} -- simplificació en literals de registres 
 -- (interpreta 'camp' seguit de separador o delimitador, com a 'camp = camp')
class PropXY t where
 getXY :: t -> (Int, Int)
 setXY :: Int -> Int -> t -> t

-- funcionalitat 2D requereix que el tipus implementi PropXY

class (PropXY t) => IFun2D t where
 desplaça2D :: Int -> Int -> t -> t

 -- implementació per defecte a la classe

 desplaça2D dx dy punt = setXY (x+dx) (y+dy) punt
 where
 (x,y) = getXY punt

data TPunt2D = Punt2D {x, y ::Int}

instance PropXY TPunt2D where
 getXY Punt2D {x, y} = (x, y)
 setXY x y punt = punt {x, y} -- equival a punt {x = x, y = y}

instance IFun2D TPunt2D -- genera una instància per a TPunt2D amb la implementació per defecte

Vegeu exemple més complet més avall

Instanciació genèrica modifica

Quan totes les operacions es basen en operacions de les classes base i no en particularitats d'un tipus, es pot definir la instanciació a nivell de classe amb efecte automàtic. (Requereix l'extensió de llenguatge UndecidableInstances).

{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}

class PropXY t where
 getXY :: t -> (Int, Int)
 setXY :: Int -> Int -> t -> t

class IFun2D t where
 desplaça2D :: Int -> Int -> t -> t

instance (PropXY t) => IFun2D t where -- (IFun2D t) serà automàtic per a tot tipus t tal que (PropXY t)
 desplaça2D dx dy punt = setXY (x+dx) (y+dy) punt
 where
 (x,y) = getXY punt

Derivació d'instàncies per a tipus paramètrics amb requeriments modifica

Amb l'extensió de llenguatge StandaloneDeriving el compilador permet que afegeixis requeriments de context a la derivació d'instàncies amb clàusules deriving independents de la definició de l'estructura.[99]

{-# LANGUAGE StandaloneDeriving #-}

data Foo a = Bar a | Baz String

deriving instance Eq a => Eq (Foo [a])
deriving instance Eq a => Eq (Foo (Maybe a))

Derivació ràpida d'instàncies de classes amb implementació per defecte modifica

L'extensió DeriveAnyClass ho permet.[100] Si Bar és una classe on tots els mètodes tenen implementació per defecte

data Foo = Foo ...
instance Bar Foo -- sense implementació específica

es pot escriure en una sola línia (a banda de la pragma d'extensió de llenguatge)

{-# LANGUAGE DeriveAnyClass #-}

data Foo = Foo ... deriving (Bar)

Exemple a Compilador Haskell de Glasgow#serialització en format JSON

Tipus derivats -- newtype modifica

newtype defineix un tipus derivat d'un altre tipus (anomenat tipus base) amb possible herència de les instàncies de classes de tipus instanciades pel tipus base.

-- defineix el tipus TipusDerivat amb la mateixa representació interna que el tipus base
newtype TipusDerivat = Constructor TipusBase deriving (Classes_quines_instàncies_del_tipusBase_heretarà)
-- o bé
newtype TipusDerivat = Constructor { accessor: TipusBase} deriving ...

newtype[101] defineix un tipus derivat mitjançant una estructura d'un únic component, el tipus base, mantenint la mateixa representació interna (el compilador esborra el constructor).[102]

newtype defineix un morfisme (el constructor) del tipus base al derivat, que manté l'estructura de les classes que esmentem a la clàusula deriving heretant-ne, el tipus derivat, les instàncies. Si el newtype es defineix com a registre, l'accessor del camp constitueix el morfisme invers.

Per al cas de voler múltiples implementacions d'una classe de tipus (diverses relacions d'ordre o diversos monoides), com que les implementacions no tenen nom, cal definir un tipus derivat nou per cadascuna d'elles (vegeu exemple tot seguit).

L'extensió GeneralizedNewtypeDeriving permet que el tipus definit amb newtype hereti les instàncies de classes mono-paràmetre, esmentant-les a la clàusula deriving, més enllà de les quatre especificades per la versió Haskell98 del llenguatge (Eq, Ord, Enum, Bounded).[103]

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- | Monoide per a la suma.
newtype Sum a = Sum { getSum :: a }
 deriving (Eq, Ord, Read, Show, Bounded, Generic, Generic1, Num) -- instàncies a heretar

-- el constructor: Sum :: a -> Sum a -- morfisme que manté l'estructura de les classes esmentades
-- l'accessor: getSum :: Sum a -> a -- morfisme invers

instance (Num a) => Monoid (Sum a) where -- Num a => `a` té l'estructura algebraica d'anell 
 mappend (Sum x) (Sum y) = Sum (x + y) -- associativa
 mempty = Sum 0 -- element neutre

-- | Monoide per al producte.
newtype Product a = Product { getProduct :: a }
 deriving (Eq, Ord, Read, Show, Bounded, Generic, Generic1, Num)

instance (Num a) => Monoid (Product a) where
 mappend (Product x) (Product y) = Product (x * y) -- associativa
 mempty = Product 1 -- element neutre

ús:

import Data.Monoid
import Data.Foldable (Foldable, foldMap)
import Data.Category ((>>>)) -- (>>>): composició esq. a dreta

plegatSuma, plegatProd :: (Num a, Foldable t) => t a -> a
plegatSuma = foldMap Sum >>> getSum
plegatProd = foldMap Product >>> getProduct
  • Newtype també possibilita l'etiquetatge de tipus bàsics per evitar errors de transposició de paràmetres: "Keyword arguments in Haskell"[104]

ús dels newtypes predefinits modifica

Els tipus All i Any definits amb newtype per als quals s'implementa un Monoide per a la conjunció o bé disjunció, permeten un tractament similar en definir els següents quantificadors de col·leccions basats en propietats.

Apliquem el constructor adequat (All o bé Any) als valors i el plegat amb monoide farà servir l'op. definida per al newtype corresponent. En acabat, caldrà convertir el tipus del resultat amb la inversa del constructor: l'accessor.

{-| Ús dels newtype All i Any predefinits a Data.Monoid
-}

import Data.Function ((&)) -- (&) = flip ($) -- aplicació cap enrere
import Control.Category ((>>>)) -- f >>> g = g. f -- composició esq. a dreta

import Data.Monoid (All(..), Any(..))

{-| 
-- de la definició a Data.Monoid

newtype All = All { getAll :: Bool } -- implementa monoid per a la conjunció (&&)
 deriving (Eq, Ord, Read, Show, Bounded, Generic)

newtype Any = Any { getAny :: Bool } -- implementa monoid per a la disjunció (||)
 deriving (Eq, Ord, Read, Show, Bounded, Generic)

-- de Data.Foldable
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
-}

existeix, perATots :: Foldable t => (a -> Bool) -> t a -> Bool

-- convertint la sortida de 'prop' al Monoide adequat, i reconvertint el resultat:

existeix prop xs = foldMap (prop >>> Any) xs & getAny
perATots prop xs = foldMap (prop >>> All) xs & getAll

ésParell :: Integral t => t -> Bool
ésParell x = x `mod` 2 == 0

-- versió tàcita (sense param. formals reduïbles)
ésParell = (`mod` 2) >>> (== 0)

default (Int) -- desambiguació literals numèrics 

mostra = [1,2,3]

main = do
 print $ existeix ésParell mostra
 print $ perATots ésParell mostra

tipus derivats de funcions modifica

També s'utilitza per definir un tipus com a abstracció d'un tipus de funció. Això permet definir operacions de combinació de funcions del tipus específic, com a les Fletxes

 -- sigui, per exemple, f :: a -> [b] -- el tipus de funció sobre el que volem definir operacions
 -- x :: a

 newtype TFiltre a b = Filtre { runFiltre :: a -> [b] }

 -- Constructor: Filtre :: (a -> [b]) -> TFiltre 
 -- Inversa del constructor: runFiltre :: TFiltre -> (a -> [b]) 

 filtre_f = Filtre f -- element del nou tipus aplicant el constructor a la funció

 runFiltre filtre_f -- retorna la funció f original, aplicant la ''inversa_del_constructor''

 runFiltre filtre_f x -- aplica la funció original a x

Col·leccions polimòrfiques modifica

Tipus existencial modifica

El tipus existencial (contrari d'universal) designa tipus amb condicionants, i és un mecanisme de tipus per poder acomodar components de tipus divers que tinguin una interfície (operatòria, classe de tipus) en comú i així poder agrupar-los en col·leccions i tractar-ne els elements amb les operacions que comparteixen.

Vegeu [105] El tipus existencial, originàriament és una mena de tipus abstracte, relacionat amb registres que poden prendre diverses formes concretes que se'n consideren subtipus, donant lloc a un conjunt de tipus.

(pseudocodi)
T = ∃x { v: x; f: (x -> Int); } -- tipus existencial de registre, variant el tipus x
IntT = { v: Int; f: (Int -> Int); } -- subtipus on x = Int
FloatT = { v: Float; f: (Float -> Int); } -- subtipus on x = Float
  • El compilador GHC utilitza la paraula reservada forall (quantificador ∀), per als tipus existencials apel·lant a un isomorfisme.[106][107]
  • Altres compiladors de Haskell fan servir la paraula reservada exists com a quantificador existencial, el EHC/UHC segons la ref.,[108] i el JHC també,[109] en comptes del forall del GHC.

Tipificació més habitual, per exemple, objectes amb un component textualitzable (Show a)

 {-# LANGUAGE ExistentialQuantification #-}

data TObj = forall a. (Show a) => Obj a -- per aquells tipus 'a' textualitzables "(Show a)"

 -- Si l'expressem amb sintaxi GADTs, el quantificador no serà necessari
 {-# LANGUAGE GADTs #-}

 data TObj where
 Obj :: (Show a) => a -> TObj
quantificació existencial als registres

Vegeu ref.[110]

  • No es permeten els camps quantificats existencialment en les actualitzacions de registres.[110]
  • Cas de sintaxi GADT's els camps quantif. existencialment només poden aparèixer en actualitzacions, si la var. quantificada existencialment apareix individualment en el tipus resultat del Constructor, però no si hi apareix com a paràmetre, per exemple d'una llista.[110]
quantificació existencial als àlies de tipus - sinònims liberalitzats

Admet restriccions existencials, a més d'altres possibilitats.[66] Per aquells tipus 'b' tals que (Show b) ...

type Discard a = forall b. Show b => a -> b -> (a, String)

Llistes homogènies d'elements amb components heterogenis modifica

Vegeu.[111] El #Tipus existencial permet definir tipus de dades polimòrfics amb components caracteritzats per admetre un mateix grup d'operacions.

D'aquesta manera es pot treballar amb llistes d'elements que incorporen components de tipus divers que implementin una mateixa classe de tipus, i tractar-los amb les operacions de la classe.

Cal especificar l'opció de compilació "-XExistentialQuantification" o la pragma {-# LANGUAGE ExistentialQuantification #-}

{-# LANGUAGE ExistentialQuantification #-}

 -- tipus amb constructor Obj i un component
 -- d'aquells tipus ''a'' que implementin la classe Show

 data ObjPresentable = forall a. (Show a) => Obj a

 llistaHetero :: [ObjPresentable]
 llistaHetero = [Obj 1, Obj "foo", Obj 'c']

 mostra :: [ObjPresentable] -> String
 mostra [] = ""
 mostra (Obj x : []) = show x -- mostra el darrer (la cua és buida)
 mostra (Obj x : xs) = show x ++ ", " ++ mostra xs -- aplica al component x l'operació show
 -- de la classe Show que el component d'Obj implementa
 main = putStrLn $ mostra llistaHetero
--------

-- amb derivació automàtica d'instàncies
deriving instance Show ObjPresentable -- la derivació d'instàncies dels objectes existencials va separada

main = print llistaHetero

Vegeu-ne l'exemple #Col·leccions amb components heterogenis - Emulació del despatx dels mètodes virtuals de la O.O.

Tipatge dinàmic - Paràmetres de tipus divers modifica

Haskell admet pas de paràmetres amb control del tipus en temps d'execució, només per a tipus monomòrfics (sense variables de tipus).[112]

Exemple amb l'ús de l'extensió ViewPatterns (Vegeu #Patrons de vistes)

{-# LANGUAGE ViewPatterns, ScopedTypeVariables #-}

import Data.Dynamic

-- fromDynamic :: Dynamic -> Maybe a -- per obtenir un resultat, cal provar-ho amb restricció de tipus: 

-- control dinàmic del tipus del paràmetre, farem servir ViewPatterns: ''(vista -> patró)'' sobre el paràmetre.
printDynamic :: Dynamic -> IO ()
printDynamic (fromDynamic -> Just (x :: Int)) = putStrLn $ "era un Int: " ++ show x
printDynamic (fromDynamic -> Just (x :: Float)) = putStrLn $ "era un Float: " ++ show x
printDynamic (fromDynamic -> Just (xy :: (Int, Int))) = putStrLn $ "era un parell de Ints: " ++ show xy
printDynamic (fromDynamic -> Just (xs :: [Int])) = putStrLn $ "era una llista de Ints: " ++ show xs
printDynamic (fromDynamic -> Just (f :: (Int -> Int))) = putStrLn $ "era una funció (Int -> Int): 2 -> " ++ show (f 2)
printDynamic _ = error "printDynamic: tipus inesperat"

main = do
 printDynamic $ toDyn (1 :: Int)
 printDynamic $ toDyn (-2.5 :: Float)
 printDynamic $ toDyn ((2, 3) :: (Int, Int))
 printDynamic $ toDyn ([1..3] :: [Int])
 printDynamic $ toDyn ((*2) :: Int -> Int)

dona

$ runhaskell prova3
era un Int: 1
era un Float: -2.5
era un parell de Ints: (2,3)
era una llista de Ints: [1,2,3]
era una funció (Int -> Int): 2 -> 4

Seqüències i col·leccions en codi funcional pur modifica

Algunes de les funcions següents incorporen en la funció nucli de la iteració un paràmetre a l'entrada i a la sortida interpretable com a estat de la iteració, que evoluciona partint d'un valor inicial llavor per l'aplicació reiterada de la funció nucli.

Seqüències - generació d'una seqüència partint d'un valor modifica

És la particularització de Haskell d'un Anamorfisme o desplegament d'estructures: (b -> a*).[113]

-- en un anamorfisme que forma una llista:
anamorf :: (estat -> (valor, estat)) -> (estat -> Bool) -> estat -> [valor]
anamorf desplega ésFinal estat =
 if ésFinal estat
 then []
 else valor : (anamorf desplega ésFinal estat')
 where (valor, estat') = desplega estat

Amb la funció unfoldr ("unfold right", cat: desplega per la dreta) de Data.List. La seqüència s'acaba quan la funció nucli no retorna valor (Nothing).

unfoldr :: (llavor -> Maybe (item, llavor {-següent-})) -> -- funció nucli per obtenir opcionalment el següent ''ítem''
 llavor {-inicial-} ->
 [item]

Seqüències - transformació d'una seqüència amb memòria modifica

Amb mapAccumL de Data.List, podem especificar la funció nucli de la iteració, que partint d'un estat i un element de la seqüència ens proporcioni l'estat inicial de la següent iteració, i l'element de la seqüència de sortida.

mapAccum{L|R} :: (llavor -> item -> (llavor {-següent-}, item')) -> -- funció nucli de la iteració
 llavor {-inicial-} ->
 [item] {-inicial-} ->
 (llavor {-final-}, [item'])

Col·leccions - reducció d'una col·lecció a un valor o bé estructura (plegat) - Les classes Foldable/Bifoldable i Monoid modifica

És la versió de Haskell d'un catamorfisme o reducció d'estructures (a* -> b).[113]

Cal especificar com a paràmetre la funció del plegat:

foldl :: (acc -> item -> acc {-següent-}) -> -- funció nucli de la iteració
 acc {-inicial-} ->
 [item] {-inicial-} ->
 acc {-final-}

foldl op acc [] = acc
foldl op acc (x:xs) = foldl op (acc `op` x) xs

foldl de Data.List per a llistes ha estat substituïda en el mòdul Prelude per foldl de Data.Foldable per a col·leccions de tipus genèric.

Vegeu també iteracions a espai constant de la pila amb plegat per l'esquerra estricte

  • La classe Foldable, del mòdul Data.Foldable, generalitza la reducció per a les col·leccions
class Foldable t where
 foldr, foldr' :: (a -> acc -> acc) -> acc -> t a -> acc -- plegat per la dreta, foldr' estricte
 foldl, foldl' :: (acc -> a -> acc) -> acc -> t a -> acc -- plegat per l'esquerra, foldl' estricte
 foldMap :: Monoid m => (a -> m) -> t a -> m -- plegat amb mapeig a un tipus que implementa Monoide
 fold :: Monoid a => t a -> a -- plegat per a col·leccions d'elements que implementen Monoide
 ...
 -- plegats especials
 toList :: t a -> [a] -- foldMap pure 
 null :: t a -> Bool -- existeix algun element: null = foldr (\ _ _ -> False) True
 length :: t a -> Int -- compta els elements: length = foldl' (\ c _ -> c+1) 0
 elem :: Eq a => a -> t a -> Bool -- algun element igual?: elem = any. (==)

 maximum, minimum :: forall a. Ord a => t a -> a -- obté el superior/inferior per a tot element de la col·lecció NO buida
 sum :: Num a => t a -> a -- plegat sobre el monoide (a,+,0)
 product :: Num a => t a -> a -- plegat sobre el monoide (a,*,1)

-- altres plegats
and :: Foldable t => t Bool -> Bool -- plegat sobre el monoide (Bool,&&,True)
or :: Foldable t => t Bool -> Bool -- plegat sobre el monoide (Bool,||,False)
concat :: Foldable t => t [a] -> [a] -- plegat sobre el monoide ([a],++,[])
all, any :: Foldable t => (a -> Bool) -> t a -> Bool -- plegat aplicant predicat
  • En cas que vulguem avaluació estricta, si l'acumulador és un tipus algebraic convé especificar avaluació estricta explícita als components de l'acumulador, altrament l'avaluació HNF deixa pendent d'avaluar les expressions dels components, engreixant la pila que pot petar de la mateixa manera que en els fold{l|r} no estrictes.
la classe Bifoldable

Des de GHC 8.2 el mòdul Data.Bifoldable proporciona, per a col·leccions d'elements de dominis duals, per ex. (Either a b), la possibilitat de reduir amb dues funcions de plegat, una per cada domini.

class Bifoldable t where
 bifoldMap :: Monoid m => (a -> m) -> (b -> m) -> t a b -> m

 bifoldr :: (a -> acc -> acc) -> (b -> acc -> acc) -> acc -> t a b -> acc

 bifoldl :: (acc -> a -> acc) -> (acc -> b -> acc) -> acc -> t a b -> acc
 ...

Per cada funció de Data.Foldable hi ha una funció corresp. de Data.Bifoldable anb el prefix bi.

Monoides per als plegats modifica

  • La classe Monoid, del mòdul Data.Monoid, facilita la definició de Monoides
  • Podem necessitar diferents implementacions de Monoide (ex. per a la Suma i per al Producte), però les instàncies no tenen nom i per tant no es poden seleccionar. Cal crear tipus derivats amb newtype[114] per cada instància addicional. El mòdul Data.Monoid en té uns quants de predefinits.
  • Els tipus derivats amb newtype només poden tenir un sol component (el tipus base). Si estan definits com a registre, l'accessor ens proporcionarà la conversió inversa de la del constructor. No suposen cap sobrecost d'accés, doncs el compilador esborra el constructor.[102]
class Monoid a where
 mappend :: a -> a -> a -- operació associativa del monoide
 mempty :: a -- element neutre del monoide
 ...

-- Hi ha monoides predefinits, per exemple per a la suma i el producte

newtype Sum a = Sum { getSum :: a}

-- * el constructor del ''newtype'' constitueix una funció del tipus base al tipus derivat
-- * l'accessor del ''newtype'' constitueix una funció del tipus derivat al tipus base

instance (Num a) => Monoid (Sum a) where
 mappend (Sum x) (Sum y) = Sum (x + y) -- associativa
 mempty = Sum 0 -- element neutre

newtype Product a = Product { getProduct :: a }

Exemple. Plegat de la suma i el producte sobre una col·lecció de numèrics (Num t), utilitzant els "newtype" respectius:

import Data.Monoid (Sum(..), Product(..)) -- importa constructor i accessors del tipus ..
import Data.Foldable (Foldable, foldMap)
import Data.Category ((>>>)) -- composició d'esquerra a dreta: f >>> g == g. f 

plegatSuma, plegatProd :: (Num a, Foldable t) => t a -> a
plegatSuma xs = getSum $ foldMap (Sum) xs

-- alternativa tàcita (sense paràmetres formals reduïbles)
plegatSuma = foldMap Sum >>> getSum 
plegatProd = foldMap Product >>> getProduct

Col·leccions - composició - les classes Semigroup i Monoid modifica

Des de GHC 8.0 s'ha incorporat al paquet base l'estructura Semigrup en un mòdul Data.Semigroup amb una operació associativa definida per (<>) amb associativitat infixr 6 <>.

Abans ja teníem la classe Monoid de Data.Monoid on mappend és l'operació associativa que en posició infix (`mappend`) processa associant per l'esquerra (assoc. per defecte: infixl 9), mentre que també es defineix (<>) com a sinònim que processa associant per la dreta (infixr 6 <>).

Exemple sobre llistes, conjunts i diccionaris (tots ells implementen les classes Semigroup i Monoid). Els vectors del paquet vector també.

ghci
Prelude> import Data.Semigroup
Prelude Data.Semigroup> [1,2] <> [3,4] -- composició de llistes
[1,2,3,4]

Prelude Data.Semigroup>:set -XOverloadedLists -- extensió OverloadedLists per l'ús de literals llistes en altres tipus
Prelude Data.Semigroup> import Data.Set as S
Prelude Data.Semigroup S> import Data.Map as M
Prelude Data.Semigroup S M> import Data.Vector as V
Prelude Data.Semigroup S M V> ([1,2] :: Vector Int) <> ([3,4] :: Vector Int) -- composició de vectors
[1,2,3,4]

Prelude Data.Semigroup S M V> ([1,2] :: Set Int) <> ([2,3] :: Set Int) -- composició de conjunts
fromList [1,2,3]

Prelude Data.Semigroup S M V> ([(1,"a"), (2,"b")] :: Map Int String) <> ([(2,"b'"), (3,"c'")] :: Map Int String) -- composició de diccionaris
fromList [(1,"a"),(2,"b"),(3,"c'")]

Col·leccions - aplicació als elements d'una col·lecció modifica

La classe Functor modifica

La classe Functor descriu una estructura de correspondències entre elements i contenidors que mapeja objectes a objectes i funcions a funcions mantenint la identitat dels objectes i la composició de les funcions en els dos dominis.

Un functor F és un homomorfisme que compleix:

  •   per a tot objecte   del domini d'origen; en Haskell: fmap id ≡ id,
  •   per a tot morfisme   i  ; en Haskell: fmap (g · f) ≡ (fmap g) · (fmap f)

Això possibilita substituir la composició d'aplicacions de morfismes ((fmap g) · (fmap f)), per l'aplicació de la composició (fmap (g · f) ). (Vegeu també desforestació).

L'operació fmap[115] defineix la correspondència de funcions del domini dels elements al domini dels contenidors mitjançant l'aplicació d'una funció als elements de les col·leccions (ex.: [a]) o bé als resultats dels d'efectes (ex.: IO a) que l'instancien.

class Functor t where
 fmap :: (a -> b) -> (t a -> t b) -- aplica una funció als elements de la col·lecció ''t''
 -- o bé si ''t'' és un efecte (ex. IO), al resultat de l'efecte

Si la funció no és injectiva la imatge de diversos elements del domini origen pot coincidir, i en cas de tractar-se d'un conjunt, no preserva la mida de l'original, incomplint la regla de la composició com a l'exemple de la ref.[116][117]

  • Tanmateix les implementacions de conjunts aporten la seva operació map particular, malgrat no implementar Functor.
  • Els tipus monomòrfics (que no incorporen paràmetres, com ara Text, ByteString) tampoc. (El tipus ha de venir parametritzat amb el tipus de l'element)
  • El paquet mono-traversable aporta una solució vàlida per a totes les col·leccions siguin paramètriques o monomòrfiques, basada en classes genèriques i, per als tipus dependents, "tipus associats" (Index t, ContainerKey t, MapValue t) o bé #Famílies de tipus (Element t).

La classe Bifunctor modifica

Des de GHC 8.2 el paquet base estén els Functors a les col·leccions d'elements amb domini dual (per ex.: Either a b) amb mapeig que incorpora funcions per cadascun dels dominis. Del mòdul Data.Bifunctor

class Bifunctor p where

 bimap :: (a -> b) -> (c -> d) -> p a c -> p b d

 first :: (a -> b) -> p a c -> p b c
 second :: (b -> c) -> p a b -> p a c

Functors contravariants - la classe Contravariant modifica

Quan el contingut del contenidor és una funció, es pot definir un Functor sobre el domini dels arguments (posicions contravariants) de la funció.

La covariança i contravariança es fa servir més als llenguatges que implementen subtipatge. Subtipatge, covariança i contravariança a Scala

Al paquet contravariant.[118]

newtype Predicat a = Predicat {getPredicat :: a -> Bool} -- tipus 'a' en posició contravariant

newtype Comparació a = Comparació {getComparació :: a -> a -> Ordering} -- tipus 'a' en posició contravariant

-- Donada una relació 'f' en el conjunt del tipus 'b', una projecció (a -> b) estableix la relació en el conjunt del tipus 'a' entre aquells elements quines imatges formin part de la relació.
class Contravariant f where
 contramap :: (a -> b) -> f b -> f a
 ...

-- contramap sobre el newtype Comparació, que defineix una relació d'ordre, confereix ordenació al domini origen de la projecció

-- per exemple: una projecció sobre parells:
ghci
Prelude> import Data.Functor.Contravariant as C
Prelude C> import Data.Function as F ((&))

-- apliquem amb "contramap" una projecció "snd" cap al domini contravariant de Comparison, obtenint un Comparison sobre l'origen de la projecció, els parells
Prelude C F> let f = contramap snd (Comparison compare) 
Prelude C F> :t f -- el tipus del resultat és Comparison sobre parells, comparant pel segon de la tupla
f :: Ord a1 => Comparison (a, a1)

Profunctors modifica

Un Profunctor és un Bifunctor que és contravariant en el domini del primer paràmetre de tipus i covariant en el segon.

Per tant podem qualificar el primer paràmetre com a entrada, i el segon com a sortida (Les funcions en serien una instància donat que els paràm. d'una funció són contravariants i la sortida covariant).

Podem implementar els profunctors en tot allò que tingui un o més arguments (posició contravariant) del domini del primer paràmetre de tipus i un resultat (posició covariant) del segon paràmetre de tipus.

L'objectiu és poder aplicar simultàniament un contramap (funció incident) al domini contravariant (l'entrada) i un map (funció sortint) al domini covariant (la sortida), obtenint un nou profunctor sobre els dominis origen del contramap i el de destí del map.

Al paquet profunctors.[119]

class Profunctor p where
 dimap :: (a -> b) -> (c -> d) -> p b c -> p a d

Exemple d'instàncies de Profunctors són

  • les funcions: ((->) a b)
  • les Fletxes: (Arrow p => p a b) (vegeu secció d'efectes fletxa)

Els profunctors poden ser base per a les Lents.[120][121]

Efectes modifica

Un efecte és un possible canvi (d'estat, de l'entorn, una excepció, una fallada de càlcul (resultat no definit)), amb la possible producció d'un, diversos o bé cap resultat, que recomani seqüenciar les operacions que en depenguin per aconseguir la constància del resultat per a les mateixes entrades.

Es representa amb un tipus parametritzat pel tipus del resultat, com ara (IO tipusDelResultat).

  • efecte simple: quan sempre hi ha resultat (representable amb (Efecte a)).
  • efecte fallada: quan no sempre s'obté resultat (Les mònades amb element absorbent per l'esquerra en (>>=) en faciliten l'ús,[122] per ex.: un tipus opcional: (Maybe a)). Vegeu Mònades amb efecte fallada. La implementació de l'element absorbent per l'esquerra en (>>=) fa innecessari la comprovació de la correctesa a cada encadenament.
  • efecte múltiple amb possible fallada: diversos resultats o bé cap. Vegeu la classe MonadPlus, que permet la composició de resultats amb un Monoide.

Exemples:

  • IO a : efectes superficials (ent/sortida, vars. globals, excepcions) amb resultat de tipus 'a'
  • ST s a : encapsulament d'actualitzacions destructives (vars. locals amb cicle de vida limitat a l'àmbit i avaluació del càlcul)
(La variable s representa l'espai d'avaluació de l'efecte)
  • Maybe a : efecte fallada, resultat opcional de funcions parcialment definides.
  • Either err a: efecte fallada amb info. de l'error, resultat de l'avaluació de codi de resultat incert. (try acció_que_pot_disparar_excepcions)

Composició d'efectes - Combinació o bé encadenament de resultats modifica

  • La classe Applicative possibilita combinar els resultats de diverses accions amb una funció combinadora elevada al tipus de l'efecte. Defineix la composició d'un efecte amb resultat funció amb un efecte subseqüent sobre quin resultat s'aplicarà la funció resultant de l'efecte precedent. Com que en aplicar una funció de N paràmetres sobre un valor s'obté una funció de (N-1), podrem combinar els resultats d'una seqüència de N efectes amb un efecte que tingui per resultat una funció de N paràmetres o bé un constructor de N components. Vegeu Functor aplicatiu
  • La classe Mònada possibilita la composició d'efectes mitjançant l'encadenament (>>=) combinant un efecte amb una funció d'efectes sobre el resultat de l'efecte precedent. Vegeu Mònada (programació funcional)
  • La classe Arrow (cat:Fletxa) generalitza les mònades prenent les funcions amb efecte de la mònada, fixant-ne el tipus de l'entrada, modelant la seqüència d'efectes com un encadenament de funcions d'efectes col·laterals. Per poder efectuar càlculs amb els resultats en una seqüència d'efectes, caldrà aparellar el resultat precedent amb l'actual, i utilitzar el tractament de parells que les fletxes inclouen. Vegeu Fletxa (programació funcional)

El model Applicative no seqüència les computacions sinó només la seva engegada i per això s'utilitza en la combinació de processos paral·lels, mentre que en els altres models el resultat de la computació precedent s'ofereix de paràmetre de la següent.

Efectes aplicatius modifica

Efectes que implementen la classe Applicative.[123]

Generació d'efectes aplicatius modifica
  • A la classe Applicative (mòdul Control.Applicative) "pure" eleva un valor (funcions incloses) a la categoria d'efecte aplicatiu (que implementa Applicative).
-- generador
pure :: tipResultat -> efecte tipResultat

-- exemple
instance Applicative Maybe where
 pure = Just 
 ...

-- ús habitual, tenint en compte que (<*>) :: efecte (a -> b) -> efecte a -> efecte b
-- combina els resultats de N accions amb un combinador de N paràmetres

combinaNAccions :: (a1 -> a2 -> ... -> aN -> r) :: efecte r
combinaNAccions combinadorAmbNParàmetres = (pure combinadorAmbNParàmetres) <*> acció1 <*> acció2 <*> ... <*> accióN
Combinació de resultats d'efectes aplicatius modifica
  • La classe Applicative facilita la combinació dels resultats d'una seqüència d'efectes amb un combinador (funció, o bé un constructor), elevat a la categoria d'efecte. És com aplicar la funció o constructor elevats a efecte, als resultats dels efectes com a paràmetres.
  • El generador pure servirà per elevar el combinador (funció o bé constructor) a la categoria d'efecte.
  • La funció fmap si s'aplica a un combinador de diversos paràmetres sobre un efecte, retorna un efecte funció aplicable sobre la resta de paràmetres.
  • Applicative no pressuposa la seqüenciació dels efectes col·laterals sinó només la seva engegada, doncs els efectes poden executar-se en paral·lel i esperar que acabin per combinar-ne els resultats, com ara el cas de l'efecte aplicatiu Concurrently que combina resultats d'accions executades en paral·lel.
class (Functor efecte) => Applicative efecte where
 pure :: tipResultat -> efecte tipResultat -- l'utilitzarem per elevar el combinador a la categoria de l'efecte.

 -- (<*>) aplica la funció resultant del primer efecte, al resultat del segon
 -- si la funció té N paràmetres, el resultat serà una funció de (N-1) paràmetres que es podrà combinar amb els resultats de (N-1) efectes subsegüents
 (<*>) :: efecte (a -> b) -> efecte a -> efecte b 

 -- (*>) i (<*) seqüencien dos efectes, retornant només un dels resultats
 (*>) :: efecte a -> efecte b -> efecte b 
 (<*) :: efecte a -> efecte b -> efecte a 

instance Applicative Maybe where
 pure = Just

 Just f <*> acció = fmap f acció
 Nothing <*> _ = Nothing
 ...

Exemple combinant amb el constructor de Tupla2:

-- (,) :: a -> b -> (a, b)
-- pure (,) :: efecte (a -> b -> (a, b)) -- pure eleva el constructor (,) a la categoria d'efecte
aparellaDuesEntrades = pure (,) <*> llegeixPrimer <*> llegeixSegon

-- fmap de la classe Functor (base per a la Applicative) sobre la funció i el primer efecte
fmap :: (a -> b) -> efecte a -> efecte b -- si la funció. té més d'un param. (''b'' és una funció)
 -- podrem combinar el resultat de fmap amb (<*>)
aparellaDuesEntrades = (fmap (,) llegeixPrimer) <*> llegeixSegon -- els parèntesis són per subratllar (no fan falta)

-- substituint fmap per l'operador infix equivalent (<$>) tenim la forma més utilitzada
aparellaDuesEntrades = (,) <$> llegeixPrimer <*> llegeixSegon

Efectes monàdics modifica

Efectes que implementen la classe Monad.[124]

Generació d'efectes monàdics modifica
  • A la classe Mònada (mòdul Control.Monad) "return" eleva un valor a la categoria d'efecte monàdic (que implementa Monad).
-- generador
return :: tipResultat -> efecte tipResultat

-- exemple
instance Monad Maybe where
 return = Just 
 ...

-- els efectes monàdics tenen una sintaxi especial en els blocs 'do' explicats més avall.
acció params = do
 ...
 return resultat -- return genera un valor mònada amb el paràm. com a resultat

Actualment la classe Applicative és base per a la classe Monad, i return = pure, i (>>) = (*>)

Seqüenciació d'efectes monàdics modifica
  • A la classe Mònada (mòdul Control.Monad) (>>=) encadena un efecte amb una funció efecte sobre el resultat de l'efecte precedent.
class (Applicative efecte) => Monad efecte where
 return :: tipResultat -> efecte tipResultat

 (>>=) :: efecte a -> (a -> efecte b) -> efecte b

-- exemple:
llegeixData = llegeixDia >>= (\ dia -> llegeixMes >>= (\ mes -> if ésVàlidDiaDelMes dia mes
 then return $ construeixData dia mes
 else ioError $ userError "data incorrecta"
))

-- (>>) encadena dues accions, ignorant el resultat de la primera
 (>>) :: Monad efecte => efecte a -> efecte b -> efecte b

 -- (>>) s'expressa en termes de (>>=)
 m_x >> m_y = m_x >>= (\ _ -> m_y)

Actualment la classe Applicative és base per a la classe Monad i els operadors corresp. ofereixen la mateixa operatòria(>>) = (*>)

Les funcions del mòdul Control.Monad (forM_ llista acció, forever acció, replicateM_ n acció) fan la feina de les clàusules de control imperatiu d'altres llenguatges (forEach, while true do ..., for i = 1 to n do ...).[125]

Vegeu també Combinadors aplicatius partint de mònades

Efectes fletxa modifica

Vegeu ref.[126]

Una fletxa, concepte desenvolupat per John Hughes, és una generalització d'una funció on el resultat pot dependre d'efectes col·laterals o bé ser independent de l'entrada.[126] Es concreta en les classes Category[127] (que generalitza les funcions amb paràmetres pel tipus de l'entrada i de la sortida) i la seva especialització Arrow[128] que refina les categories amb el tractament de parells, necessari per combinar-ne resultats. Vegeu Fletxa (programació funcional)#Fletxes i Parells

Generació d'efectes fletxa modifica
  • A la classe Arrow (mòdul Control.Arrow) "arr" eleva una funció a la categoria d'efecte fletxa (computacions amb tractament de parells).
-- generador
arr :: (tipEntrada -> tipResultat) -> efecte tipEntrada tipResultat

Les fletxes generalitzen les mònades. Podem convertir qualsevol funció d'efectes monàdics (a -> efecte b) en una Fletxa de Kleisli, prefixant-la amb el constructor Kleisli.

-- del mòdul Control.Arrow del paquet ''base''

newtype Kleisli efecte a b = Kleisli { runKleisli :: a -> efecte b }

-- inclou una instància de Fletxa per al tipus Kleisli
instance Monad efecte => Arrow (Kleisli efecte) where ...

A ghci:

* Prelude Control.Arrow> let funMonàdica = return

* Prelude Control.Arrow> :t funMonàdica
funMonàdica :: (Monad m) => a -> m a

* Prelude Control.Arrow> :t (Kleisli funMonàdica)
Kleisli funMonàdica :: (Monad m) => Kleisli m a a
Seqüenciació d'efectes fletxa modifica

Encadenament de computacions:

 -- els morfismes venen parametritzats pel tipus de l'entrada i el tipus del resultat
 class Category cat where
 id :: cat a a -- morfisme identitat
 (.) :: cat b c -> cat a b -> cat a c -- composició de morfismes de dreta cap a l'esquerra

 (>>>) :: Category cat => cat a b -> cat b c -> cat a c -- composició de morfismes (d'esq. a dreta)
 (<<<) :: Category cat => cat b c -> cat a b -> cat a c -- sinònim de (.)

 -- les funcions (a -> b) implementen Category
 -- els efectes fletxa s'hi basen.

La composició de fletxes ha de mantenir l'operació generadora (arr) i el tractament de parells.[130]

class (Category efecte) => Arrow efecte where

 arr :: (b -> c) -> efecte b c -- eleva una funció a la categoria d'efecte fletxa

 -- first converteix un efecte fletxa en un altre sobre parells, actuant sobre el primer element.
 first :: efecte b c -> efecte (b, d) (c, d)
 ...

arr (f >>> g)  arr f >>> arr g -- per a les funcions f i g

first (f >>> g)  first f >>> first g -- per a les fletxes f i g

Exemples: Ent./Sort. amb fletxes de Kleisli (mònades elevades a fletxes) i el tractament de XML amb fletxes sobre subarbres XML.[131]

Compiladors que suporten Fletxes: GHC,[132] Hugs[133]

Entrada / Sortida - Mònades modifica

Les operacions d'entrada/sortida produeixen efectes a l'entorn i un resultat programàtic opcional en forma de valor (cas d'entrada), es representa amb el tipus (IO a). En cas de sortida, es retorna el valor buit (), resultant el tipus (IO ()).

Per la seqüenciació d'operacions s'utilitza preferentment la classe mònada sobre el tipus (IO a), donant lloc a la mònada IO.

La funció inicial main de qualsevol programa ha de ser una expressió d'operacions d'ent./sortida i per tant del tipus de la mònada IO.

La funció evaluate permet seqüenciar com a efecte IO, les excepcions que pot llançar una expressió funcional pura.

Les accions de lectura/escriptura de variables globals IORef també formen part dels efectes IO.

 -- expressions de la ''mònada'' IO (el tipus porta com a paràmetre el tipus del resultat de les operacions)
 getLine -- captura línia d'entrada amb resultat String -- tipus ''IO String''
 getLine >>= putStr -- imprimeix línia introduïda (resultat buit) -- tipus ''IO ()''
 putStr "polseu Intro" >> getLine >> putStrLn "Fet" >> return True -- (resultat de la cadena: booleà) tipus ''IO Bool''
 return "abc" >>= putStrLn -- estableix "abc" com a resultat monàdic i l'imprimeix tot seguit

 -- >>= -- encadena amb una funció efecte que s'aplica al resultat de l'efecte precedent
 -- >> -- encadena amb un altre efecte, ignorant el resultat del precedent
 -- return x -- op. generadora d'una mònada amb resultat x quedant del tipus IO X
 -- no pressuposa ''retorn'' d'enlloc; per ex. (return 5 >>= print)

 -- fail missatge -- cridada internament, cas que el patró a l'esquerra de (<-) en un bloc 'do' no sigui exhaustiu.
 -- des de GHC 8.0 la funció 'fail' se separa de la classe Monad i va a la classe MonadFail amb instàncies per als tipus IO i Maybe.

Blocs Do: notació especial de les expressions monàdiques modifica

Encadenament de les instruccions d'un bloc. Vegeu mònada - Blocs Do

La clàusula do exposa l'encadenament d'efectes de manera seqüencial, amb una expressió monàdica per línia, introduint l'estil imperatiu.

correspondència
sintaxi mònada expressió equivalent comentari
patró <- acció; e Mònada amb mètode fail
per si falla l'encaix
let { f patró = e ;
f _ = fail "error ..." }

in acció >>= f
mònada Monad a GHC < 8.8[134]
o bé mònada MonadFail a GHC >= 8.0[135]
Mònada sense mètode fail.
L'encaix haurà de ser irrefutable
acció >>= (\ patró -> e) mònada Monad des de GHC >= 8.8[134]
acció; e acció >> e
let {x = v1; y = v2} ; e let {x = v1; y = v2} in e el bloc del let es pot expressar amb sagnat
aliniant el sagnat a la primera variable
-- exemple de bloc 'do'
bloc = do
 x <- getLine -- (<-) separa sintàcticament el resultat de l'efecte que el produeix
 putStrLn "Has teclejat: "
 putStrLn x
 return x

-- el patró (resultat "<-" efecte (";"|"\n") resta_del_bloc) 
-- es tradueix per (efecte >>= \ resultat -> resta_del_bloc)
-- les altres línies s'encadenen amb (>>) que ignora el resultat de l'efecte precedent

-- equivalent amb expressions monàdiques
bloc = getLine >>= \ x -> (-- encadena amb una lambda (funció anònima)
 -- la resta del bloc s'engloba al cos de la lambda
 -- permetent l'accés a la variable a totes les expressions del bloc
 putStrLn "Has teclejat: " >>
 putStrLn x >>
 return x
) -- el parèntesi només és per explicitar que l'abast d'una lambda és el màxim

Vegeu també Recursivitat en les definicions als subblocs let, i també, recursivitat en els efectes en subblocs rec

Seqüències i efectes - la classe Traversable modifica

Permet l'avaluació seqüencial dels elements d'una col·lecció travessable d'esquerra a dreta si són efectes o bé de les imatges del mapeig dels elements amb una funció d'efectes (a -> efecte b). Vegeu el mòdul Data.Traversable.[136]

  • Traversable i efectes aplicatius (que implementen Applicative)
class (Functor t, Foldable t) => Traversable t

 -- 'traverse' avalua seqüencialment les imatges d'aplicar una funció d'efectes aplicatius als elements d'una col·lecció
 traverse :: Applicative efecte => (a -> efecte b) -> t a -> efecte (t b)

 -- 'sequenceA' avalua seqüencialment els elements d'una col·lecció d'efectes aplicatius 
 sequenceA :: Applicative efecte => t (efecte a) -> efecte (t a) 
 sequenceA xs = traverse id xs
  • Traversable i efectes monàdics (que implementen Monad)
class (Functor t, Foldable t) => Traversable t

 -- 'mapM' avalua seqüencialment les imatges d'aplicar una funció d'efectes monàdics als elements d'una col·lecció
 mapM :: Monad efecte => (a -> efecte b) -> t a -> efecte (t b)

 -- 'sequence' avalua seqüencialment els elements d'una col·lecció d'efectes monàdics 
 sequence :: Monad efecte => t (efecte a) -> efecte (t a)
 sequence xs = mapM id xs
  • Des de GHC 8.2 la classe Bitraversable ofereix una operativa similar per a col·leccions d'elements de domini dual, per ex. (Either a b)
class (Bifunctor t, Bifoldable t) => Bitraversable t where
 -- bitraverse avalua seqüencialment les imatges d'aplicar una funció d'efectes monàdics als elements d'una col·lecció
 -- proporcionant una funció diferent per als elements segons el domini.
 bitraverse :: Applicative f => (a -> f c) -> (b -> f d) -> t a b -> f (t c d) 
 ...
  • forM equival a mapM amb els paràmetres intercanviats.
  • hi ha versions amb un guió baix al final per quan només interessa l'efecte col·lateral descartant els resultats (resultat buit com a (IO ())). forM_ equival al forEach d'altres llenguatges.
import Control.Monad (forM, forM_)

-- forM avalua seqüencialment una funció efecte per als elements d'una llista
-- i retorna la llista de resultats com a resultat de l'efecte

imprimeix_i_retorna_dobles :: [Int] -> IO [Int]
imprimeix_i_retorna_dobles xs = forM xs $ \x -> do -- per cada x de la llista xs
 let y = 2 * x
 print y
 return y

-- el bloc ''do'' és un artifici sintàctic per encadenar instruccions d'efectes
-- per una de sola no paga la pena.

imprimeix_dobles :: [Int] -> IO ()
imprimeix_dobles xs = forM_ xs $ \x -> -- les versions amb guió baix al final (forM_) retornen (IO ())
 print $ 2 * x -- no cal fer un bloc ''do'' per una sola instrucció d'efectes
  • Bloc do com a paràmetre:

El bloc do no és més que un artifici sintàctic per escriure una expressió de tipus mònada, una acció formada per l'encadenament d'accions amb funcions d'efectes sobre el resultat de l'acció precedent. Com a tal expressió es pot posar com a paràmetre.

Aquí el posem com a paràmetre (operand de '$') d'un llaç (Monad.forever) amb control d'excepcions.

($) és una funció amb polimorfisme de rang 1 (Vegeu rank-n polymorphism), i no admet funcions polimòrfiques com a paràmetre.

import Control.Monad as Monad

main = do
 print "Farem l'eco fins que l'entrada sigui buida."
 catch(
 Monad.forever $ do -- bucle infinit, caldrà una excepció per sortir-ne
 x <- getLine
 case x of
 [] -> ioError (userError "Línia buida") -- llança excepció
 _ -> putStrLn x
)
 (\excep -> print excep
)
Alternatives en efectes - Blocs do niuats modifica

Per posar més d'una instrucció en una branca d'un "case" o d'un "if" cal agrupar-les dins un subbloc "do"

Les funcions when i unless de Control.Monad proporcionen l'avaluació condicionada d'un efecte.[137]

Recursivitat en els blocs do modifica

Vegeu Mònada (programació funcional)#Recursivitat en els blocs do

excepcions modifica

1. Les excepcions del mòdul Control.Exception de la biblioteca base són un efecte global IO. Poden ser llançades des de codi funcional, però només poden ésser caçades en una expressió o bloc do de la mònada IO[138] Aquesta limitació es pot superar si fem servir la biblioteca exceptions descrita a continuació.

2. La biblioteca exceptions suporta tractament d'excepcions genèric respecte al tipus de la mònada, i és extensible a les mònades transformades. A més inclou els tipus d'excepció genèric SomeException de la biblioteca base. Les IOError (IOExceptions) són admeses en el context de la mònada IO que implementa les classes definides. També inclou un transformador per al tractament encapsulable en codi funcional pur.[139]

3. La biblio safe-exceptions permet tractar adequadament de manera diferenciada les excepcions síncrones i les asíncrones (les llançades des d'altres fils d'execució al propi) sense perdre'n cap pel camí.[140]

Habitualment les expressions funcionals s'avaluen de manera tardana en el moment que se'n demana el valor. Per assegurar-ne la caça i tractament d'excepcions en un punt, la funció evaluate de Control.Exception permet forçar-ne el càlcul dins el codi seqüencial (monàdic) fent aflorar les possibles excepcions en un punt de la seqüència. Si l'expressió conté tipus algebraics, cal tenir en compte que els constructors poden aturar l'avaluació de les expressions dels components si aquests no estan definits com a estrictes (prefix '!'). Per avaluar-los completament en aquest cas, caldrà avaluar en profunditat a Forma Normal, fent servir force de DeepSeq com a (evaluate. force $ expr).[141][142]

evaluate :: a -> IO a -- força l'avaluació (a WHNF) d'una computació funcional per aflorar-ne les excepcions
--
-- amb el mòdul DeepSeq podem forçar les computacions d'expressions amb resultat de tipus algebraics
-- (perquè avaluant només a WHNF, els constructors aturen l'avaluació de les expressions dels components)
import "deepseq" Control.Deepseq

avaluaEnProfunditat :: NFData a => a -> IO a
avaluaEnProfunditat = evaluate. force

Segons l'apartat Catching exceptions del mòdul Control.Exception.[143]

  • Per fer neteja en recuperar-se d'una excepció, es recomana bracket o bé finally o bé onException
  • Per recuperar-se d'una excepció i fer alguna cosa més, es recomana try
  • Per caçar excepcions asíncrones (les llançades des d'un altre fil d'execució) es recomana catch o bé catches.

Vegeu també "Exceptions Best Practices in Haskell".[144]

Generadors d'excepcions modifica

  • userError: generador d'excepcions d'usuari del Haskell98.
 userError :: String -> IOError
 userError missatge -- construeix una excepció IOError de l'aplicació

-- Per discriminar diferents 'IOError' al GHC actual, 
-- o bé fas encaix de patrons (userError msg)
-- o bé se'n pot consultar el subtipus i els atributs
-- al mòdul System.IO.Error, tenim 
-- isUserError :: IOError -> Bool
-- ioeGetErrorString :: IOError -> String
  • la nova classe Exception permet generar excepcions de tipus definits per l'usuari. Requereix que el tipus implementi Show i Typeable.
{-# LANGUAGE DeriveDataTypeable #-}
import Control.Exception
import Data.Typeable

data TExcepcionsDeLAplicació = EParàmetreIl·legal String | EUnaAltraExcepció String
 deriving (Show, Typeable)

instance Exception TExcepcionsDeLAplicació -- instancia els mètodes per defecte de la classe ''Exception''

Llançadors modifica

throw :: Exception e => e -> a -- llança excep en el codi funcional pur
throwIO :: Exception e => e -> IO a -- llança excep. en el context de la mònada IO (efecte global)
ioError :: IOError -> IO a -- llança una excepció IOError

-- afegeix localització i informacions a un IOError
annotateIOError :: IOError -> String -> Maybe Handle -> Maybe FilePath -> IOError

Vegeu errors I/O.[145]

Llançador generalitzat modifica
  • el paquet exceptions[146] generalitza el context de les excepcions. Permet llançar i caçar excepcions des de mònades diferents de l'IO. (aquelles que implementin MonadThrow i MonadCatch).
-- definit a "exceptions" Control.Monad.Catch

class Monad efecte => MonadThrow efecte where
 throwM :: Exception e => e -> efecte a

excepcions en la gestió de recursos modifica

bracket modifica

Enclou en tres paràmetres la vetlla d'excepcions en un càlcul amb un recurs, i els manipuladors per adquirir-lo i per alliberar-lo en cas d'excepció o en acabar.

  • el recurs obert a l'acció del primer paràmetre es passa als paràmetres següents.
 bracket :: IO recurs -> -- adquisició de recurs, ex.: (openFile nom_fitxer mode) :: IO Handle
 (recurs -> IO b) -> -- alliberament del recurs, per ex.: hclose :: Handle -> IO ()
 (recurs -> IO c) -> -- computació amb el recurs adquirit, ex.: (\handle -> do {...}) :: Handle -> IO c
 IO c -- retorna el resultat de la computació

Exemple:

-- A System.IO:
withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
withFile name mode = bracket (openFile name mode) hClose -- aplicació parcial de ''bracket'', caldrà aplicar-lo sobre la computació a vetllar
-- ús: 
withFile nom_fitxer ReadMode (\ descriptor_del_fitxer -> acció_que_pot_llançar_excepcions)
bracket i avaluació tardana modifica

Un error comú amb l'Ent./Sortida tardana és el següent:

A l'exemple següent hGetContents no s'avalua fins que no s'avaluï el withFile, però com que no entrega el contingut si no li ho demanen, bracket (withFile és aplicació parcial de bracket) tanca el recurs. El resultat és la sortida buida. El compilador no avisa de l'error !!

La versió correcta és ficar el consumidor dins l'acció vetllada pel bracket, és a dir, el bloc del withFile, consumint el contingut abans no es tanqui el recurs.

{-| fitxer prova.hs -}
import System.IO (withFile, IOMode(ReadMode), hGetContents)
import Data.Function ((&)) -- aplicació cap enrere (infixl 1)

-- imprimeix nombre de línies llegides
incorrecte = do
 contingut <- withFile "test.txt" ReadMode hGetContents
 let nombreDeLínies = contingut & lines & length
 print nombreDeLínies

correcte = do
 withFile "test.txt" ReadMode $ \hd -> do
 contingut <- hGetContents hd
 let nombreDeLínies = contingut & lines & length
 print nombreDeLínies
-- fi de fitxer

-- fitxer test.txt
abc
-- fi de fitxer

ghci
Prelude> :l prova -- carrega i compila el codi precedent
[1 of 1] Compiling Main (prova.hs, interpreted)
Ok, modules loaded: Main.
* Main> incorrecte
*** Exception: test.txt: hGetContents: illegal operation (delayed read on closed handle)
* Main> correcte
1

try - avaluació i anàlisi per casos de les expressions llançadores d'excepcions modifica

try avalua un efecte IO que pot llançar excepcions retornant èxit o fallada en un tipus Either permetent-ne l'anàlisi per casos. (Either tipError) implementa una mònada amb efecte fallada. (Left error) és l'element absorbent per l'esquerra de Either en (>>=). Això permet encadenar amb (>>=) diversos efectes que poden fallar obtenint l'error del primer que falla.

-- prova.hs
import Control.Exception (try, evaluate, ArithException)
import Data.Fixed (Uni, Deci, Centi, Milli, Micro, Nano, Pico) -- coma fixa de representació sencera
-- (sencer * resolució_del_tipus)

-- try :: Exception e => IO a -> IO (Either e a)

type TipDada = Micro -- coma fixa, proveu també TipDada = Double

main = do
 let x = 0.0001 :: TipDada
 y = 0 :: TipDada
 -- ''evaluate'' avalua com a efecte IO una expr. funcional que pot llançar excepcions 
 result <- try (evaluate (x/y)) :: IO (Either ArithException TipDada)
 case result of
 Right valor -> putStrLn $ "correcte: " ++ show valor
 Left excep -> putStrLn $ "excepció: " ++ show excep

Amb TipDada == Micro (Coma fixa) l'excepció salta

excepció: divide by zero

Amb TipDada == Double (Coma flotant IEEE 754), no hi ha excepció (Divisió entre zero), dona:

correcte: Infinity

try també es pot fer servir amb finally, però bracket és més adequat per la gestió de recursos.

try generalitzat modifica

Del paquet exceptions,[146] generalitzant el context de llançament i caça d'excepcions per a mònades diferents de l'IO (aquelles que implementin MonadCatch)

try :: (MonadCatch efecte, Exception ex) => efecte a -> efecte (Either ex a)
finally modifica

Assegura l'exec. de la segona acció malgrat excepcions en la primera. Si hi ha hagut excepció, és rellançada al final.[147]

onException
com finally però només executa el segon bloc en cas que hi hagi hagut excepció.
finally, onException :: IO a -> IO b -> IO a

-- es fa servir habitualment en posició ''infix''
acció `finally` (coses_a_fer_en_acabar_encara_que_peti_l'acció_vetllada)

-- exemple de "onException" a ghci
$ ghci
Prelude>import Control.Exception
Prelude Control.Exception> throwIO (userError "òstia") `onException` putStrLn "abc"
abc 
*** Exception: user error (òstia)

catch -- per caçar excepcions (especialment les asíncrones) modifica

La clàusula catch té dos paràmetres, l'operació vetllada i el manipulador d'excepcions Les excepcions del Haskell98 (tipus algebraic Exception) a GHC van passar al mòdul Control.OldException però ha sigut retirat a la versió 7.6.1[148]

-- excepcions del Haskell98, basades en un tipus Exception, unió discriminada dels diferents tipus d'excepcions 
{-# LANGUAGE CPP #-} -- codi de PreProcessador de llenguatge 'C', els comentaris s'hi delimiten per /* */

#ifdef __GLASGOW_HASKELL__
 #if __GLASGOW_HASKELL__ >= 706 /* GHC >= 7.6 */
 #error "les excepcions del H98 ja no estan suportades, el mòdul Control.OldException ja no hi és"
 #elif __GLASGOW_HASKELL__ >= 610 /* GHC >= 6.10 */
 import Control.OldException 
 #else /* cas de GHC < 6.10 */
 import Control.Exception
 #endif
#endif

 catch
 (evaluate (x/y) >>= print) -- ''evaluate'' força l'avaluació en el context IO d'una expressió que llança excepcions 
 (\excep -> case excep of
 ArithException ae | ae == DivideByZero -> print "divisió per zero"
 | otherwise -> print $ "excepcio aritmètica" ++ show excep
 _ -> print $ "excepció per " ++ show excep
)

Per a les excepcions noves de GHC (qualsevol tipus que implementi la classe Exception), són al mòdul Control.Exception[149]

  • Gestors d'excepció segons el tipus. Requereix l'extensió de llenguatge ScopedTypeVariables.
  • el model d'aplicació successiva de catchs per tractar els diferents tipus d'excepcions té una pega: les excepcions llançades dins dels handlers poden ser caçades pels catchs subseqüents
  • la funció catches posposa el tractament de les excepcions produïdes dins els handlers.
-- excepcions del Haskell2010, basades en un tipus existencial SomeException amb components que implementen la classe Exception

{-# LANGUAGE DeriveDataTypeable #-} -- per poder derivar Typeable
{-# LANGUAGE ScopedTypeVariables #-}

import Prelude hiding (catch) -- el catch del prelude està desaconsellat per què no caça les excep. del codi funcional.
{- Del codi del Prelude:
-- Non-I\/O exceptions are not caught by this variant; to catch all
-- exceptions, use 'Control.Exception.catch' from "Control.Exception".
-}
import Control.Exception
import Data.Typeable

type ExcepcióDeLAplicació = Excepcio1 | Excepcio2 String 
 deriving (Show, Typeable) 

instance Exception ExcepcióDeLAplicació -- Exception requereix instàncies de Show i Typeable

-- si es tracta de caçar una excepció en una expr. funcional pura, cal forçar-ne l'avaluació com a efecte amb ''evaluate''.

-- alternativa1: les excepcions produïdes dins un handler poden ser caçades pel handler següent
f = acció `catch` (\ (excep :: ArithException) -> tractaExcepció_Aritmètica excep)
 `catch` (\ (excep :: ExcepcióDeLAplicació) -> tractaExcepció_DeLAplicació excep)
 `catch` (\ (excep :: IOException) -> tractaExcepció_IO excep) -- si tractaExcepció_IO en llança una altra
 -- aquesta altra excepció serà caçada pel següent 'catch'.
 `catch` (\ (excep :: SomeException) -> tractaExcepció_Altres excep) -- 'catch' escombra, per al tipus més genèric 'SomeException'

-- alternativa2: 'catches' no captura les excepcions llançades dins els handlers.
f = acció `catches` [Handler (\ (excep :: ArithException) -> tractaExcepció_Aritmètica excep),
 Handler (\ (excep :: ExcepcióDeLAplicació) -> tractaExcepció_DeLAplicació excep)
 Handler (\ (excep :: IOException) -> tractaExcepció_IO excep),
 Handler (\ (excep :: SomeException) -> tractaExcepció_Altres excep)
 ]

Vegeu catches.[150]

Vegeu exemple Compilador Haskell de Glasgow#Excepcions de tipus definits per l'usuari.

catch generalitzat modifica
  • el paquet exceptions[146] generalitza el context de les excepcions. Permet llançar i caçar excepcions des de mònades diferents de l'IO (aquelles que implementin MonadCatch).
class MonadThrow efecte => MonadCatch efecte where
 catch :: Exception ex => efecte a -> (ex -> efecte a) -> efecte a

-- la implementació ha de complir la regla: catch (throwM ex) f ≡ f ex
  • el paquet safe-exceptions[140] permet separar el tractament de les excepcions síncrones i asíncrones (les originades des d'altres fils d'execució) millorant la seguretat del tractament.

La funció error assenyala la fallada del programa modifica

Per al cas d'estats inconsistents o bé operacions no definides per als valors dels paràmetres.

La crida a la rutina error dispara una excepció genèrica ErrorCall que mostra el missatge.

error missatge -- 'error' en funcions parcials no dona cap pista de la crida culpable.
 -- per la impressió de traces de crides, vegeu HasCallStack o bé compilació per l'ajustatge (profiling)

És preferible evitar-ne l'ús en funcions parcials, convertint-les en totals amb resultat opcional i analitzar-ne el resultat dins la rutina que ha fet la crida amb paràmetres il·legals.

  • des de GHC 7.8.1: errorWithStackTrace amb bolcat de pìla si s'ha compilat per l'ajustatge amb profiling.
No requereix l'opció d'execució de depuració +RTS -xc però sí ghc -prof -fprof-auto Main.hs;
L'ús de la versió d'ajustatge (profiling) del RunTimeSystem requereix disposar de versions compilades amb profiling de totes les dependències.[151]
import GHC.Stack (errorWithStackTrace) -- des de GHC 7.8.1, obsolet des de GHC 8.0 (funcionalitat integrada a 'error')

funcióParcial p1 p2 
 | precondició p1 p2 = resultat
 | otherwise = errorWithStackTrace "funció_tal: la precond. falla" -- bolca la pila de crides si s'ha compilat amb "-prof -fprof-auto"
 where
 resultat = ...

Això millora a partir de la versió 7.10.2 amb un nou mecanisme d'obtenció d'una pila de crides mitjançant paràmetres implícits especials inclosos al context de les crides que es pretén traçar, sense haver de recórrer al profiling.

A GHC 8.0 error incorpora la funcionalitat de errorWithStackTrace mostrant la situació de l'error. Excepte al paquet base, on les crides a error han estat reanomenades a errorWithoutStackTrace.

Fins ara era millor reconvertir les funcions parcials en funcions totals amb resultat opcional (Maybe) analitzant el resultat a la funció que fa la crida, i en cas de resultat inesperat provocar la petada,

  • o bé amb encaix incomplet obtenint la posició de la petada,[152]
  • o bé amb la crida err equivalent a error del paquet file-location[153] que ens permet mostrar la posició (requereix l'extensió TemplateHaskell). $(x) és un mecanisme de GHC per avaluar en temps de compilació, anomenat crida splice.
{-# LANGUAGE PackageImports, TemplateHaskell #-}

-- err' crida a ''error'' afegint la situació (fitxer:línia:columna) obtinguda en temps de compilació

-- la utilitzarem per assenyalar l'error "des de la funció culpable i no dins la funció parcial"
import "file-location" FileLocation (err') 

-- funció total headMaybe equivalent a la parcial ''head''
-- evitem la crida a error de la funció parcial i la traslladem a la funció culpable (la que fa la crida)

headMaybe :: [a] -> Maybe a
headMaybe (x : _) = Just x
headMaybe _ = Nothing

obtenirElCap :: [a] -> a
obtenirElCap llista =
 case headMaybe llista of
 Just cap -> cap
 Nothing -> $(err') "OH NO!" -- $(err') s'avalua en temps de compilació ($()), capturant la posició (fitxer:línia:columna)

main = print $ obtenirElCap ([] :: [Int])

Resultat:

$ ./prova
prova: main:Main prova.hs:14:21 OH NO!

La biblioteca Safe ofereix diverses alternatives de les funcions del Prelude que poden petar.

Ara per ara, per assegurar la finalització del programa, és millor evitar les funcions parcials que criden a error i compilar amb -Wall per assegurar l'exhaustivitat dels encaixos.

Cal tenir en compte que els accessors de registres amb més d'un constructor que no siguin comuns a tots ells, també són funcions parcials.

Assercions, Precondicions i Postcondicions modifica

assert avalua la condició, i si és certa retorna el segon argument i,si no, peta dient-nos on.[154]

L'ús de l'opció d'optimització (-O ó bé -On | n>0) elimina les assercions del codi objecte.

assert condició expr -- si Fals, dispara l'excepció AssertionFailed indicant nom_del_fitxer i línia

per exemple:

  • Amb resultat opcional per evitar petades per crides a 'error'
import Control.Exception (assert)

funcióParcial :: a -> b -> Maybe c
funcióParcial p1 p2 
 | precondició p1 p2 = assert (postcondició p1 p2 resultat) $ Just resultat
 | otherwise = Nothing -- "la precond. falla"
 where
 resultat = ...
  • Amb excepcions:
{-# LANGUAGE DeriveDataTypeable #-}
import Control.Exception (assert, throw, evaluate, try)
import Data.Typeable

data TElMeuError = ElMeuError String 
 deriving (Typeable, Show)

instance Exception TElMeuError -- implementa la classe Exception

funcióParcial :: a -> b -> c
funcióParcial p1 p2 
 | precondició p1 p2 = assert (postcondició p1 p2 resultat) resultat
 | otherwise = throw $ ElMeuError "funcióParcial: la precond. falla"
 where
 resultat = ...

main = do
 -- 'evaluate' força l'avaluació d'una computació funcional pura llançadora d'excepcions
 -- 'try' captura el resultat o l'excepció en un Either
 eitherResultat <- try (evaluate (funcióParcial p1 p2)) :: IO (Either TElMeuError String) 
 case eitherResultat of
 Right resultat -> putStrLn $ "Correcte: " ++ show resultat
 Left (ElMeuError msg) -> putStrLn $ "ElMeuError: " ++ msg
obtenció del punt de crida origen de la violació de la precondició modifica

1. Amb paràmetre implícit especial de tipus CallStack

Vegeu ref.[155] (Des de GHC 7.10.2)[156] La seva obtenció reflecteix la pila d'aquelles crides on explícitament aparegui l'implícit de tipus CallStack com a restricció de context, amb el mateix nom de variable.

Des de GHC 8.0 vindrà definit un sinònim de restricció HasCallStack que evitarà la necessitat de l'extensió de sintaxi ImplicitParams.[157]

type HasCallStack = (?callStack :: CallStack) :: Constraint -- especifica Constraint com a 'kind' (tipus del tipus)

Exemple:

{-# LANGUAGE CPP #-} -- a les directives CPP els comentaris segueixen la sintaxi del C: /* comentari */

#if MIN_VERSION_base(4,9,0) /* GHC >= 8.0 */

import Control.Exception (assert)
import GHC.Stack (HasCallStack)

funcióParcial :: HasCallStack => a -> b -> c
funcióParcial p1 p2 
 | precondició p1 p2 = {- cas definit -} assert (postcondició p1 p2 resultat) resultat
 | otherwise = {- cas no definit -} error $ 
 "funcióParcial: la precondició falla" -- a GHC 8.0 hi ha bolcat automàtic del CallStack "?callStack" amb l'error
 -- la funció 'error' antiga (anterior a GHC 8.0) ha estat reanomenada 'errorWithoutCallStack' al paquet 'base'
 where
 resultat = expressió p1 p2

#elif MIN_VERSION_base(4,8,1) /* GHC >= 7.10.2 && < 8.0 */

{-# LANGUAGE ImplicitParams #-}

import Control.Exception (assert)
import GHC.Stack (CallStack, showCallStack) 

funcióParcial :: (?loc :: CallStack) => a -> b -> c
funcióParcial p1 p2 
 | precondició p1 p2 = {- cas definit -} assert (postcondició p1 p2 resultat) resultat
 | otherwise = {- cas no definit -} error $ 
 "funcióParcial: la precondició falla; cridat des de : \n" ++ showCallStack ?loc
 where
 resultat = expressió p1 p2
#endif

2. Alternativa. Bolcar la pila de crides simulada, havent compilat amb la variant d'ajustatge del RunTimeSystem. (profiling)

La comprovació de la precondició dins la rutina no ens informarà de la rutina origen del problema, excepte si habilitem la simulació de pila de crides de l'ajustatge (compilant amb -prof i executant amb +RTS -xc). Vegeu Depuració. El problema de la manca d'informació de situació en les petades

No finalització - Símbol ⊥ (Bottom) modifica

Valor intern que atura el programa prematurament indicant "No finalització del programa", especialment com a opció de fons en un case, en cas que no encaixi cap de les opcions o bé un bucle sens fi. El tipus de ⊥ és arbitrari.[158]

Prelude> :type error "abc"
error "abc" :: t -- el tipus de "no finalització" és arbitrari.

En anglès bottom com a verb vol dir "tocar fons". En català es podria anomenar "fondeig" (fondejar: immobilitzar una barca, ancorant-la al fons).

Implementació pendent - Undefined modifica

undefined[159] és un valor de fondeig (⊥: atura el programa) per poder explicitar el tipus d'una funció pendent d'implementar i poder així passar la compilació. Crida a la funció error.

-- ''undefined'' en una definició, explicita implementació pendent
 <identificador> <patrons> = undefined :: <tipus>

Canvis d'estat - Variables modifica

Haskell permet crear objectes mudables, que poden canviar d'estat mitjançant operacions sempre dins d'una mònada (sinó l'ordre, i per tant el resultat, no estarien garantits).

La mònada IO caracteritza l'estat global de l'aplicació amb objectes amb cicle de vida no acotat. Les variables mudables globals són les IORef's.

La mònada ST caracteritza l'estat local d'una computació puntual, permetent encapsular actualitzacions in situ (destructives) dins de codi funcional pur. Les variables mudables locals són les STRef's i el seu cicle de vida queda limitat a l'àmbit de l'efecte ST.

Caldrà distingir el tipus de la mònada especificant el tipus resultant de l'expressió o bloc do.

La sincronització d'accés a variables des de diferents fils d'execució (detallat a Haskell concurrent) es pot obtenir:

  • amb sincronització per baldes (ang:locks) amb variables MVar's (ang:mutable variable o més precisament mailbox variable).
  • amb les transaccions en memòria (similars a les de bases de dades). La mònada STM (inicials de Software Transactional Memory) modela el funcionament i validació de les transaccions. Les variables transaccionals són les TVar (immutables) i TMVar (mudables).
variables
tipus mònada generadors mòdul descripció
IORef a[160] IO newIORef x Data.IORef vars. globals no sincronitzades,
cicle de vida no acotat
STRef a[161] ST newSTRef x Data.STRef vars. per a canvis d'estat encapsulables,
cicle de vida lligat a l'àmbit de ST
MVar a[162] IO newMVar x
-- var buida per a un tipus T
newEmptyMVar:: IO (MVar T)
Control.Concurrent.MVar bústia de comunicació d'un sol element
emprada com a variable sincronitzada amb Monitor (concurrència)
també es pot fer servir com a semàfor binari.
TVar a[163] STM
IO
newTVar x -- encapsulable
newTVarIO x -- global
Control.Concurrent.STM.TVar posicions de memòria compartida
suporten transaccions de memòria atòmiques
TMVar a[164] STM
IO
newTMVar x -- encapsulable
newTMVarIO x -- global
Control.Concurrent.STM.TMVar MVar protegida per transaccions de memòria

Variables d'estat global no sincronitzades IORef modifica

Les referències IORef,[165][166] equivalents de les ref del ML Estàndard, permeten modificar l'estat global caracteritzat com a efecte IO.

No és convenient utilitzar una mateixa IORef en diferents fils d'execució. Protegir-ne més d'una mitjançant atomicModifyIORef està desaconsellat[167] (les MVar hi són més indicades).

 import Data.IORef (IORef, newIORef, readIORef, writeIORef, modifyIORef)
 import qualified Control.Monad as Monad
 import System.IO (stdout, hFlush)

 tornem_hi :: IORef Bool -> IO ()
 tornem_hi ref_estat = do

 x <- readIORef ref_estat -- llegeix variable ref_estat
 writeIORef ref_estat $ not x -- escriu

 -- modifyIORef ref_estat (not) -- alternativa: modifica la ref. amb la funció

 putStrLn $ 
 if x then "Blanc" else "Negre"

 putStr "Premeu intro:"
 hFlush stdout
 getLine -- espera tecleig Intro i ignora el resultat del getLine
 -- sense variable (v <- getline),
 -- el bloc do compon les línies amb (>>) en lloc de (>>=)
 return ()

 main = do
 ref_estat <- newIORef False -- crea variable del tipus del valor inicial
 -- i n'obté la referència
 Monad.forever $ tornem_hi ref_estat -- repeteix seqüencialment

Variables d'àmbit local STRef per l'encapsulament d'efectes col·laterals modifica

La mònada ST[168][169] (abbrev. de State) encapsula canvis d'estat dins de codi funcional pur i permet fer computacions amb actualitzacions in situ (destructives) mitjançant referències STRef[170] a objectes de cicle de vida restringit a l'àmbit i avaluació de l'efecte.

en memòria local modifica
  • Els efectes locals produïts en un fil d'execució (ST s tipusDelResultat), venen parametritzats amb una variable s (l'espai d'avaluació de l'efecte), que es deixa lliure per indicar l'espai propi del fil d'execució.
  • la funció runST avalua un efecte local i n'extreu el resultat.
 import Control.Monad.ST (ST, runST, stToIO)
 import Data.STRef (STRef, newSTRef, readSTRef, writeSTRef, modifySTRef)
 import Control.Monad as Monad

 -- fold_left: aplica operació binària ''f'' acumulant resultat
 -- sobre un valor inicial i els elements d'una llista d'esq. a dreta

 foldlST :: (a -> b -> a) -> a -> [b] -> ST s a
 foldlST f acc_ini xs = do
 ref_acc <- newSTRef acc_ini -- Crea una variable per a l'acumulador amb el valor inicial acc_ini

 Monad.forM_ xs $ \x -> do -- encadena l'aplicació a tots els x de la llista xs...

 acc <- readSTRef ref_acc -- llegeix l'acumulador
 writeSTRef ref_acc (f acc x) -- aplica f a l'acumulador i a x i en desa el resultat a la variable

 -- modifySTRef ref_acc $ (flip f) x -- alternativa, flip altera l'ordre dels paràmetres formals

 readSTRef ref_acc -- finalment llegeix l'acumulat que passa a ser
 -- el resultat de l'efecte del bloc ''do'' més extern

 main = do
 -- runST avalua foldlST en l'espai del fil actual
 let resultat = runST $ foldlST (+) 0 [1..4]
 putStrLn $ "total: " ++ show resultat
en memòria global modifica
  • stToIO avalua un efecte local de tipus (ST s tipusDelResultat) en l'espai de memòria global, assignant RealWorld a la variable s, oferint el resultat en la mònada IO.[171]
 -- implementació de foldlST igual que l'anterior

 main = do
 resultat <- stToIO $ foldlST (+) 0 [1..4] -- stToIO avalua l'efecte encapsulat ST en l'espai global RealWorld
 putStrLn $ "total: " ++ show resultat

Variables MVar per a l'accés sincronitzat modifica

Les variables MVar ofereixen l'accés sincronitzat entre fils d'execució amb el mecanisme de les bústies de comunicació. Ampliació a Haskell concurrent

Mutabilitat als vectors modifica

Vegeu vectors d'elements d'allotjament directe

Entrada/Sortida reactiva - Els Iterats (ang:Iteratees) modifica

L'abstracció dels Iterats aporta un altre enfocament, descomponent l'entrada/sortida en productors del corrent de dades, consumidors i transformadors.[172]

  • Els Iterats (consumidors) es descriuen com una abstracció componible per al procés incremental d'una seqüència de parts de l'entrada de dades, per l'obtenció d'un resultat. És un component reactiu. Es reacciona a l'entrada de cadascun dels elements (o bé troç de fitxer (ang:chunk)) com si fos un plegat (reducció), combinant l'element (o bé el troç de fitxer) en funció de l'estat, que s'actualitza en funció de l'entrada, i en rebre el "fi de seqüència" s'ofereix el resultat.
  • L'Enumerador (productor) és l'abstracció que genera el corrent de dades per al consum dels Iterats.
  • Els Enumerats (ang: Enumeratee) actuen com a consumidors i productors alhora, fent de transformadors interposats.

El procés es completa en associar un productor (l'Enumerador) amb un o més components reactius (l'Iterat amb possible interposició dels Enumerats)

Nota el sufix -ee
En anglès, donat un verb afegint-li el sufix -er tenim un nom subjecte d'una acció[173] com ara del verb employ el substantiu Employer.
El sufix -ee obté de l'acció un subjecte passiu (persona/objecte afectats o subordinats),[174] així el parell (Employer, Employee) es podria traduir per (Contractador, Contractat)
Així els termes tècnics {Iteratee, Enumeratee} en català serien {l'Iterat, l'Enumerat} com Refugee és {el refugiat}.

Algunes implementacions inclouen una capa de gestió de recursos (ex. el transformador de mònades ResourceT[175] als conduits)[176] que en ésser cridat (runResourceT), executa les accions incrementals i, en acabar, les accions d'alliberament registrades pel generador.

import Data.Conduit
import Data.Conduit.Binary as CB
import Data.Conduit.List as Cl
import Data.Conduit.Text as Ct

main :: IO ()
main = do
 let canonada = CB.sourceFile "test.txt" -- productor per trossets (''Enumerador'')
 $$ Ct.decode Ct.utf8 -- transformador reactiu (''Enumerat'')
 =$ Ct.lines -- transformador reactiu (''Enumerat'')
 =$ Cl.fold (\x _ -> x +1) (0::Int) -- consumidor reactiu dels trossets (''Iterat'')

 nombreDeLínies <- runResourceT $ canonada
 putStrLn $ show nombreDeLínies

Efecte fallada en una seqüència de computacions modifica

Els elements absorbents per l'esquerra de l'encadenament en una mònada, en produir un resultat absorbent assimilable a fallada, fan inútil l'avaluació de la computació subseqüent, perquè el resultat és el mateix valor, de manera que l'encadenament progressa només per als elements no absorbents en (>>=).

Per exemple:

  • a la mònada Maybe: l'encadenament de resultats opcionals de funcions definides parcialment (f x >>= g), s'atura a la primera fallada
  • a la mònada Either: l'encadenament d'avaluacions d'accions que disparen excepcions (try acció1 >>= \_ -> try acció2), s'atura al primer Left
-- a la mònada Maybe
Nothing >>= _ = Nothing -- l'encadenament no s'avalua en aquest cas
Just r >>= f = f r

-- a la mònada Either
Left err >>= _ = Left err -- l'encadenament no s'avalua en aquest cas
Right r >>= f = f r

Això facilita l'encadenament de computacions que poden fallar, sense haver de consultar la correcció del resultat a cada pas.

tipus de la mònada element absorbent en (>>=)
Maybe a Nothing
Either err a Left err
[a] [ ]
classe
MonadPlus m mzero

fallada monàdica en el codi funcional modifica

Com acabem d'esmentar, l'element absorbent en l'encadenament en la mònada Maybe (o bé Either) evita l'avaluació de les computacions posteriors.

{-| fitxer total.hs
-}
{-# LANGUAGE UnicodeSyntax #-}

headMay :: [a]  Maybe a
headMay (x : _) = return x -- resultat exitós, equival a (Just x)
headMay [] = Nothing -- element absorbent de la mònada Maybe: (Nothing >>= _ = Nothing)

doblar :: Num a => a  Maybe a
doblar x = return $ 2 * x

doblarElCap :: Num a => [a]  Maybe a
doblarElCap llista = headMay llista >>= doblar

prova:

$ ghci
Prelude> :load total.hs
[1 of 1] Compiling Main (total.hs, interpreted)
Ok, modules loaded: Main.
* Main> doblarElCap []
Nothing
* Main> doblarElCap [1,2,3]
Just 2
un tipus específic per al subdomini de casos definits modifica

I una funció de validació dels casos definits:

import Control.Category ((>>>)) -- composició esq-dreta -- f >>> g = g. f
import Data.Function ((&)) -- aplic. cap enrere -- (&) = flip ($) -- infixl 1 -- desde GHC 7.10.1

newtype TLlistaNoBuida a = LlistaNoBuida { obtenirLlista :: [a]} deriving (Eq, Ord, Show)

-- Constructor: LlistaNoBuida :: [a] -> TLlistaNoBuida a
-- Inversa del constructor: obtenirLlista :: TLlistaNoBuida a -> [a]

validaLlistaNoBuida :: [a] -> Maybe (TLlistaNoBuida a)
validaLlistaNoBuida xs @ (_ : _) = Just $ LlistaNoBuida xs
validaLlistaNoBuida [] = Nothing

head :: TLlistaNoBuida a -> a
head = obtenirLlista >>> Prelude.head 

-----------------------------------
-- operant en la mònada Maybe:
-- un resultat Nothing (element absorbent en (>>=)) fa que l'encadenament no progressi
--
obtenirLaLlistaNoBuidaMenor :: Ord a => [a] -> [a] -> Maybe (TLlistaNoBuida a)
obtenirLaLlistaNoBuidaMenor l1 l2 = do
 nb1 <- validaLlistaNoBuida l1
 nb2 <- validaLlistaNoBuida l2
 if nb1 < nb2 then return nb1 else return nb2

default (Int) -- desambiguació dels literals numèrics segons seqüència de tipus

main = do
 case obtenirLaLlistaNoBuidaMenor [1,2,3] [2,3,4] of
 Just llistaNoBuida -> llistaNoBuida & show & putStrLn
 Nothing -> putStrLn "alguna de les llistes era buida"

fallada monàdica en els efectes col·laterals - el transformadors EitherT i MaybeT modifica

La fallada en la mònada IO per encaixos incomplets del segon operand de (>>=) (op. fail desplaçada (des de GHC 8.0) a la classe MonadFail)[177] es produeix disparant una excepció IOError.[178]

En el cas de la mònada ST, el comportament no està definit. La implementació per defecte crida la rutina error aturant el programa.[179]

Per afegir-hi la funcionalitat de l'element absorbent en (>>=) cal aplicar-hi un transformador de mònades que la incorpori.

El transformador de mònades EitherT,[180] de manera similar al MaybeT, afegeix la possibilitat d'evitar càlculs posteriors a la fallada, a una mònada, afegint-hi a més a més, informació de l'error. Aquí l'aplicarem a la mònada IO, resultant la mònada (EitherT tipError IO).

{-# LANGUAGE PackageImports, UnicodeSyntax #-}
import "either" Control.Monad.Trans.Either (EitherT (..))
import "transformers" Control.Monad.Trans.Class (lift)
import Control.Category ((>>>)) -- g >>> f == f. g

-- el tipus EitherT, definit a Control.Monad.Trans.Either
-- newtype EitherT err mònada a = EitherT { runEitherT :: mònada (Either err a) }

-- els valors (Left err) són elements absorbents de la mònada Either en (>>=)

-- errors de l'aplicació
data Err = ErrLectura | ErrForaMarges Int deriving (Eq)

instance Show Err where
 show ErrLectura = "error de lectura"
 show (ErrForaMarges x) = "fora marges: " ++ show x

-- funció total -- en comptes d'excepció retorna fallada amb l'error
llegirSencer :: String  Either Err Int
llegirSencer s = case reads s of
 [(x, "")]  return x
 _  Left ErrLectura -- element absorbent

llegeixSencerDEntrada :: EitherT Err IO Int
llegeixSencerDEntrada = do
 s <- lift getLine
 EitherT $ return $ llegirSencer s

-- apliquem encadenaments sobre el resultat de la lectura, segurs que no petarà.

comprovaMarges :: Int  EitherT Err IO Int
comprovaMarges x = if x >= 0
 then return x
 else EitherT $ return $ Left $ ErrForaMarges x -- element absorbent

obtenirLArrelQuadrada :: Int  EitherT err IO Float
obtenirLArrelQuadrada = fromIntegral >>> sqrt >>> return

main = do
 -- en l'encadenament (>>=) el primer resultat Left (element absorbent en (>>=)) fa que l'avaluació no progressi.
 eiResult <- runEitherT $ llegeixSencerDEntrada >>= comprovaMarges >>= obtenirLArrelQuadrada
 case eiResult of
 Right v  putStrLn $ "resultat: " ++ show v
 Left err  putStrLn $ "error: " ++ show err -- mostra el primer error que es produeixi
cabal install either
runhaskell prova.hs

Lents (Referències funcionals) - Consulta i manipulació de parts d'estructures complexes modifica

Una lent és un mecanisme componible per enfocar una part d'una estructura per la consulta, o bé per la modificació de l'estructura alterant-ne el component enfocat.[181][182][183][184]

També s'anomenen referències funcionals, per ésser l'equivalent funcional dels punters als components de l'Orientació a objectes.

Classes de lents:

  1. lents pròpiament dites: permeten adreçar components d'un tipus producte, per exemple un tipus de registre
  2. prismes: permeten adreçar components d'un tipus suma (unió discriminada), on la selecció del component pot fallar si el valor no correspon a la variant pretesa.
  3. isos: permeten adreçar components sobre una transformació reversible, per exemple d'una tupla2 (a, b) transformada en (b, a)
  4. travesses i plegats: permeten adreçar múltiples components per una transformació dels mateixos o bé una reducció dels valors mitjançant un plegament.
  • Història de les lents.[185]
  • Lents de Van Laarhoven.[186] Va ser el creador de la base teòrica de les lents actual emprada per la biblioteca "lens" de Edward Kmett.[184]
  • Lents basades en Profunctors. Recentment s'ha comprovat que els profunctors descrits més amunt ofereixen una manera més planera de definir-hi les lents.[187][188][189]

Exemple amb biblioteca "lens" (lents de Van Laarhoven) a Compilador Haskell de Glasgow#Lents de Van Laarhoven -- Consulta i manipulació de parts d'estructures complexes

Lents basades en profunctors modifica

  • la biblioteca Mezzolens defineix les lents com una funció de transformadors: una transformació (profunctor) d'estructures en funció de la transformació d'un component.

Per fabricar una lent caldrà una funció de selecció del component enfocat i una funció de transformació de l'estructura en funció de la imatge (transformada) del component enfocat.

Són instàncies de Profunctor les funcions i les fletxes de Kleisli.

instance Profunctor (->) 
instance (Functor f) => Profunctor (Kleisli f)

Per exemple podem aplicar una lent a un profunctor funció que transformi el component, obtenint un profunctor del mateix tipus (funció) que transforma l'estructura que el conté.

La composició de lents (composició de funcions de profunctors) ens permetrà manipular components inserits profundament.

-- Sigui 'a' el tipus del component objectiu de l'estructura d'entrada de tipus 'ta'
-- i 'b' el tipus del component transformat, pertanyent a l'estructura de sortida de tipus 'tb'

-- type Lens ta tb a b = forall p. Strong p => p a b -> p ta tb

Strong és una especialitat de Profunctor. Els seus mètodes poden passar com a lents que proporcionen una transformació de l'estructura Parell mitjançant la transformació d'un component amb el profunctor d'entrada. (vegeu Mezzolens.Profunctor)

class Profunctor p => Strong p where
 _1 :: p a b -> p (a, c) (b, c) -- lent sobre el primer component d'un parell (tupla2)
 _2 :: p a b -> p (c, a) (c, b) -- lent sobre el segon component

així, aplicant les lents precedents a una funció que transformi el component obtenim una funció sobre el parell que el conté:

Prelude> import Mezzolens.Profunctor as MP

Prelude MP>:t _1 (f :: a -> b) 
:: (a, c) -> (b, c)

Prelude MP>:t _2 (f :: a -> b) 
:: (c, a) -> (c, b)

Generació de lents (amb "lens getter setter" de Mezzolens.Unchecked)

  • el getter selecciona de l'estructura el component que interessa (ta -> a).
  • el setter proporciona la transformació de l'estructura continent en funció de la imatge del component enfocat (b -> ta -> tb).
  • el resultat és una lent que ofereix una transformació de les estructures ta a tb, en funció d'una transformació del component de a a b (p a b -> p ta tb).
-- lens :: (ta -> a) -> (b -> ta -> tb) -> Lens ta tb a b

Treballarem amb lents simples, que no modifiquen el tipus

-- type Lens' ta a = Lens ta ta a a -- lents simples

-- Les lents es poden compondre 
-- (.) :: Lens' a b -> Lens' b c -> Lens' a c

Exemple:

{-| fitxer prova-mezzo.hs -}
{-# LANGUAGE PackageImports #-}

import "mezzolens" Mezzolens (Lens', get, (^.), set)
import "mezzolens" Mezzolens.Unchecked (lens)
import Data.Function ((&)) -- (&): aplic. cap enrere

-- seguim la convenció del paquet "lens" de nomenar els camps amb prefix '_' i les lents corresponents sense el prefix

data Arc = Arc {_graus, _minuts, _segons :: Int} deriving (Show)
data Situació = Situació {_latitud, _longitud :: Arc} deriving (Show)

-- estructura a manipular
sitBcn = Situació (arcDeGrausDec 41.399423) (arcDeGrausDec 2.128037)

-- decimal a sexagesimal
arcDeGrausDec :: Double -> Arc
arcDeGrausDec v = Arc partSencera mins secs
 where
 -- fracció pròpia, retorna fracció negativa cas de valors negatius de Lat/Lon 
 (partSencera, partFracció) = properFraction v 
 (mins, secs) = truncate (partFracció * 3600) `quotRem` 60 -- parteix cap a zero amb `quotRem`

-- generació de lents (amb "lens getter setter" de Mezzolens.Unchecked)
-- lens :: (ta -> a) -> (b -> ta -> tb) -> Lens ta tb a b

graus, minuts, segons :: Lens' Arc Int
graus = lens _graus (\v arc -> arc {_graus = v})
minuts = lens _minuts (\v arc -> arc {_minuts = v})
segons = lens _segons (\v arc -> arc {_segons = v})

latitud :: Lens' Situació Arc
latitud = lens _latitud (\v sit -> sit {_latitud = v})

-- lents compostes
lentGrausDeLatitud, lentMinutsDeLatitud, lentSegonsDeLatitud :: Lens' Situació Int
lentGrausDeLatitud = latitud. graus
lentMinutsDeLatitud = latitud. minuts
lentSegonsDeLatitud = latitud. segons

-- llegeix (get) el component enfocat per la lent
grausLat = sitBcn & get lentGrausDeLatitud

-- (^.) és una versió infix de get
-- grausLat = sitBcn ^. lentGrausDeLatitud

-- actualitza el component enfocat amb una funció:
-- (+2) és una funció (les funcions són instància de ''Profunctor'')
-- apliquem la lent sobre el valor de Profunctor (+2) sobre el domini del component i obtindrem un Profunctor del mateix tipus (una funció) que aplicarem a l'estructura continent sitBcn.

sitDosGrausMésAlNordDeBcn = sitBcn & lentGrausDeLatitud (+2)

-- estableix (set) el valor del component enfocat
sitBcnAmbGrausLat45 = sitBcn & set lentGrausDeLatitud 45 -- set lent v = lent (const v)

-- fi de fitxer

# amb GHCi v. 7.10.3
ghci 
Prelude> :l prova-mezzo.hs -- carrega el codi precedent
[1 of 1] Compiling Main (prova-mezzo.hs, interpreted)
Ok, modules loaded: Main.

* Main> :t lentGrausDeLatitud
lentGrausDeLatitud
 :: Mezzolens.Profunctor.Strong p =>
 Mezzolens.Optics.Optical p Situació Situació Int Int

* Main> import Mezzolens as M

* Main M> :t M.get lentGrausDeLatitud
M.get lentGrausDeLatitud :: Situació -> Int

* Main M> :t lentGrausDeLatitud (+2)
lentGrausDeLatitud (+2) :: Situació -> Situació

Prisma modifica

Un prisma és una lent per enfocar un component d'un tipus suma (pluri-constructor), que pot no ser present en un valor i per tant fallar.

-- Sigui 'a' el tipus del component objectiu de l'estructura d'entrada de tipus 'ta'
-- i 'b' el tipus del component transformat, pertanyent a l'estructura de sortida de tipus 'tb'

La funció de selecció, primer paràmetre del generador prism, ofereix en un tipus Either el component enfocat o bé, cas que la variant del tipus suma no sigui la desitjada, un valor del tipus de l'estructura transformada resultant que signifiqui la fallada (ta -> Either tb a).

El segon paràmetre del generador prism construeix l'estructura resultant partint de la imatge del component enfocat (b -> tb).

Exemple amb el cap en una llista.

-- de Mezzolens.Optics substituint l'àlies Optical
type Prism ta tb a b = forall p. Choice p => p a b -> p ta tb 

-- Choice és una especialitat de Profunctor amb lents que permeten transformar un tipus Either en funció de la transformació d'un component.
class Profunctor p => Choice p where
 _Left :: p a b -> p (Either a c) (Either b c) -- lent sobre el component de la variant Left
 _Right :: p a b -> p (Either c a) (Either c b) -- lent sobre el component de la variant Right

-- de Mezzolens.Unchecked
prism :: (ta -> Either tb a) -> (b -> tb) -> Prism ta tb a b
-- ^ prism match build
-- match proporciona el component amb Right o bé l'estructura resultant buida amb Left 
-- build forma una variant de l'estructura resultant multi-constructor, partint de la imatge del component

* Main> import Mezzolens as M
* Main M> import Mezzolens.Unchecked as MU
* Main M MU> :set -XLambdaCase

-- prisma sobre el cap d'una llista
* Main M MU> :{ -- getter parcial oferint el resultat en un Either
 let headMatch :: [a] -> Either [a] a 
 headMatch = \case 
 (x : _) -> Right x -- resultat per a variant objectiu
 [] -> Left [] -- resultat per a variant no objectiu 

 headBuild = \x -> [x]

 let _Cap = MU.prism headMatch headBuild
 :}

-- Consulta amb el prisma
* Main M MU> [sitBcn] ^? (_Cap. lentGrausDeLatitud) -- (^?) ofereix el resultat del 'getter' parcial en un Maybe
Just 41

-- Actualització sobre estructura que conté la variant objectiu del prisma
* Main M MU> [sitBcn] & (_Cap. lentGrausDeLatitud) (+2)
[Situació {_latitud = Arc {_graus = 43, _minuts = 23, _segons = 57}, _longitud = Arc {_graus = 2, _minuts = 7, _segons = 40}}]

-- Actualització sobre estructura que no conté la variant objectiu 
* Main M MU> [] & (_Cap. lentGrausDeLatitud) (+2)
[]

Travesses i plegats amb lents modifica

La classe Wandering ofereix una lent per enfocar tots els elements d'un contenidor travessable.

class (Strong p, Choice p) => Wandering p where
 wander :: Traversable f => p a b -> p (f a) (f b)
  • el mètode wander del profunctor Wandering ofereix una lent per travessar un travessable. Caldrà compondre'l amb la lent sobre l'element.[190]
  • la funció Mezzolens.gets fa servir Data.Functor.Constant.Constant com a Applicative, que ignora les funcions de composició i retorna la composició Monoidal dels elements.[191]
  • toListOf obté una seqüència de resultats, a base d'elevar-los amb pure a un Applicative Monoide que cal concretar amb una restricció de tipus.[192]
  • sumOf obté el plegat sobre el Monoide del newtype Sum.[192]
* Main>import Mezzolens as M
* Main M>import Mezzolens.Profunctor as MP

-- travessa un travessable actualitzant-ne els elements
* Main M MP>[sitBcn, sitDosGrausMésAlNordDeBcn] & (MP.wander. lentGrausDeLatitud) (+3)
[Situació {_latitud = Arc {_graus = 44, _minuts = 23, _segons = 57}, _longitud = Arc {_graus = 2, _minuts = 7, _segons = 40}},Situació {_latitud = Arc {_graus = 46, _minuts = 23, _segons = 57}, _longitud = Arc {_graus = 2, _minuts = 7, _segons = 40}}]

-- "toListOf" els elements del recorregut són elevats amb "pure" a un Applicative que cal concretar amb una restricció de tipus.
* Main M MP>[sitBcn, sitDosGrausMésAlNordDeBcn] & M.toListOf (MP.wander. lentGrausDeLatitud) :: [Int]
[41,43]

-- (^..) és un sinònim infix de 'toListOf'
* Main M MP>[sitBcn, sitDosGrausMésAlNordDeBcn] ^.. (MP.wander. lentGrausDeLatitud) :: [Int]
[41,43]

-- "sumOf": plegat sobre el Monoide del "newtype Sum"
* Main M MP>[sitBcn, sitDosGrausMésAlNordDeBcn] & M.sumOf (MP.wander. lentGrausDeLatitud)
84

Mòduls modifica

Per compilar no cal especificar tots els mòduls a la comanda sinó només el fitxer del mòdul que conté la clàusula main.

ghc --make Main.hs
# o bé
ghc --make -i src Main.hs # cas que els fonts siguin a la carpeta 'src'

Els mòduls a importar s'interpreten com a noms de fitxer sense l'extensió (l'extensió del fitxer pot indicar un tipus de document del qual s'obtindrà el codi Haskell mitjançant un preprocessador),[193] els compostos per parts separades per un punt com a camí CarpetaA.CarpetaB.NomDelFitxer.

  • (.hs): mòdul en codi Haskell
  • (.lhs): mòdul de Haskell literari.[194]
  • (.x): mòdul d'especificació de lèxic segons preprocessador Alex.[195]
  • (.y): mòdul d'especificació de gramàtica segons preprocessador Happy.[196]

encapsulament modifica

No hi ha doble especificació (interfície / implementació) per als mòduls. S'exporten els identificadors llistats a la clàusula module, o tots si no hi ha llista.

module A(
 func1, Tipus2, Classe3,
 pattern SinònimDePatró, -- amb l'extensió de llenguatge 'PatternSynonyms'
 module MòdulP, {- reexporta tot el que s'importa d'aquest mòdul -}
 module MòdulX, {- cas d'un àlies, reexporta tot el que s'importa dels mòduls reanomenats -}
 module A {- si el nom del mòdul coincideix amb l'actual, exporta totes les declaracions locals -}
) where

import MòdulP (A,B,C) -- detalla elements a importar que re-exportarem citant el mòdul

-- el mateix àlies permet reexportar el contingut de diversos mòduls de cop
import MòdulQ as MòdulX
import MòdulR as MòdulX
import MòdulS as MòdulX
...

distingir entre tipus i valors a l'export./import. quan hi ha coincidències modifica

Per exportar operadors constructors de tipus i no confondre'ls amb un operador funció del mateix nom, cal prefixar-lo amb el prefix type que explicita l'espai de noms al qual pertany l'identificador que volem exportar o bé importar. Cal esmentar l'extensió de llenguatge ExplicitNamespaces o altres que la impliquin.[197]

{-# LANGUAGE ExplicitNamespaces #-}

module N(-- 'type' a l'exportació explicita l'espai de noms de l'identificador que segueix
 f, 
 type (++) -- amb 'type' exporta el constructor de tipus (++) definit tot seguit
 -- evitant la confusió amb la funció (++) de les llistes
) where 
 data family a ++ b = L a | R b

-- per reexportar-lo
module M(f, type (++)) where ...
 import N(f, type (++))

accés públic als membres de classes i tipus modifica

Per

  • exportar els identificadors interns d'un tipus: constructors; noms de camp en registres.
  • i facilitar l'accés públic als mètodes d'una classe,

cal esmentar-los entre parèntesis a l'exportació, o bé adjuntar l'el·lipsi (..) per indicar tots. Altrament el tipus o la classe seran opacs.

module A(
 func1,

 Tipus2(..), -- tipus transparent, accés public als constructors
 Tipus3, -- tipus opac, modificable només per les operacions del tipus
 Tipus4(ConstructorPúblic_A, accessorPúblic_B), -- només publica els esmentats

 Classe4(..), -- classe amb accés públic a tots els mètodes
 UnaAltraClasse(mètodePúblic_A, mètodePúblic_B), -- els mètodes no publicats només son accessibles en el mòdul

) where
...

Importació modifica

 import MòdulA -- importa-ho tot
 import MòdulA (id1, id2, id3) -- enumeració: importa només els esmentats
 import MòdulA hiding (id1, id2) -- ''hiding'': tots excepte els esmentats
 -- as B: reanomenament del mòdul
 import qualified MòdulB as B -- ''qualified'': qualificació obligatòria: B.ident
 import MòdulB as B [hiding (...] -- sense ''qualified'': qualificació opcional en cas de col·lisió de noms
 import MòdulA as B (id1, id2) -- importa els esmentats amb qualificació opcional reanomenada
Importació d'instàncies (implementacions de classes) modifica
 import MòdulA () -- llista buida, per importar només les instàncies
  • les instàncies visibles d'un mòdul s'exporten sempre; qualsevol importació del mòdul les importa totes.[198]
Importacions especificant el paquet desitjat modifica

Cal esmentar la pragma d'ampliació de sintaxi {-# LANGUAGE PackageImports #-}

import "nom_del_paquet" Nom_del_mòdul
-- com ara:
import "network" Network.Socket
import qualified "network" Network.Socket as NS
Evitar la importació automàtica de les funcions predefinides modifica

Per evitar-ne la importació de només algunes:

import Prelude hiding (head, tail, init, last) -- amaga funcions parcials del 'Prelude' que poden petar (n'hi ha una colla)

Amb la pragma {-# LANGUAGE NoImplicitPrelude #-} amaguem tot el Prelude, i podem substituir-lo per un altre mòdul:

-- Per a l'exemple que substitueix l'entrada/sortida de tipus String per la de tipus Text
-- perquè els caràcters no anglosaxons ([[ASCII]]), el tipus String els mostra amb codis numèrics.
module ElMeuPrelude (
 putStr, putStrLn, getLine
 module PreludeMancat, -- exporta la resta del Prelude de 'PreludeMancat'
) where

import Prelude as PreludeMancat hiding (putStr, putStrLn, getLine) -- amaga la E/S amb operands String
import Data.Text.IO (putStr, putStrLn, getLine) -- importa la E/S per al tipus Text

ús:

-- no importis el Prelude que ja tinc el meu.
{-# LANGUAGE NoImplicitPrelude #-}
module ... where
import ElMeuPrelude
...

espai de noms de mòdul jeràrquic modifica

Els detalls de tractament intern que no formaran part de l'API se solen encapsular en mòduls allotjats en carpetes internes. Per accedir-hi, un nom del mòdul separat per punts ("A.B.NomDelFitxer") reflecteix el camí d'accés (A/B/NomDelFitxer), excepte l'extensió del fitxer, que indica si el font és de codi (.hs) o és un document al qual cal aplicar un preprocessador (de lèxic (.x), de gramàtica (.y), de programació literària (incrustada dins la documentació) (.lhs), o altres) per obtenir-ne el codi font.

En compilar, el senyal -i<dirs> indica a GHC la llista de directoris base de la recerca dels fonts[199]

És corrent reanomenar els noms jeràrquics a un sobrenom curt

 import qualified Control.Monad.ST.Strict as ST

mòduls amb referències mútues (mútuament recursius) modifica

Vegeu-ho a Compilador Haskell de Glasgow#Mòduls amb referències mútues (mútuament recursius)

arrencada modifica

El mòdul principal ha de dur per nom de mòdul "Main" i ha d'exportar la funció "main". Si no hi ha clàusula module es pren per defecte "module Main where", exportant tots els identificadors (útil per a proves a l'intèrpret).

Els arguments de la línia d'ordres de consola (getArgs), no inclouen el nom del programa, que s'obté amb getProgName.

Per indicar l'eixida amb codi de finalització: exitWith (ExitSuccess | ExitFailure codi_de_finalització) dispara una excepció de fi de programa.

Exemple senzill. (System.Environment no té en compte la codif. local, vegeu comentari):

-- per una lectura correcta (dels caràcters no anglosaxons) de la consola de comandes a Linux
-- cal esmentar "System.Environment.UTF8" del paquet utf8-string
--, en lloc de "System.Environment"

import System.Environment (getProgName, getArgs) -- a Linux System.Environment.UTF8
import System.Exit (exitSuccess, exitWith, ExitCode(..))
import Text.Printf (printf)

main = do
 args <- getArgs
 nomProg <- getProgName
 case args of
 [] -> do
 printf "%s: cal especificar un paràmetre.\n" nomProg
 exitWith (ExitFailure 1) -- dispara excep. de fi de programa especificant el codi de finalització.

 (arg:_) -> do
 printf "el primer param. és: %s\n" arg
 exitSuccess -- equival a: exitWith ExitSuccess -- dispara excep. de fi de programa amb codi 0

En el llançament, a més dels paràmetres del programa, s'hi poden afegir paràmetres per al Run Time System a partir del separador +RTS tancant opcionalment amb -RTS[200] en cas d'haver d'afegir, tot seguit, paràmetres pel programa

arrencada amb opcions modifica

Vegeu-ho a GHC.

funcions i tipus predefinits i biblioteques bàsiques modifica

El mòdul de funcions predefinides es coneix com a "Prelude". Vegeu Prelude estàndard.[201][202] Prelude del GHC.[203]

Les API's de les biblioteques bàsiques són aquí[204]

Podeu consultar a quins mòduls pot pertànyer un identificador mitjançant els cercadors de l'API del Haskell [https://web.archive.org/web/20100322194454/http://holumbus.fh-wedel.de/hayoo/hayoo.html Arxivat 2010-03-22 a Wayback Machine. Hayoo] o bé Hoogle

Precisió dels tipus bàsics modifica

Vegeu-ho a GHC#Precisió dels tipus bàsics

Vectors modifica

Vectors per interfície modifica

Vectors immutables
interfície (classe) IArray, parametritzats pels tipus de l'índex i de l'element.[205] Vegeu #Vectors immutables del H98
Vectors mudables
interfície (classe) MArray, parametritzats pels tipus de l'índex i de l'element.[206] Exemple a #Vectors mudables de dades no encapsulades
Vectors d'alt rendiment
al paquet vector,[207] vectors d'índex enter amb base 0, immutables (tipus Vector) i mudables (tipus MVector), amb versions per a elements allotjats indirectament (allotjament encapsulat) o bé amb allotjament directe (qualificats Unboxed). Paquets relacionats: vector-algorithms (algorismes eficients en espai amb actualitzacions in-situ per a vectors mudables), vector-instances, vector-binary-instances (instàncies de serialització), vector-read-instances, ...
Vectors pluridimensionals (amb paral·lelisme intern)
a GHC amb el paquet REPA (Regular Parallel Arrays)
Vectors de tractament paral·lel (paral·lelisme de dades DPH)
interfície PArray.[208] (cada op. conclou havent avaluat tots els elements) Només a GHC.[209]
Vectors amb allotjament contigu, per a ésser accedits des de mòduls forans (per ex. lleng. C)
interfície Storable.[210]

Immutables, per implementació i allotjament dels elements modifica

Els elements dels vectors poden estar:

  • allotjats indirectament (encapsulats en memòria dinàmica, ang:boxed): representats per un punter (l'avaluació serà tardana)
  • allotjats directament (no encapsulats, ang:unboxed): representats per un valor, evidentment avaluats abans d'ésser-hi assignats (avaluació estricta, el nom del tipus es diferencia incorporant la lletra 'U' de Unboxed com a xxUArray).
amb actualització de còpia completa modifica

Ineficients en espai. Vegeu Array[211] UArray[212]

 data Array idx elem -- Elements allotjats indirectament (encapsulats en memòria dinàmica, avaluació tardana)
 data UArray idx elem -- Amb U de ''Unboxed'': elements no encapsulats, allotjats directament, avaluació estricta.
-- exemple
import Data.Array.Unboxed (UArray) -- UArray: tipus amb elements 'unboxed' (no encapsulats)
import Data.Array.IArray (listArray) -- IArray: interfície immutable

vec :: UArray Int Float -- vector de Floats amb índex de tipus Int
vec = listArray (0,2) [0.5, 1.5, 2.5]

-- (//) actualització amb una llista de parells (índex, nou_valor)
nouVec = vec // [(indx, nou_valor)] -- còpia completa
amb actualització per aplicació de diferències. DiffArrays modifica
  • Vegeu DiffArrays.[213] s'afavoreix l'accés en temps constant a la versió més recent; els valors de referències antigues s'obtenen per aplicació de les diferències i per tant l'accés és més lent com més antiga sigui la variable, és a dir, més actualitzacions funcionals hagi sofert.[214]
import Data.Array.Diff (DiffUArray) -- Amb U de Unboxed: elements no encapsulats, allotjats directament, avaluació estricta
import Data.Array.IArray (listArray)

vec :: DiffUArray Int Float
vec = listArray (0,2) [0.5, 1.5, 2.5]

-- (//) actualització; l'accés al valor de ''nouVec'' serà més ràpid que al del seu antecessor ''vec''
nouVec = vec // [(indx, nou_valor)]

Mudables, per implementació i allotjament modifica

  • Vectors d'accés aleatori com a efecte global IO (objectes amb cicle de vida no acotat), en memòria global.[215]
 data IOArray idx elm -- amb allotjament indirecte dels elements (encapsulats en memòria dinàmica, avaluació tardana).
 data IOUArray idx elm -- amb U de Unboxed: elements no encapsulats, allotjats directament, avaluació estricta.
  • Vectors d'accés aleatori com a efecte encapsulable ST (objectes amb vida lligada a l'àmbit de l'efecte), en memòria local (o bé global cas d'avaluació de l'efecte ST amb stToIO).[216]
 data STArray s idx elm -- amb allotjament indirecte dels elements (encapsulats en memòria dinàmica).
 data STUArray s idx elm -- amb U de Unboxed: elements no encapsulats, allotjats directament, avaluació estricta.

(El paràm. 's' correspon al param. de l'espai de l'efecte local (ST s tipResultat))

Exemples a #Vectors mudables de dades no encapsulades.

Vectors immutables del H98 modifica

La classe Array combinada amb la Ix (índexs), especificades en el Haskell98, implementen vectors immutables amb elements d'avaluació tardana.[217][26]

 -- vector d'una dimensió, s'inicialitza amb
 -- el rang i una llista exhaustiva de parells (índex, valor)
 import Data.Array -- Array al Haskell98

 vec = array (menor, major) [ (i, expr_valor) | i <-[ menor..major ]]
 element = vec!indx -- (!) valor per a l'índex indx

 -- (//) actualització amb una llista de parells (índex, nou_valor)
 nouVec = vec // [(indx, nou_valor)]

 -- vector de dues dimensions
 w = array ((menor_i1, menor_i2), (major_i1, major_i2)) [((i, j), expr_valor) |
 i <- [menor_i1..major_i1], j <- [menor_i2..major_i2]]
vector amb índex de tipus enumerat modifica

Caldrà que l'enumerat implementi la classe dels índexs Ix (A GHC: al mòdul Data.Ix)[218]

class Ord a => Ix a where
 range :: (a, a) -> [a] -- '(range (desDe, finsA))' obté la llista [desDe .. finsA]
 index :: (a, a) -> a -> Int -- '(índex (desDe, finsA) x)' obté la distància(desDe, x)
 inRange :: (a, a) -> a -> Bool -- '(inRange (desDe, finsA) x)' indica si (desDe <= x && x <= finsA)
 rangeSize :: (a, a) -> Int -- '(rangeSize (desDe, finsA))' obté la mida de [desDe .. finsA]

Exemple:

 import Data.Array -- Array al Haskell98 
 import Data.Ix -- Ix al Haskell98
 import Text.Printf
 import Data.Function ((&)) -- (&) = flip ($) -- infixl 1 -- desde GHC 7.10.1

 data DiaFeiner = Dl | Dm | Dc | Dj | Dv
 deriving (Show, Eq, Ord, Enum, Ix) -- el compilador instancia Ix

 encàrrecs :: Array DiaFeiner Int
 encàrrecs = array (Dl,Dv) [(Dl,5),(Dm,6),(Dc,3),(Dj,7),(Dv,8)] -- rang i parells (índex, valor)

 main = do
 printf "Per dimecres tinc %d encàrrecs\n" (encàrrecs!Dc)

 putStr "De Dilluns a Divendres: "
 range (Dl,Dv) & map (encàrrecs!) -- aplicació parcial de l'operació (!)
 & show
 & putStrLn

A part d'aquests vectors, que generen una còpia a cada actualització, n'hi ha d'altres de força més eficients.[219][220] Entre aquests vegeu més avall #Vectors mudables de dades no encapsulades.

Vectors mudables de dades no encapsulades modifica

Amb allotjament directe (no encapsulat) dels elements.

vect. mudables amb cicle de vida lligat a l'àmbit (efecte encapsulable ST) modifica
  • Per a vectors amb cicle de vida lligat a l'àmbit i tipus del vector: (STUArray s tipus_de_l'índex tipus_de_l'element). (La 'U' és per Unboxed: allotja el valor i no un punter)
el tipus de l'índex ha d'implementar la classe Ix igual que els vectors de la def. del 98.
import Control.Monad.ST
import Data.Array.ST

obtenirParell :: ST s (Int, Int) -- el param. 's' indica l'espai d'avaluació de l'efecte
obtenirParell = do

 -- indiquem el tipus de ''newArray '' per indicar el tipus del vector STUArray
 -- (si volguéssim vectors d'elements d'allotjament indirecte i avaluació tardana utilitzaríem STArray)
 -- i perquè els literals 1,10,37, són de tipus indeterminat (vegeu secció "literals" més amunt)
 -- i cal concretar-ne el tipus.

 arr <- newArray (1,10) 37 :: ST s (STUArray s Int Int) -- rang valorInicial i tipus
 a <- readArray arr 1
 writeArray arr 1 64
 b <- readArray arr 1
 return (a,b)

main = do
 let x = runST obtenirParell -- avalua l'efecte ST en l'espai del fil d'execució
 print x
vect. mudables amb cicle de vida no acotat (efecte superficial IO) modifica
  • En memòria global i tipus del vector: (IOUArray tipus_de_l'índex tipus_de_l'element)
import Data.Array.IO

obtenirParell :: IO (Int, Int)
obtenirParell = do

 -- indiquem el tipus de ''newArray '' per indicar el tipus del vector IOUArray
 -- (si volguéssim vectors d'elements d'allotjament indirecte i avaluació tardana utilitzaríem IOArray)

 arr <- newArray (1,10) 37 :: IO (IOUArray Int Int) -- rang valorInicial i tipus
 a <- readArray arr 1
 writeArray arr 1 64
 b <- readArray arr 1
 return (a,b)

main = obtenirParell >>= print

Genèrics modifica

L'equivalent dels mòduls genèrics (paramètrics en tipus) d'altres llenguatges, es pot fer de dues maneres:

  • l'antiga, afegint tots els paràmetres de tipus als paràmetres de la classe de tipus, especificant les dependències funcionals.
  • la moderna, els paràmetres de tipus independents s'afegeixen als de la classe i els dependents es defineixen com a tipus associats a la classe.

Genèrics amb dependències funcionals modifica

Afegint les variables de tipus com a paràmetres de les classes i definint-ne les relacions amb dependències funcionals.

A les classes multiparàmetre pot ser que una part dels paràmetres vingui determinada per altres (per exemple el tipus de la col·lecció determina el tipus de l'element).[221]

La sintaxi de les dependències funcionals és:

class NomClasse a b c | a b -> c where

indicant que a les instàncies el grup de valors (a, b) determina el de 'c' i n'ha d'esperar un sol valor per cada ocurrència del grup determinador. Els tipus determinats s'expressen en funció dels determinadors, i si són monomòrfics (sense variables) n'hi ha prou amb el comodí '_'.

-- exemple de classe multi-param. amb dependències funcionals
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-}
class (Eq elt) => Conjunt cjt elt | cjt -> elt where
 buit :: cjt
 afegeix :: elt -> cjt -> cjt

instance Eq a => Conjunt [a] a where
 buit = []
 afegeix x [] = [x]
 afegeix x (y : ys)
 | x == y = y : ys
 | otherwise = y : afegeix x ys

test = afegeix 1 $ (buit :: [Int])

main = print test

Genèrics amb tipus associats modifica

Els paràmetres de tipus dependents del tipus índex de la classe s'especifiquen en funció del mateix. Són abstractes a la definició de la signatura i concretables a les instàncies.

class Col·lecció t where
 type Element t -- tipus associat com a àlies, instanciable mitjançant una expressió de tipus
 data Opcions t -- tipus associat estructural, instanciable mitjançant constructors

Exemple:

-- exemple de classe amb tipus associats
{-# LANGUAGE TypeFamilies, FlexibleContexts #-}

class (Eq (Element t)) => Conjunt t where
 type Element t
 buit :: t
 afegeix :: Element t -> t -> t

instance Eq a => Conjunt [a] where
 type Element [a] = a
 buit = []
 afegeix x [] = [x]
 afegeix x (y : ys)
 | x == y = y : ys
 | otherwise = y : afegeix x ys

prova = afegeix 1 $ (buit :: [Int])
  • Una variant d'aquest sistema és definir els tipus associats no dins la classe sinó a nivell de mòdul, mitjançant #Famílies de tipus (clàusules type family/instance per als àlies de tipus, data family/instance per als tipus estructurals):
-- exemple de classe amb famílies de tipus
{-# LANGUAGE TypeFamilies, FlexibleContexts #-}

type family Element t
type instance Element [a] = a

class (Eq (Element t)) => Conjunt t where
 buit :: t
 afegeix :: Element t -> t -> t

instance Eq a => Conjunt [a] where
 buit = []
 afegeix x [] = [x]
 afegeix x (y : ys)
 | x == y = y : ys
 | otherwise = y : afegeix x ys

prova = afegeix 1 $ (buit :: [Int])

Correspondència amb els functors de ML modifica

Correspondència de les classes de Haskell amb els functors de ML, en definir una signatura de conjunt i la seva implementació. Vegeu Functors a OCaml

correspondència aproximada
Haskell ML a l'exemple
class signature Conjunt
instance structure
class (amb requeriments de context) signatura del functor
instance paramètrica (amb requeriments de context) def. del functor ConjuntVectorAmbIgualtat
restricció a un tipus concret del tipus d'una instance paramètrica aplicació del functor ConjuntVectorDeSencers
(els Int implementen igualtat)
  • Amb classes i tipus associats i canviant l'agregació de llista a vector.
{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FlexibleInstances #-}

import Data.Vector as Vector
import Data.Function ((&)) -- (&) = flip ($) -- infixl 1 -- desde GHC 7.10.1

class Set tip_conjunt where -- signatura del model de Conjunt
 type Element tip_conjunt -- Element és funció de tipus de tipconjunt

 buit :: tip_conjunt
 afegeix :: Element tip_conjunt -> tip_conjunt -> tip_conjunt
 llistar :: tip_conjunt -> [Element tip_conjunt]

-- functor ConjuntVectorAmbIgualtat
-- la var. del genèric és tip_elemt
-- els requeriments del tipus de la var. del genèric s'expliciten en el ''context =>''
-- i queda restringit existencialment (aquells tip_elemt tals que ''Eq tip_elemt'')

instance Eq tip_elemt => Set (Vector tip_elemt) where -- la var. del genèric és tip_elemt
 -- el tipus de la var és "aquells tip_elemt tals que Eq tip_elemt"

 type Element (Vector tip_elemt) = tip_elemt -- obtenció de tip_elemt per encaix de tip_conjunt
 buit = Vector.empty
 afegeix = \x xs -> Vector.cons x $ Vector.filter (/= x) xs
 llistar = Vector.toList

-- aplicació del functor al tipus ''Int'' mitjançant una restricció de tipus,
-- substituint la variable existencial ''tip_elemt'' del genèric
-- pel tipus concret Int com a subtipus (compleix els requeriments (def. d'igualtat) de ''tip_elemt'')

conj_buit = buit :: Vector Int -- ConjuntVectorDeSencers

prova1 = conj_buit & afegeix 2
 & afegeix 1
 & llistar
--------------------------------------------
-- Ampliació amb noves funcions

class (Set tip_conjunt) => SetHelp tip_conjunt where -- signatura noves funcions

 -- Element és membre de la classe base
 afegeixLlista :: [Element tip_conjunt] -> tip_conjunt -> tip_conjunt 

instance Eq tip_elemt => SetHelp (Vector tip_elemt) where -- estructura complementària

 afegeixLlista llista conjunt = Vector.foldr afegeix conjunt (Vector.fromList llista)

prova2 = conj_buit & afegeixLlista [1,2]
 & llistar

Temes avançats modifica

Monomorfisme, polimorfisme i quantificadors explícits modifica

Vegeu ref.[222]

Polimorfisme universal modifica

El polimorfisme per defecte dels paràmetres de funcions és l'universal.[223] El nombre de nivells de quantificació universal en una funció s'anomena ordre del tipus de la funció (ang: type rank). Vegeu Rank-N types (tipus d'ordre N).[224]

{-# LANGUAGE ExplicitForAll #-}

 -- declaracions idèntiques -- per a tot tipus ''a''
 id :: a -> a -- (tipus de 1r ordre)
 id :: forall a. a -> a -- amb quantificador universal explícit. (tipus de 1r ordre)

 -- forall explícit als paràmetres
 foo :: (forall a. a -> a) -> (Char,Bool) -- funció amb tipus de 2n ordre (ang: rank-2 type)

El forall explícit incorpora les variables de tipus a l'àmbit de la definició fent que les variables coincidents de les restriccions de tipus internes quedin unificades.[225][226]

{-# LANGUAGE ScopedTypeVariables #-}

f :: forall a. [a] -> [a] -- incorpora la var. 'a' a l'àmbit de la definició
f xs = ys ++ ys
 where
 ys :: [a] -- 'a' és el tipus del primer paràmetre de la funció
 ys = reverse xs

Vegeu també

  • Rank-2 types, ($) and the Monomorphism restriction[18]
  • Extensió {-# LANGUAGE NoMonomorphismRestriction #-}[227]
  • Des de GHC 7.2 Monomorfisme als lligams locals (let|where), excepte que s'expliciti la generalització.[228]

Tipus de Dades Algebraics Generalitzats (GADTs) modifica

És un tipus de dades paramètric, definit amb la clàusula data on els constructors venen descrits dins una clàusula where, amb una entrada per constructor com si fos una funció, permetent que els constructors retornin subtipus (més específics) del tipus paramètric que es defineix.

Té aplicació en els llenguatges incrustats per a camps d'aplicació específics, anomenats DSL (Domain Specific Language). Requereix l'extensió de llenguatge 'GADTs'.

Vegeu[229]

  • El guany és el refinament de tipus en fer l'encaix de patrons.
{-# LANGUAGE GADTs #-}

data Terme a where
 Literal :: Int -> Terme Int -- retorna el subtipus (Terme Int)
 Successor :: Terme Int -> Terme Int
 EsZero :: Terme Int -> Terme Bool -- retorna (Terme Bool)
 SiCondició :: Terme Bool -> Terme a -> Terme a -> Terme a -- retorna el tipus paramètric
 Parell :: Terme a -> Terme b -> Terme (a,b) -- retorna un parell com a paràmetre

avalua :: Terme a -> a -> a
avalua (Literal i) j = i+j -- l'operació + és permesa per què es dedueix
 -- que ''Literal i'' és de tipus (''Terme Int'')
 -- i per tant, en aquest encaix, ''a'' és Int
 -- ja no caldrà declarar que el tipus de ''a''
 -- ha d'implementar la suma: (Num a) => ...

Variables d'accés no garantit (amb unsafePerformIO) modifica

És una solució poc ortodoxa per la modificació de variables globals sense haver de passar la variable com a paràmetre, bàsicament per la modificació de preferències en temps d'execució.

De fet trenca el principi de transparència referencial de les funcions que les incorporin: constància del resultat amb les mateixes entrades, impedint l'ús de tècniques d'optimització com la memoïtzació (taules paràmetres/resultats per evitar el recàlcul).

Cal assegurar-se que la seva modificació sigui avaluada, abans de fer-ne ús, perquè el llenguatge no ho garanteix.

unsafePerformIO és la "porta del darrere" de la mònada IO.[230]

{-# OPTIONS_GHC -fno-cse -fno-full-laziness #-} -- recomanació de System.IO.Unsafe
import System.IO.Unsafe
import Data.IORef

-- variable de prova, a l'estil del llenguatge ML
{-# NOINLINE intRef #-} -- recomanació de System.IO.Unsafe
intRef :: IORef Int
intRef = unsafePerformIO $ newIORef 0

-- amp paràmetre ''unit'' () per forçar el recàlcul a cada invocació
-- evitant la memorització de les definicions sense paràmetres

{-# NOINLINE llegeix #-}
llegeix :: () -> Int
llegeix _ = unsafePerformIO $ readIORef intRef

Aquesta solució és incompatible amb la certificació Safe Haskell.

El sistema més correcte és tractar les preferències com un entorn canviant i passar-lo com a paràmetre allà on calgui, fent servir la mònada Reader o bé el transformador de mònades corresponent ReaderT.[231]

Vegeu Compilador Haskell de Glasgow#Tractament d'un entorn canviant, amb la mònada Reader

Classificacions dels tipus modifica

GHC utilitza la següent nomenclatura:[232][233]

Unboxed
Un tipus és Unboxed (no "encapsulat en memòria dinàmica") si i només si la seva representació no és un punter.
Lifted
El tipus Lifted és el dels objectes de codi, quin resultat s'avalua per encaix, on, després de provar tots els encaixos, el valor de fons (⊥)[158] atura el programa si els encaixos no són exhaustius o bé s'hi produeix un bucle.[233] Es diu que el tipus del resultat dels objectes de codi incorpora aquest valor (⊥).
  • Les tuples no encapsulades com a retorn (# ... #) són unlifted. Els tipus unlifted a la dreta d'una fletxa o assignació, són augmentats a Lifted implícitament.[233]
  • Només els tipus encapsulats (representats per un punter, ang:boxed) poden ser Lifted. Però hi ha tipus encapsulats Unlifted com ara ByteArray#.[233]
Data
Un tipus declarat amb la clàusula data.
Algebraic
És un tipus amb un o més constructors, encaixable amb un case, tant si s'ha creat amb data com amb newtype.
Primitiu
Un tipus és primitiu si no està definit mitjançant el Haskell.

kind modifica

Vegeu les refs.[234][235]

El kind (cat:mena) és una mena de tipus per als constructors de tipus, o, dit d'altra manera, un tipus d'ordre superior.

Defineix un conjunt de tipus,

  • o bé amb una màscara que descriu l'aritat del tipus, i la dels components de tipus d'ordre superior entre parèntesis (ex.: * -> (* -> *) -> *),
El Kind té dos constructors '*' i (->)
  • o bé partint d'un conjunt de restriccions de context (kind Constraint).
  • Afegit posteriorment, el kind '#'. '*' és el kind dels tipus Lifted (objectes de codi) mentre que '#' ho és dels tipus Unlifted (ex.:ByteArray#).[236][237]
expressió de tipus kind comentari
Int * tipus d'aritat 0 (no és funció de cap paràmetre de tipus)
Maybe * -> * constructor de tipus que és funció d'un component (Maybe a) (funció de tipus d'aritat 1)
Maybe Bool * aplicant el constructor Maybe a un tipus, tenim un tipus d'aritat 0
(Int -> Double) -> Int -> Double (* -> *) -> * -> * quan un dels paràmetres és una funció, s'expressa entre parèntesis

Són d'aplicació a les #Famílies de tipus, l'especificació de kind pot indicar l'aritat de la família i, en conseqüència, els paràmetres addicionals que requereixen les instàncies.

Vegeu també "GHC - Quantificació de kind explícita".[238] Requereix extensió {-# LANGUAGE KindSignatures #-}

sort

Els Kind tenen una categoria que els classifica (tipus a nivell de kind) que s'anomena sort. Només hi ha un valor de sort que és BOX.[239]

  • Promoció de tipus: GHC facilita automàticament la promoció de tipus a Kinds i de constructors de tipus a constructors de Kind, amb l'extensió DataKinds.[239]
data Nat = Zero | Succ Nat

-- és promogut a:

Nat :: BOX
Zero :: Nat
Succ :: Nat -> Nat

Per indicar en una expressió que ens referim a tipus i constructors de llistes i tuples promoguts a kinds, cal prefixar-ne els identificadors amb un apòstrof.[239]

{-# LANGUAGE DataKinds, PolyKinds, KindSignatures, GADTs, TypeOperators #-}
data Nat = Zero | Succ Nat

data Vec :: * -> Nat -> * where
 Nil :: Vec a Zero
 Cons :: a -> Vec a n -> Vec a (Succ n)

data HList :: [*] -> * where
 HNil :: HList '[]
 HCons :: a -> HList t -> HList (a ': t)

data Tuple :: (*,*) -> * where
 Tuple :: a -> b -> Tuple '(a,b)

kind Constraint modifica

kind que defineix el conjunt de tipus per les restriccions de context que han de complir.

Hi ha 3 menes de restriccions:[240]

  • requeriment de visibilitat d'instància de classe en el context d'ús, com a (Show a)
  • paràmetres implícits, com a (?x::Int) -- cal l'extensió ImplicitParams
  • requeriment d'unificació de tipus, com a (a ~ T b)[241] -- amb l'extensió TypeFamilies o GADTs

A partir de GHC 7.4, una especificació de restriccions de context adopta la categoria de kind Constraint i se li pot assignar un àlies.[242]

{- LANGUAGE ConstraintKinds -}

-- (Show a, Read a) :: Constraint -- requeriment de visibilitat d'instància de classe en el context d'ús
-- (?x::Int) :: Constraint -- arguments implícits (requeriment de visibilitat de l'implícit en el punt de crida)
-- (a ~ T b) :: Constraint -- restricció d'unificació de tipus

-- els podem assignar un àlies
type Textual a = (Show a, Read a)

viaString :: Textual a => a -> a
viaString = read. show

--- ghci

Prelude> :set -XExplicitForAll
Prelude> :kind forall t.(Show t, Read t)
forall t.(Show t, Read t) :: Constraint

Prelude> :set -XConstraintKinds
Prelude> type Textual t = (Show t, Read t)
Prelude> :kind forall t. Textual t
forall t. Textual t :: Constraint

Famílies de tipus modifica

Són una generalització dels tipus associats a les classes,[243] i permeten la definició, a nivell de mòdul, dels tipus dependents d'un altre tipus, declarat com a índex de la família, que en el cas dels tipus associats correspondria al tipus índex de la classe.

L'especificació equivalent a la signatura del tipus associat duu el qualificatiu family (com data family o bé type family) mentre que la corresponent a les instàncies duen el qualificatiu instance com es detalla tot seguit.

Famílies de tipus estructurals modifica

Permeten una diversificació de l'estructura, així com les classes admeten una diversificació del comportament, en instàncies per tipus.

Designen un conjunt de tipus mitjançant un constructor de família, i una variable anomenada l'índex i, opcionalment, una especificació de #kind, l'ordre del tipus.

Vegeu ref.[244] Requereix l'extensió de llenguatge TypeFamilies.

{-# LANGUAGE TypeFamilies, FlexibleInstances #-}

-- família de llistes
data family XList t -- t és l'índex de la família, de manera similar a les classes

-- instància de la família de tipus XList per al tipus Char
data instance XList Char = XNil | XCons Char (XList Char) deriving (Eq, Show)

-- instància de la família de tipus XList per al tipus () -- Unit (buit)
 -- com que el tipus () té un únic valor (), estalviem recursos
 -- especificant directa i únicament la llargada de la llista de ()
data instance XList () = XListUnit Int deriving (Eq, Show)

-- -------------- ús, requereix extensió FlexibleInstances

class TéLlargada t where
 llargada :: t -> Int

instance TéLlargada (XList ()) where
 llargada (XListUnit len) = len

instance TéLlargada (XList Char) where
 llargada XNil = 0
 llargada (XCons x xs) = 1 + llargada xs

laMevaLlistaDeBuits = XListUnit 10

main = print $ llargada laMevaLlistaDeBuits
  • L'especificació de kind permet conèixer els paràmetres de tipus que cal afegir-hi:
-- família de diccionaris per tipus de clau
data family Dicc k :: * -> *

-- el ''kind'' :: * -> * indica funció de tipus d'aritat 1,
-- o sigui que la instància requereix un altre paràmetre de tipus, el de l'element

-- instància per a claus de tipus String
data instance Dicc String e = DiccStringNil | DiccStringCons (String, e) (Dicc String e)

-- instància per a claus de tipus Unit (un únic valor de clau
 -- i per tant un sol element possible)
data instance Dicc () e = DiccUnitNil | DiccUnitSimple e

Famílies de sinònims de tipus (funcions de tipus) modifica

Les famílies de tipus especificades com a àlies (clàusula type family) defineixen funcions de tipus, quines correspondències (origen -> imatge) es concreten en instàncies de la família de tipus per casos d'encaix.

Exemple:

Les col·leccions monomòrfiques (el seu tipus és d'aritat 0, kind '*') com ara Text, ByteString o bé IntSet no poden implementar la classe de tipus Functor ja que aquesta requereix que estiguin parametritzades pel tipus de l'element (kind * -> *).
Això els impedeix d'implementar les classes derivades de Functor, per ex.: Foldable (per la reducció) i Traversable (per l'avaluació seqüencial dels elements si són efectes, o de les imatges d'un mapeig amb una funció d'efectes) que en depenen.
El paquet mono-traversable hi aporta una solució basada en famílies de sinònims de tipus, que permet un tractament universal de les col·leccions, siguin o no monomòrfiques.[245]
{-# LANGUAGE TypeFamilies #-}
type family Element t -- família de tipus Element de 't'
type instance Element Text = Char -- instància de la família Element per al tipus 'Text'
type instance Element IntSet = Int
type instance Element ByteString = Word8
type instance Element [a] = a
type instance Element (Set a) = a
...

class MonoFunctor t where
 omap :: (Element t -> Element t) -> t -> t 

instance MonoFunctor Text where
 omap = Text.map
...

-- també defineix les classes MonoFoldable i MonoTraversable
-- així com MonoFoldableEq (defineix pertinença (''elem'') com a plegat, per a contenidors amb elements amb igualtat)
--, MonoFoldableOrd (defineix màxim i mínim per a contenidors amb elements ordenables)
--, MonoFoldableMonoid (defineix concatMap per a contenidors amb elements que implementen Monoide)
-- ...
  • Els tipus associats a les classes no són altra cosa que famílies de tipus indexades pels paràmetres de la classe dels quals depenguin
{-# LANGUAGE TypeFamilies #-}

class Pila t where
 type Element t -- tipus associat: família de tipus indexada al param. de la classe
 buida :: t
 apila :: Element t -> t -> t
 ésBuida :: t -> Bool
 daltIDesapila :: t -> Maybe (Element t, t) -- resultat opcional per si la Pila era buida

-- instancia una Pila basada en llistes
instance Pila [a] where
 type Element [a] = a -- instància de la família de tipus
 buida = [] -- Nil
 apila = (:) -- Cons
 ésBuida = null
 daltIDesapila (x : resta) = Just (x, resta)
 daltIDesapila [] = Nothing -- evitem l'error "daltIDesapila: la llista era buida!"
Famílies de sinònims de tipus tancades modifica

Una família de sinònims de tipus amb un conjunt tancat (no ampliable) d'instàncies es pot expressar incloent les instàncies de la família en una clàusula where.[246]

type family F a where
 F Int = Double
 F Bool = Char
 F a = String

tipus dependents de valors modifica

Vegeu ref.[247] Exemple a Compilador Haskell de Glasgow#Tipus dependents de valors

Exemples modifica

Encapsulament estil O.O. modifica

(Aquest enfocament ha quedat superat pel tractament d'estructures complexes amb lents (referències funcionals)).

Cercant una aproximació funcional (amb immutabilitat) a l'encapsulament de la orientació a objectes i simulació d'herència de propietats del component:

  • Exemple. Els tipus d'objecte TPunt2D i TPunt3D (en terminologia Java classes (d'objectes) sense operacions), implementen (els en terminologia Java interfaces), el primer IFun2D i el segon IFun3D que requereix també IFun2D.

Un mòdul per cada tipus, encapsulant dades i operacions.

  • Es pot evitar duplicar les implementacions d'alguns mètodes desacoblant-los de l'estructura, definint classes de propietats (mètodes getters/setters), i classes basades en aquestes, implementant els mètodes per defecte a la classe en un mòdul a banda, i sobreescrivint-los quan convingui a la instanciació invalidant el mètode per defecte. (ang.:overriding)
-- fitxer ClassesPropietats.hs -- (getters/setters)
module ClassesPropietats (PropXY(..), PropZ(..)) where

class PropXY t where
 getXY :: t -> (Int, Int)
 setXY :: Int -> Int -> t -> t

class PropZ t where
 getZ :: t -> Int
 setZ :: Int -> t -> t
{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
-----------------------------------------------------------------------
-- Interfícies de funcionalitat
-- fitxer ClassesBasadesEnPropietats.hs 
module ClassesBasadesEnPropietats (IFun2D(..), IFun3D(..)) where

import ClassesPropietats

-- funcionalitat 2D

class (PropXY t) => IFun2D t where
 desplaça2D :: Int -> Int -> t -> t

instance (PropXY t) => IFun2D t where -- instanciació genèrica (ext. UndecidableInstances)
 desplaça2D dx dy punt = setXY (x+dx) (y+dy) punt
 where
 (x,y) = getXY punt

-- funcionalitat 3D

class (IFun2D t, PropZ t) => IFun3D t where
 desplaça3D :: Int -> Int -> Int -> t -> t

instance (IFun2D t, PropZ t) => IFun3D t where -- instanciació genèrica (ext. UndecidableInstances)
 desplaça3D dx dy dz punt = setZ (z+dz) $ desplaça2D dx dy punt
 where
 z = getZ punt
-- fitxer Punt2D.hs per al tipus TPunt2D
{-# LANGUAGE NamedFieldPuns #-} -- sintaxi de literals de registres simplificada
module Punt2D (
 TPunt2D, -- tipus opac (no exportem els constructors)
 nouPunt2D, -- el separador coma al final s'accepta
) where

import ClassesPropietats
import ClassesBasadesEnPropietats

data TPunt2D = Punt2D { x,y ::Int} deriving (Eq, Show)

-- generadors
nouPunt2D :: Int -> Int -> TPunt2D
nouPunt2D = Punt2D -- definició tàcita (estalviant arguments)

-- propietats
instance PropXY TPunt2D where
 getXY Punt2D {x,y} = (x, y) -- el literal dels camps lliga amb més precedència que l'aplicació (no cal agrupar-lo amb el constructor.
 -- {x, y} a l'encaix, equival a {x = x, y = y} per l'extensió NamedFieldPuns
 setXY x y punt = punt { x, y} -- {x, y} al cos equival a {x = x, y = y} per l'extensió NamedFieldPuns
-- fitxer Punt3D.hs per al tipus TPunt3D
{-# LANGUAGE NamedFieldPuns #-} -- sintaxi de literals de registres simplificada
module Punt3D (TPunt3D {- tipus opac -}, nouPunt3D) where

import ClassesPropietats
import ClassesBasadesEnPropietats

data TPunt3D = Punt3D { x, y, z :: Int} deriving (Eq, Show)

-- generador
nouPunt3D :: Int -> Int -> Int -> TPunt3D
nouPunt3D = Punt3D 

-- propietats
instance PropXY TPunt3D where
 getXY Punt3D {x,y} = (x, y)
 setXY x y punt = punt { x, y} -- {x, y} al cos equival a {x = x, y = y} per l'extensió NamedFieldPuns

instance PropZ TPunt3D where
 getZ Punt3D {z} = z -- a l'encaix {z} equival a {z = z}
 setZ z punt = punt {z} -- {z} equival a {z = z}

prova:

-- fitxer Main.hs
module Main where

import ClassesPropietats
import ClassesBasadesEnPropietats
import Punt2D
import Punt3D
import Data.Function ((&)) -- (&) = flip ($) -- infixl 1 -- desde GHC 7.10.1

p2D = nouPunt2D 1 2
 & setXY 3 4
 & desplaça2D 1 1

p3D = nouPunt3D 1 2 3
 & desplaça2D 2 2
 & desplaça3D 1 1 1

main = do
 putStrLn $ "p2D: " ++ show p2D
 putStrLn $ "p3D: " ++ show p3D

execució

runhaskell Main
  • Amb la prevista incorporació del polimorfisme de "registres amb camps específics" com a requeriment de context, les classes basades en accessors de camps, es derivaran automàticament.[248][249]

Col·leccions amb components heterogenis - Emulació del despatx dels mètodes virtuals de la O.O. modifica

Vegeu ref.[111] El #Tipus existencial dona solució al tractament de col·leccions heterogènies incorporant els elements heterogenis com a components amb quantificació existencial (requerint que instancïin les classes que aportin la operatòria requerida), sense trencar l'homogeneïtat de les col·leccions.

{-# LANGUAGE ExistentialQuantification #-}
module Forma (TForma(..), CForma(..)) where

import Text.Printf
import Data.Function ((&))

-- Farem una col·lecció de formes diverses que implementin CForma per obtenir-ne perímetre i àrea

class CForma t where
 perímetre :: t -> Double
 àrea :: t -> Double
 nom :: t -> String

-- tipus existencial (designa un cjt. de tipus amb característiques pròpies)
-- característica: incorpora un component d'aquells tipus ''t'' que implementin CForma

data TForma = forall t. CForma t => Forma t

-- implementació del tipus existencial amb
-- despatx dels mètodes propis als mètodes del component

instance CForma TForma where
 perímetre (Forma forma) = perímetre forma
 àrea (Forma forma) = àrea forma
 nom (Forma forma) = nom forma

instance Show TForma where
 show forma = printf format (forma & nom) (forma & perímetre) (forma & àrea)
 where format = "(Forma %s: perímetre %.2f, àrea %.2f)\n"
module Cercle (TCercle, cercleNou) where

import Forma (CForma(..))

type TRadi = Double

data TCercle = Cercle TRadi deriving (Eq, Show)

cercleNou :: TRadi -> Maybe TCercle
cercleNou r
 | r > 0 = Just (Cercle r)
 | otherwise = Nothing

instance CForma TCercle where
 perímetre (Cercle r) = 2 * pi * r
 àrea (Cercle r) = pi * r ^ 2
 nom _ = "Cercle"
module Rectangle (TRectangle, rectangleNou) where

import Forma (CForma(..))

type TCostat = Double

data TRectangle = Rectangle TCostat TCostat deriving (Eq, Show)

rectangleNou :: TCostat -> TCostat -> Maybe TRectangle
rectangleNou x y
 | x > 0 && y > 0 = Just $ Rectangle x y
 | otherwise = Nothing

instance CForma TRectangle where
 perímetre (Rectangle x y) = 2 * (x + y)
 àrea (Rectangle x y) = x * y
 nom _ = "Rectangle"
module Main where

import Forma (TForma(..)) 
import Cercle (cercleNou)
import Rectangle (rectangleNou)

formes :: [Maybe TForma]
formes = [fmap Forma (cercleNou 2.4),
 fmap Forma (rectangleNou 3.1 4.4),
 fmap Forma (cercleNou (-2))]

main = print formes
runhaskell Main.hs

dona la següent sortida:

[Just (Forma Cercle: perímetre 15.08, àrea 18.10)
,Just (Forma Rectangle: perímetre 15.00, àrea 13.64)
,Nothing]

Entrada / Sortida amb descriptor regionalitzat modifica

  • Vegeu withFile (fitxers de text) / withBinaryFile (de System.IO) basat en la clàusula de gestió de recursos bracket amb tancament automàtic en cas d'excepció.
-- obre el fitxer, proporciona al tercer param. el 'Handle' per a l'acció i tanca el fitxer en havent acabat l'acció o en cas d'excepció.
withFile, withBinaryFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r 

-- per al cas de codi espars (disgregat)
openFile, openBinaryFile :: FilePath -> IOMode -> IO Handle -- obre el fitxer i proporciona el recurs
hClose :: Handle -> IO () -- tanca el recurs

-- withFile es defineix mitjançant bracket
withFile nomDelFitxer mode acció = bracket (openFile nomDelFitxer mode) hClose acció

-- o per eta-reducció, obtenint la versió tàcita (que estalvia arguments)
withFile nomDelFitxer mode = bracket (openFile nomDelFitxer mode) hClose

Exemple:

 -- Fitxer prova.hs (decodifica nombres i n'imprimeix el factorial)
 module Main(main) where

 import qualified Control.Monad as Monad
 import qualified Numeric
 import Control.Exception (try)
 import Text.Printf
 import System.IO
 import System(getArgs)

 llegirSencerDecimal :: String -> Maybe (Int, String)
 llegirSencerDecimal text =
 case Numeric.readDec text of -- retorna llista d'interpretacions sintàctiques
 [(n_descodificat, resta)] -> Just (n_descodificat, resta) -- una sola interpretació
 [] -> Nothing -- cap ni una => no hi ha dígits
 _ -> Nothing -- diverses interpretacions => entrada ambigua

 -- Integer: precisió arbitrària, Int: precisió s/. paraula de la màquina

 fact :: Int -> Integer
 fact 0 = 1
 fact n | n > 0 = fact_rf 1 n

 where fact_rf :: Integer -> Int -> Integer
 fact_rf acum n | n > 1 = fact_rf (acum * toInteger n) (n-1)
 | n == 1 = acum

 imprimeix_fact n resta_de_linia = do
 printf "n=%d => fact n =%d" n (fact n) -- escriu n, (fact n)
 case resta_de_linia of
 [] -> putStrLn "" -- escriu salt de línia
 (sep:_) | sep `elem` "," -> putStrLn " (seguit de separador)"
 _ -> putStrLn " (hi ha text enganxat al nombre)"

 analitza_linia_i_tracta_nombre :: Handle -> IO (Either IOError ())
 analitza_linia_i_tracta_nombre h1 = try -- retorna excepció en un tipus Either per controlar-la més tard
 (do
 línia <- hGetLine h1
 case llegirSencerDecimal línia of
 Just (n, resta) -> imprimeix_fact (abs n) resta
 Nothing -> putStrLn "error de descodificació o nombre inexistent"
)

 processa_linia_fitxer:: Handle -> IO ()
 processa_linia_fitxer h1 = do
 resultat <- analitza_linia_i_tracta_nombre h1
 case resultat of -- controla excepció al resultat de tipus Either IOError ()
 Left excepcio -> do
 -- tasques de finalització
 -- hClose h1 -- tanca fitxer (amb withFile ja no caldrà)
 ioError excepcio -- dispara excepció
 Right _valor -> return ()

 processa_nom_fitxer nom = catch
 (do
 withFile nom ReadMode $ \handle ->
 -- llaç fins que es dispari una excepció per fi de fitxer
 Monad.forever $ processa_linia_fitxer handle
)
 (\excep -> putStrLn $ show excep
)

 main = do
 args <- getArgs
 case args of
 [] -> print "afegiu nom_del_fitxer\n"
 -- mapM_ mapeja seqüencialment la funció efecte als arguments, descartant resultats
 _ -> Monad.mapM_ processa_nom_fitxer args

 -- fi de prova.hs
-- fitxer nums.txt
0
1ABC
2,
10
15—fi de nums.txt
runhaskell prova.hs nums.txt

produeix la sortida següent:

n=0 => fact n =1
n=1 => fact n =1 (hi ha text enganxat al nombre)
n=2 => fact n =2 (seguit de separador)
n=10 => fact n =3628800
n=15 => fact n =1307674368000
nums.txt: hGetLine: end of file

Expressions regulars modifica

Vegeu-ho a Compilador Haskell de Glasgow#Expressions regulars

Lents de Van Laarhoven -- consulta i manipulació de parts d'estructures complexes modifica

Vegeu-ho a GHC

Exemples de Fletxes (seqüenciació d'efectes basada en l'encadenament de funcions amb efectes) modifica

Ent./Sort. amb fletxes de Kleisli (mònades elevades a fletxes) modifica

Vegeu exemple a Fletxa (programació funcional)#Ent./Sort. amb fletxes de Kleisli (mònades elevades a fletxes)

Extreure informació d'un document XML modifica

Vegeu exemple a Fletxa (programació funcional)

Plantilles per a expressions, patrons, tipus i declaracions modifica

Vegeu Compilador Haskell de Glasgow#Quasi-Quotations (Plantilles)

Concurrència modifica

Concurrència simple amb MVars - Productor-consumidor modifica

Vegeu-ho a Haskell concurrent

Canals amb cues d'entrada (Chan) - Client-servidor modifica

Vegeu exemple a Haskell concurrent

Concurrència condicionada amb variables transaccionals (TVar) - Mònada STM (transaccions de memòria per programari) modifica

Vegeu exemple a Haskell concurrent

Paral·lelisme modifica

Vegeu Compilador Haskell de Glasgow#Paral·lelisme de tasques - Compilació per a processadors multicor

Paral·lelisme de tasques - Compilació per a processadors multicor modifica

Només a GHC. Vegeu Compilador Haskell de Glasgow#Paral·lelisme de tasques - Compilació per a processadors multicor.

Amb Estratègies modifica

Vegeu: Estratègies i també Reducció en paral·lel mitjançant estratègies

Amb la mònada Par modifica

Vegeu Haskell concurrent#Futuribles -- Encadenament de càlculs asíncrons - la mònada Par

Paralel·lisme d'accions IO - El functor aplicatiu Concurrently modifica

Vegeu Haskell concurrent#Operacions d'Entrada/Sortida asíncrones i simultànies

Amb paral·lelisme de dades DPH modifica

Vegeu QuickSort amb Data Parallel Haskell

Ús de les instruccions SIMD sobre dades vectoritzades modifica

Vegeu SIMD - Single Instruction, Multiple Data

Paral·lelisme a GPUs (GPGPU) o equivalent (via OpenCL) sobre CPUs multicor modifica

Vegeu Paral·lelisme GPGPU

Relacionat modifica

Referències modifica

  1. 1,0 1,1 Haskell.org - Lazy vs. Non-strict (anglès) Avaluació tardana i avaluació no-estricta
  2. «Haskell is a strict language». Arxivat de l'original el 2009-10-19. [Consulta: 24 octubre 2009].
  3. HaskellWiki - Thunk(anglès)
  4. Generalising Monads to Arrows(anglès)
  5. Haskell 2010(anglès) Secció 2.4 Identifiers and Operators
  6. Estructura lèxica del Haskell98 (anglès)
  7. Extensió UnicodeSyntax Arxivat 2005-11-29 a Wayback Machine.(anglès)
  8. H2010 - Identificadors i variables(anglès)
  9. Sintaxi H2010(anglès) La producció consym distingeix com a constructors aquells símbols que comencen per ':' mentre que la varsym exceptua els que comencen pel mateix caràcter.
  10. Haskell 2010(anglès) La producció consym està continguda en la qop dels operadors binaris infix (secció 3.4 Operator Applications)
  11. haskellWiki - Implicit parameters(anglès)
  12. Referència de sintaxi de Haskell2010(anglès)
  13. Haddock - Com documentar(anglès)
  14. Invocant Haddock - opcions(anglès)
  15. 15,0 15,1 Haskell 2010(anglès) Secció 1.4 Namespaces
  16. IEC.DLC - Sagnat
  17. 17,0 17,1 17,2 Fixity declarations(anglès) Precedències i associativitat dels operadors
  18. 18,0 18,1 18,2 Rank-2 types, ($), and the monomorphism restriction (anglès)
  19. Guia de classes estàndards - Instàncies derivades (anglès)
  20. Instàncies derivades - especificació (anglès)
  21. Prelude del H98 (Predefinits)(anglès) La majoria de les classes instanciables amb la clàusula deriving són aquí
  22. 22,0 22,1 Data.Eq(anglès)
  23. 23,0 23,1 23,2 23,3 Data.Ord(anglès)
  24. 24,0 24,1 24,2 Prelude#Enum(anglès)
  25. 25,0 25,1 25,2 Prelude#Bounded(anglès)
  26. 26,0 26,1 26,2 26,3 Data.Ix - classe dels índexs (anglès)
  27. 27,0 27,1 Text.Show(anglès)
  28. 28,0 28,1 Text.Read(anglès)
  29. Data.Text.Internal Internally, the Text type is represented as an array of Word16 UTF-16 code units.
  30. Numeric(anglès)
  31. 31,0 31,1 Prelude#Num(anglès)
  32. 32,0 32,1 Data.Bits(anglès)
  33. 33,0 33,1 Prelude#Real(anglès)
  34. 34,0 34,1 Prelude#Integral(anglès)
  35. 35,0 35,1 Prelude#Fractional(anglès)
  36. 36,0 36,1 Prelude#RealFrac(anglès)
  37. 37,0 37,1 Prelude#Floating(anglès)
  38. 38,0 38,1 Prelude#RealFloat(anglès)
  39. 39,0 39,1 39,2 Data.Ratio(anglès)
  40. 40,0 40,1 Data.Fixed - reals de coma fixa(anglès)
  41. GHC - Extensions a la clàusula deriving Arxivat 2010-04-10 a Wayback Machine.(anglès)
  42. Data.Bool(anglès)
  43. Data.Char(anglès)
  44. 44,0 44,1 Data.String(anglès)
  45. El paquet text(anglès)
  46. Data.Int(anglès)
  47. Haskell2010 - Especificació dels enters(anglès)
  48. Data.Word(anglès)
  49. Numeric.Natural(anglès)
  50. mòdul Data.Ratio(anglès)
  51. Data.Complex(anglès)
  52. 52,0 52,1 HaskellWiki - Unary minus(anglès)
  53. Ambiguous Types, and Defaults for Overloaded Numeric Operations(anglès)
  54. Int addition and product avoiding bad results
  55. Data.Word - secció Notes
  56. classe Integral
  57. Instàncies de la classe Real
  58. L'extensió DuplicateRecordFields(anglès)
  59. Extensions de GHC - Record Puns Arxivat 2005-11-29 a Wayback Machine.(anglès) simplificació de sintaxi en l'encaix dels camps dels registres
  60. Tuples a Haskell98(anglès)
  61. Data.Tuple
  62. Record field selector polymorphism(anglès)
  63. El tipus Maybe(anglès)
  64. El tipus Either(anglès)
  65. try avalua una acció que llança excepcions retornant el resultat en un tipus Either(anglès)
  66. 66,0 66,1 Sinònims de tipus liberals Arxivat 2010-04-25 a Wayback Machine.(anglès)
  67. 67,0 67,1 Llistes - Nil i Cons(anglès)
  68. Data.List(anglès)
  69. Generalised (SQL-Like) List Comprehensions Arxivat 2014-10-25 a Wayback Machine. (anglès)
  70. FPComplete.com - List comprehension extensions Arxivat 2014-10-25 a Wayback Machine. (anglès)
  71. How to pick your string library in Haskell(anglès) Com escollir la biblioteca de cadenes de caràcters en Haskell
  72. biblioteca "text" de tires com a vectors de caràcters de 16 bits(anglès)
  73. Estructura del tipus Text a Data.Text.Internal[Enllaç no actiu](anglès)
  74. 74,0 74,1 Extensió OverloadedStrings[Enllaç no actiu](anglès)
  75. La classe IsList(anglès)
  76. OverloadedLists[Enllaç no actiu](anglès)
  77. Lambda-case Arxivat 2016-04-13 a Wayback Machine.(anglès)
  78. Aplicació parcial
  79. HaskellWiki - Lazy evaluation (anglès)
  80. Pragmes Strict i StrictData(anglès)
  81. 81,0 81,1 81,2 «BangPatterns i Strict». Arxivat de l'original el 2018-08-21. [Consulta: 15 juliol 2021].
  82. GHC - Implicit parameters Arxivat 2012-01-30 a Wayback Machine.(anglès)
  83. Data.Proxy(anglès)
  84. serve a Servant.Server(anglès)
  85. 85,0 85,1 El mòdul Data.Function
  86. Secció (tall) d'un operador binari en infix (anglès)
  87. GHC Guia d'usuari - senyal de compilació: -fwarn-incomplete-patterns Arxivat 2010-03-25 a Wayback Machine.(anglès) deshabilitada per defecte
  88. Pattern type signatures Arxivat 2010-04-14 a Wayback Machine. (anglès)
  89. Pattern guards(anglès)
  90. 90,0 90,1 GHC users guide - Pattern synonyms Arxivat 2018-08-21 a Wayback Machine.(anglès)
  91. Associated pattern synonyms(anglès)
  92. ghc users guide - View patterns Arxivat 2016-04-13 a Wayback Machine.(anglès)
  93. HaskellWiki - Let vs Where
  94. Depuració al Haskell(anglès)
  95. Debug.Trace - Imprimir traces des del codi funcional(anglès)
  96. Debug.Trace.traceStack
  97. 97,0 97,1 97,2 haskellwiki - Orphan instance(anglès)
  98. haskellWiki Multiple instances(anglès)
  99. GHC - Stand-alone deriving Arxivat 2018-08-21 a Wayback Machine.(anglès)
  100. GHC users guide - DeriveAnyClass Arxivat 2016-11-20 a Wayback Machine.(anglès)
  101. Haskellwiki - newtype
  102. 102,0 102,1 HaskellWiki - Performance, Newtypes(anglès) "The newtype constructor will be optimised away."
  103. L'extensió GeneralizedNewtypeDeriving Arxivat 2018-08-21 a Wayback Machine.(anglès)
  104. Keyword arguments in Haskell(anglès)
  105. tipus existencials en Haskell(anglès)
  106. Haskell GHC - Perquè existencial Arxivat 2010-04-25 a Wayback Machine.(anglès)
  107. «Quantificació existencial». Arxivat de l'original el 2010-04-25. [Consulta: 15 juny 2010].
  108. Tipus existencial a EHC/UHC amb la paraula reservada exists Arxivat 2012-04-29 a Wayback Machine.(anglès)
  109. Tipus existencial al JHC amb la paraula reservada exists(anglès)
  110. 110,0 110,1 110,2 Existentially quantified - Record constructors Arxivat 2016-03-14 a Wayback Machine.(anglès)
  111. 111,0 111,1 Tipus existencial (anglès)
  112. mòdul Data.Dynamic - tipatge dinàmic bàsic(anglès)
  113. 113,0 113,1 Erik Meijer et al. - Functional programming with Bananas, Lenses, Envelopes and Barbed Wire(anglès)
  114. Haskell wiki - Newtype(anglès)
  115. Functor al mòdul Data.Functor(anglès)
  116. Set is not a Functor Arxivat 2016-02-15 a Wayback Machine.(anglès)
  117. Sets, Functors and Eq confusion(anglès)
  118. El paquet contravariant(anglès)
  119. El paquet profunctors(anglès)
  120. paquet "lens" - lents per a isomorfismes (anglès)
  121. Mezzolens - Lents basades en profunctors (anglès)
  122. MonadPlus reform proposal(anglès)
  123. classe Applicative(anglès)
  124. classe Monad(anglès)
  125. Biblioteca Base de GHC - vegeu mòduls marcats Base(anglès)
  126. 126,0 126,1 Arrows: A General Interface to Computation
  127. classe Category(anglès)
  128. classe Arrow(anglès)
  129. Control.Category(anglès)
  130. 130,0 130,1 Control.Arrow(anglès)
  131. Introducció planera a "Haskell XML Toolbox" (anglès)
  132. «Fletxes (Arrows) a GHC». Arxivat de l'original el 2007-10-14. [Consulta: 3 gener 2010].
  133. «Fletxes (Arrows) a l'intèrpret Hugs». Arxivat de l'original el 2009-06-16. [Consulta: 3 gener 2010].
  134. 134,0 134,1 Proposta MonadFail
  135. Control.Monad.Fail
  136. mòdul Data.Traversable(anglès)
  137. when i unless de Control.Monad(anglès)
  138. Throw / Catch, restriccions (anglès)
  139. Biblioteca exceptions(anglès)
  140. 140,0 140,1 Biblioteca safe-exceptions(anglès)
  141. Control.Exception.evaluate(anglès)
  142. Control.DeepSeq.force(anglès)
  143. Catching exceptions(anglès)
  144. FPComplete.com - Exceptions Best Practices in Haskell(anglès)
  145. Crides d'error en Ent./Sort.(anglès)
  146. 146,0 146,1 146,2 El paquet exceptions
  147. finally de Control.Exception(anglès)
  148. Novetats de la 7.6.1 - biblio. base(anglès) "The deprecated Control.OldException module has now been removed"
  149. Control.Exception(anglès)
  150. Control.Exception.catches
  151. Configuracions del RunTimeSystem(anglès)
  152. HaskellWiki - Debugging - Locating a failure in a library function(anglès)
  153. $(err') de File-location(anglès)
  154. Assercions(anglès)
  155. Paràmetres implícits especials(anglès)
  156. Notes de la versió 7.10.2(anglès)
  157. GHC 8.0.x Migration Guide(anglès)
  158. 158,0 158,1 Bottom (No finalització)
  159. Undefined (Implementació pendent)
  160. Data.IORef(anglès)
  161. Data.STRef(anglès)
  162. Control.Concurrent.MVar(anglès)
  163. Control.Concurrent.STM.TVar(anglès)
  164. Control.Concurrent.STM.TMVar(anglès)
  165. IORef's - referències mudables dins la mònada IO
  166. Top level mutable state (anglès) Estat de nivell global d'un programa
  167. Data.IORef atomicModifyIORef(anglès) L'ús d' atomicModifyIORef per protegir més d'una IORef està desaconsellat
  168. La mònada ST (anglès) efecte de canvis d'estat
  169. La mònada ST - referència(anglès)
  170. STRef's - referències a valors mudables dins la mònada ST
  171. Control.Monad.ST - Converting ST to IO(anglès)
  172. Iteratee I/O(anglès)
  173. Lèxic anglès sufix -er Arxivat 2021-06-04 a Wayback Machine.(anglès)
  174. «Lèxic anglès sufix -ee». Arxivat de l'original el 2021-07-15. [Consulta: 15 juliol 2021].
  175. Deterministic allocation and freeing of scarce resources(anglès) Allotjament determinista i alliberament de recursos escassos.
  176. Introducció als conduits Arxivat 2014-02-22 a Wayback Machine.(anglès)
  177. proposta MonadFail(anglès)
  178. GHC.IO - failIO[Enllaç no actiu](anglès)
  179. Monad - fail (implementació per defecte en la classe Monad)[Enllaç no actiu](anglès)
  180. paquet either amb el transformador EitherT(anglès)
  181. Eduard Kmett - Lens wiki - Overview(anglès)
  182. The lens library(anglès)
  183. HaskellWiki - Lens(anglès)
  184. 184,0 184,1 El paquet lens(anglès)
  185. History of lenses(anglès)
  186. Van Laarhoven - CPS Functional References(anglès)
  187. Edward Kmett - Consider "pure profunctor" lenses(anglès)
  188. Purescript profunctor lenses - Biblioteca de lents per al llenguatge PureScript (quasi-haskell compilable a JavaScript)(anglès)
  189. La biblio mezzolens(anglès)
  190. Mezzolens - La lent wander(anglès)
  191. Easier lenses, Profunctor based, with the Mezzolens library(anglès)
  192. 192,0 192,1 Mezzolens - toListOf, sumOf(anglès)
  193. Edward Z. Yang - The Haskell Preprocessor Hierarchy(anglès)
  194. HaskellWiki - Programació literària (codi inserit en documentació)(anglès)
  195. HaskellWiki - Alex
  196. HaskellWiki - Happy
  197. «Explicit namespaces in import/export». Arxivat de l'original el 2016-04-13. [Consulta: 15 juliol 2021].
  198. Importació d'instàncies
  199. Noms de mòdul jeràrquics Arxivat 2005-11-29 a Wayback Machine.(anglès)
  200. Running a compiled program Arxivat 2009-04-17 a Wayback Machine. (anglès) Paràmetres per al Run Time System
  201. H2010 Standard Prelude - funcions i tipus predefinits(anglès)
  202. H98 Standard Prelude - funcions i tipus predefinits(anglès)
  203. Prelude del GHC(anglès)
  204. API's de les biblioteques bàsiques del Haskell(anglès)
  205. Interfície dels vectors immutables(anglès)
  206. Interfície dels vectors mudables(anglès)
  207. Paquet vector (anglès)
  208. mòdul Data.Array.Parallel(anglès)
  209. Vectors de tractament paral·lel (paral·lelisme de dades)(anglès)
  210. Vectors en memòria amb accés de nivell baix(anglès)
  211. Vectors d'elements encapsulats(anglès)
  212. Vectors d'elements Unboxed (No encapsulats)(anglès)
  213. HaskellWiki - DiffArrays(anglès)
  214. Hackage - DiffArrays - Vectors per diferència(anglès)
  215. Vectors en memòria global (mònada IO)(anglès)
  216. Vectors en memòria local (mònada ST)(anglès)
  217. mòdul Data.Array(anglès)
  218. Data.Ix(anglès)
  219. Altres tipus de vectors a GHC (anglès)
  220. Extensions comunes a GHC i Hugs Arxivat 2013-01-26 a Wayback Machine. (anglès)
  221. Classes multiparàmetre i dependències funcionals(anglès)
  222. Monomorphism_restriction(anglès)
  223. Polimorfisme
  224. Rank-N types(anglès)
  225. Lexically scoped type variables Arxivat 2018-08-21 a Wayback Machine.(anglès)
  226. Guia d'extensions de GHC - ExplicitForAll
  227. Switching off the Monomorphism restriction Arxivat 2021-01-31 a Wayback Machine.(anglès)
  228. Let Generalisation in GHC 7.2(anglès) Monomorfisme als lligams locals
  229. «Tipus de Dades Algebraics Generalitzats (GADTs)». Arxivat de l'original el 2010-04-10. [Consulta: 7 març 2010].
  230. System.IO.Unsafe - unsafePerformIO(anglès)
  231. The Reader monad(anglès)
  232. Classifying Types(anglès)
  233. 233,0 233,1 233,2 233,3 GHC - Heap objects(anglès)
  234. HaskellWiki - Kind (anglès)
  235. Haskell's kind system - a primer
  236. Unlifted Data Types(anglès)
  237. El tipus Type i els seus col·legues(anglès)
  238. Explicitly-kinded quantification Arxivat 2012-01-30 a Wayback Machine.(anglès)
  239. 239,0 239,1 239,2 Promoció de tipus Arxivat 2018-08-21 a Wayback Machine.(anglès)
  240. «El kind Constraint». Arxivat de l'original el 2015-09-05. [Consulta: 7 desembre 2015].
  241. Equality constraints Arxivat 2012-08-04 a Wayback Machine.(anglès)
  242. Constraint Kinds for GHC(anglès)
  243. «GHC - Famílies de tipus». Arxivat de l'original el 2012-08-04. [Consulta: 20 desembre 2011].
  244. HaskellWiki - Type Families(anglès)
  245. mòdul Data.MonoTraversable(anglès)
  246. Closed type families Arxivat 2016-03-14 a Wayback Machine.(anglès)
  247. Dependent Types in Haskell Arxivat 2015-12-22 a Wayback Machine.(anglès)
  248. Polymorphism over record fields(anglès)
  249. OverloadedRecordFields - MagicClasses(anglès)