Vés al contingut

Usuari:Griba2010/proves/Característiques del llenguatge Haskell

De la Viquipèdia, l'enciclopèdia lliure

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
acció :: IO Int  
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

Les accions es poden compondre

  • per encadenament del resultat (serialització)
-- composició monàdica
bloc :: IO ()
bloc = getLine >>= \x -> putStrLn x   -- imprimeix el resultat de l'entrada
  • per combinació dels resultats
-- composició aplicativa
bloc :: IO (String, String) -- aparella els resultats de dues entrades
bloc = (,) `fmap` getLine  -- aplica un combinador al resultat de la primera acció
           <*> getLine     -- 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 alguna de manera més específica, 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 * Opció 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) (anell unitari) (adreçable
als bits)
(anell unitari ordenat) (íntegre amb divisió euclidiana;
anell unitari ordenat euclidià)
(fraccionable: estructura algebraica de cos) (infos. de
fraccionables
estruct. de cos ordenat)
(càlculs i
trigonometria
estruct. de cos)
(infos. de
coma flotant
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 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 #newtype -- tipus derivats

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
{-| compilat amb GHC 8.2.0.20170310 -}
{-# LANGUAGE DuplicateRecordFields, DataKinds, FlexibleContexts, TypeApplications #-} 

import GHC.Records (HasField( getField))

type Coord = Double

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

getX :: HasField "x" r Coord => r -> Coord
getX = getField @"x"

main = do
        print $ getX $ Punt2D 2 3
        print $ getX $ Punt3D 4 5 6

Pròximament s'hi afegirà `setField`.[63]

tipus polimòrfics
[modifica]

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

  • Maybe (paràmetres i resultats opcionals).[64]
  • 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).[65][66]
 -- 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.[67]

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)[68] i ":" (tip_elem * llista_elem, anomenat cons)[68] Operacions al mòdul Data.List[69]

 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

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

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

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

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"[72]

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"[73]

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

-- | 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.[75]

{-# 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[75] "abc" :: Text
'[' 1,2,3 ']'
(* seq. de valors *)
IsList[76] fromList OverloadedLists[77] [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)

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.[78]
{-# LANGUAGE LambdaCase #-}

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

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

Aplicació parcial

[modifica]

Vegeu ref.[79]

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)[80] = 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[81]
-- modifica la semàntica
patró per defecte: estricta i per valor
~patró lazy (avaluació per necessitat call by need)

Vegeu extensions BangPatterns i Strict[82]

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.[82]

-- 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 (~).[82]

À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.[83]
{-# 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.[84]

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

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

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[86]

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.[87]

-- 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),[88] 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ó.[89]

  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.[90]

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.[91]

  • 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

[92]

{-# 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:[91]
  • 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.[93]

  • 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".[94]

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.[86]

(.) 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
  • 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.[95] Imprimeix el primer paràmetre, via unsafePerformIO i retorna el segon.[96]

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[97] 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.[98]
  • Les instàncies no es poden tapar amb altres instàncies.[99]
  • 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.[98]
  • 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.[98]

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.[100]

{-# 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.[101] 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

newtype -- tipus derivats

[modifica]

Les instàncies d'una classe de tipus per a un tipus no tenen nom. Per poder definir diferents instàncies (ex.: Monoides per a la suma i per al producte) per a un tipus, cal fer-ho sobre tipus derivats.

-- 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[102] 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).[103]

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).[104]

{-# 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"[105]

ú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 [106] 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.[107][108]
  • Altres compiladors de Haskell fan servir la paraula reservada exists com a quantificador existencial, el EHC/UHC segons la ref.,[109] i el JHC també,[110] 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.[111]

  • No es permeten els camps quantificats existencialment en les actualitzacions de registres.[111]
  • 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.[111]
quantificació existencial als àlies de tipus - sinònims liberalitzats

Admet restriccions existencials, a més d'altres possibilitats.[67] 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.[112] 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).[113]

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*).[114]

-- 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).[114]

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[115] 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.[103]
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 contenidora d'elements que permet substituir la composició d'aplicacions de morfismes per l'aplicació de la composició (Vegeu desforestació), on l'operació fmap[116] permet l'aplicació d'una funció als elements de les col·leccions 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
Els Functors han de preservar el morfisme identitat i la composició dels morfismes. 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)
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.[117][118]
  • 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.[119]

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.[120]

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.[121][122]

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,[123] 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.[124]

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.[125]

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 ...).[126]

Vegeu també Combinadors aplicatius partint de mònades

Efectes fletxa

[modifica]

Vegeu ref.[127]

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.[127] Es concreta en les classes Category[128] (que generalitza les funcions amb paràmetres pel tipus de l'entrada i de la sortida) i la seva especialització Arrow[129] 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.[131]

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.[132]

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

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[135]
o bé mònada MonadFail a GHC >= 8.0[136]
Mònada sense mètode fail.
L'encaix haurà de ser irrefutable
acció >>= (\ patró -> e) mònada Monad des de GHC >= 8.8[135]
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.[137]

  • 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.[138]

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[139] 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.[140]

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í.[141]

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).[142][143]

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.[144]

  • 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".[145]

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.[146]

Llançador generalitzat
[modifica]
  • el paquet exceptions[147] 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,[147] 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.[148]

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[149]

-- 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[150]

  • 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.[151]

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

catch generalitzat
[modifica]
  • el paquet exceptions[147] 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[141] 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.[152]
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,[153]
  • o bé amb la crida err equivalent a error del paquet file-location[154] 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.[155]

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.[156] (Des de GHC 7.10.2)[157] 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.[158]

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.[159]

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[160] é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[161] IO newIORef x Data.IORef vars. globals no sincronitzades,
cicle de vida no acotat
STRef a[162] ST newSTRef x Data.STRef vars. per a canvis d'estat encapsulables,
cicle de vida lligat a l'àmbit de ST
MVar a[163] 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[164] 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[165] 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,[166][167] 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[168] (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[169][170] (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[171] 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.[172]
 -- 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.[173]

  • 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ó[174] com ara del verb employ el substantiu Employer.
El sufix -ee obté de l'acció un subjecte passiu (persona/objecte afectats o subordinats),[175] 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[176] als conduits)[177] 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)[178] es produeix disparant una excepció IOError.[179]

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

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,[181] 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.[182][183][184][185]

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.[186]
  • Lents de Van Laarhoven.[187] Va ser el creador de la base teòrica de les lents actual emprada per la biblioteca "lens" de Edward Kmett.[185]
  • 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.[188][189][190]

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.[191]
  • 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.[192]
  • 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.[193]
  • sumOf obté el plegat sobre el Monoide del newtype Sum.[193]
* 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),[194] 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.[195]
  • (.x): mòdul d'especificació de lèxic segons preprocessador Alex.[196]
  • (.y): mòdul d'especificació de gramàtica segons preprocessador Happy.[197]

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.[198]

{-# 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.[199]
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[200]

É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[201] 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.[202][203] Prelude del GHC.[204]

Les API's de les biblioteques bàsiques són aquí[205]

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.[206] Vegeu #Vectors immutables del H98
Vectors mudables
interfície (classe) MArray, parametritzats pels tipus de l'índex i de l'element.[207] Exemple a #Vectors mudables de dades no encapsulades
Vectors d'alt rendiment
al paquet vector,[208] 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.[209] (cada op. conclou havent avaluat tots els elements) Només a GHC.[210]
Vectors amb allotjament contigu, per a ésser accedits des de mòduls forans (per ex. lleng. C)
interfície Storable.[211]

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[212] UArray[213]

 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.[214] 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.[215]
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.[216]
 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).[217]
 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.[218][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)[219]

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.[220][221] 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).[222]

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.[223]

Polimorfisme universal

[modifica]

El polimorfisme per defecte dels paràmetres de funcions és l'universal.[224] 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).[225]

{-# 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.[226][227]

{-# 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 #-}[228]
  • Des de GHC 7.2 Monomorfisme als lligams locals (let|where), excepte que s'expliciti la generalització.[229]

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[230]

  • 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.[231]

{-# 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.[232]

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:[233][234]

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 (⊥)[159] atura el programa si els encaixos no són exhaustius o bé s'hi produeix un bucle.[234] 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.[234]
  • Només els tipus encapsulats (representats per un punter, ang:boxed) poden ser Lifted. Però hi ha tipus encapsulats Unlifted com ara ByteArray#.[234]
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.[235][236]

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#).[237][238]
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".[239] 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.[240]

  • 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.[240]
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.[240]

{-# 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:[241]

  • 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)[242] -- 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.[243]

{- 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,[244] 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.[245] 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.[246]
{-# 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.[247]

type family F a where
  F Int  = Double
  F Bool = Char
  F a    = String

tipus dependents de valors

[modifica]

Vegeu ref.[248] Exemple a Compilador Haskell de Glasgow#Tipus dependents de valors

  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. Tasques a GHC - Add setField to HasField
  64. El tipus Maybe(anglès)
  65. El tipus Either(anglès)
  66. try avalua una acció que llança excepcions retornant el resultat en un tipus Either(anglès)
  67. 67,0 67,1 Sinònims de tipus liberals Arxivat 2010-04-25 a Wayback Machine.(anglès)
  68. 68,0 68,1 Llistes - Nil i Cons(anglès)
  69. Data.List(anglès)
  70. Generalised (SQL-Like) List Comprehensions Arxivat 2014-10-25 a Wayback Machine. (anglès)
  71. FPComplete.com - List comprehension extensions Arxivat 2014-10-25 a Wayback Machine. (anglès)
  72. How to pick your string library in Haskell(anglès) Com escollir la biblioteca de cadenes de caràcters en Haskell
  73. biblioteca "text" de tires com a vectors de caràcters de 16 bits(anglès)
  74. Estructura del tipus Text a Data.Text.Internal[Enllaç no actiu](anglès)
  75. 75,0 75,1 Extensió OverloadedStrings[Enllaç no actiu](anglès)
  76. La classe IsList(anglès)
  77. OverloadedLists[Enllaç no actiu](anglès)
  78. Lambda-case(anglès)
  79. Aplicació parcial
  80. HaskellWiki - Lazy evaluation (anglès)
  81. Pragmes Strict i StrictData(anglès)
  82. 82,0 82,1 82,2 BangPatterns i Strict
  83. GHC - Implicit parameters Arxivat 2012-01-30 a Wayback Machine.(anglès)
  84. Data.Proxy(anglès)
  85. serve a Servant.Server(anglès)
  86. 86,0 86,1 El mòdul Data.Function
  87. Secció (tall) d'un operador binari en infix (anglès)
  88. GHC Guia d'usuari - senyal de compilació: -fwarn-incomplete-patterns Arxivat 2010-03-25 a Wayback Machine.(anglès) deshabilitada per defecte
  89. Pattern type signatures Arxivat 2010-04-14 a Wayback Machine. (anglès)
  90. Pattern guards(anglès)
  91. 91,0 91,1 GHC users guide - Pattern synonyms(anglès)
  92. Associated pattern synonyms(anglès)
  93. ghc users guide - View patterns(anglès)
  94. HaskellWiki - Let vs Where
  95. Depuració al Haskell(anglès)
  96. Debug.Trace - Imprimir traces des del codi funcional(anglès)
  97. Debug.Trace.traceStack
  98. 98,0 98,1 98,2 haskellwiki - Orphan instance(anglès)
  99. haskellWiki Multiple instances(anglès)
  100. GHC - Stand-alone deriving(anglès)
  101. GHC users guide - DeriveAnyClass Arxivat 2016-11-20 a Wayback Machine.(anglès)
  102. Haskellwiki - newtype
  103. 103,0 103,1 HaskellWiki - Performance, Newtypes(anglès) "The newtype constructor will be optimised away."
  104. L'extensió GeneralizedNewtypeDeriving(anglès)
  105. Keyword arguments in Haskell(anglès)
  106. tipus existencials en Haskell(anglès)
  107. Haskell GHC - Perquè existencial Arxivat 2010-04-25 a Wayback Machine.(anglès)
  108. «Quantificació existencial». Arxivat de l'original el 2010-04-25. [Consulta: 15 juny 2010].
  109. Tipus existencial a EHC/UHC amb la paraula reservada exists(anglès)
  110. Tipus existencial al JHC amb la paraula reservada exists(anglès)
  111. 111,0 111,1 111,2 Existentially quantified - Record constructors(anglès)
  112. Tipus existencial (anglès)
  113. mòdul Data.Dynamic - tipatge dinàmic bàsic(anglès)
  114. 114,0 114,1 Erik Meijer et al. - Functional programming with Bananas, Lenses, Envelopes and Barbed Wire(anglès)
  115. Haskell wiki - Newtype(anglès)
  116. Functor al mòdul Data.Functor(anglès)
  117. Set is not a Functor Arxivat 2016-02-15 a Wayback Machine.(anglès)
  118. Sets, Functors and Eq confusion(anglès)
  119. El paquet contravariant(anglès)
  120. El paquet profunctors(anglès)
  121. paquet "lens" - lents per a isomorfismes (anglès)
  122. Mezzolens - Lents basades en profunctors (anglès)
  123. MonadPlus reform proposal(anglès)
  124. classe Applicative(anglès)
  125. classe Monad(anglès)
  126. Biblioteca Base de GHC - vegeu mòduls marcats Base(anglès)
  127. 127,0 127,1 Arrows: A General Interface to Computation
  128. classe Category(anglès)
  129. classe Arrow(anglès)
  130. Control.Category(anglès)
  131. 131,0 131,1 Control.Arrow(anglès)
  132. Introducció planera a "Haskell XML Toolbox" (anglès)
  133. «Fletxes (Arrows) a GHC». Arxivat de l'original el 2007-10-14. [Consulta: 3 gener 2010].
  134. «Fletxes (Arrows) a l'intèrpret Hugs». Arxivat de l'original el 2009-06-16. [Consulta: 3 gener 2010].
  135. 135,0 135,1 Proposta MonadFail
  136. Control.Monad.Fail
  137. mòdul Data.Traversable(anglès)
  138. when i unless de Control.Monad(anglès)
  139. Throw / Catch, restriccions (anglès)
  140. Biblioteca exceptions(anglès)
  141. 141,0 141,1 Biblioteca safe-exceptions(anglès)
  142. Control.Exception.evaluate(anglès)
  143. Control.DeepSeq.force(anglès)
  144. Catching exceptions(anglès)
  145. FPComplete.com - Exceptions Best Practices in Haskell(anglès)
  146. Crides d'error en Ent./Sort.(anglès)
  147. 147,0 147,1 147,2 El paquet exceptions
  148. finally de Control.Exception(anglès)
  149. Novetats de la 7.6.1 - biblio. base(anglès) "The deprecated Control.OldException module has now been removed"
  150. Control.Exception(anglès)
  151. Control.Exception.catches
  152. Configuracions del RunTimeSystem(anglès)
  153. HaskellWiki - Debugging - Locating a failure in a library function(anglès)
  154. $(err') de File-location(anglès)
  155. Assercions(anglès)
  156. Paràmetres implícits especials(anglès)
  157. Notes de la versió 7.10.2(anglès)
  158. GHC 8.0.x Migration Guide(anglès)
  159. 159,0 159,1 Bottom (No finalització)
  160. Undefined (Implementació pendent)
  161. Data.IORef(anglès)
  162. Data.STRef(anglès)
  163. Control.Concurrent.MVar(anglès)
  164. Control.Concurrent.STM.TVar(anglès)
  165. Control.Concurrent.STM.TMVar(anglès)
  166. IORef's - referències mudables dins la mònada IO
  167. Top level mutable state (anglès) Estat de nivell global d'un programa
  168. Data.IORef atomicModifyIORef(anglès) L'ús d' atomicModifyIORef per protegir més d'una IORef està desaconsellat
  169. La mònada ST (anglès) efecte de canvis d'estat
  170. La mònada ST - referència(anglès)
  171. STRef's - referències a valors mudables dins la mònada ST
  172. Control.Monad.ST - Converting ST to IO(anglès)
  173. Iteratee I/O(anglès)
  174. Lèxic anglès sufix -er(anglès)
  175. Lèxic anglès sufix -ee
  176. Deterministic allocation and freeing of scarce resources(anglès) Allotjament determinista i alliberament de recursos escassos.
  177. Introducció als conduits Arxivat 2014-02-22 a Wayback Machine.(anglès)
  178. proposta MonadFail(anglès)
  179. GHC.IO - failIO[Enllaç no actiu](anglès)
  180. Monad - fail (implementació per defecte en la classe Monad)(anglès)
  181. paquet either amb el transformador EitherT(anglès)
  182. Eduard Kmett - Lens wiki - Overview(anglès)
  183. The lens library(anglès)
  184. HaskellWiki - Lens(anglès)
  185. 185,0 185,1 El paquet lens(anglès)
  186. History of lenses(anglès)
  187. Van Laarhoven - CPS Functional References(anglès)
  188. Edward Kmett - Consider "pure profunctor" lenses(anglès)
  189. Purescript profunctor lenses - Biblioteca de lents per al llenguatge PureScript (quasi-haskell compilable a JavaScript)(anglès)
  190. La biblio mezzolens(anglès)
  191. Mezzolens - La lent wander(anglès)
  192. Easier lenses, Profunctor based, with the Mezzolens library(anglès)
  193. 193,0 193,1 Mezzolens - toListOf, sumOf(anglès)
  194. Edward Z. Yang - The Haskell Preprocessor Hierarchy(anglès)
  195. HaskellWiki - Programació literària (codi inserit en documentació)(anglès)
  196. HaskellWiki - Alex
  197. HaskellWiki - Happy
  198. Explicit namespaces in import/export
  199. Importació d'instàncies
  200. Noms de mòdul jeràrquics Arxivat 2005-11-29 a Wayback Machine.(anglès)
  201. Running a compiled program Arxivat 2009-04-17 a Wayback Machine. (anglès) Paràmetres per al Run Time System
  202. H2010 Standard Prelude - funcions i tipus predefinits(anglès)
  203. H98 Standard Prelude - funcions i tipus predefinits(anglès)
  204. Prelude del GHC(anglès)
  205. API's de les biblioteques bàsiques del Haskell(anglès)
  206. Interfície dels vectors immutables(anglès)
  207. Interfície dels vectors mudables(anglès)
  208. Paquet vector (anglès)
  209. mòdul Data.Array.Parallel(anglès)
  210. Vectors de tractament paral·lel (paral·lelisme de dades)(anglès)
  211. Vectors en memòria amb accés de nivell baix(anglès)
  212. Vectors d'elements encapsulats(anglès)
  213. Vectors d'elements Unboxed (No encapsulats)(anglès)
  214. HaskellWiki - DiffArrays(anglès)
  215. Hackage - DiffArrays - Vectors per diferència(anglès)
  216. Vectors en memòria global (mònada IO)(anglès)
  217. Vectors en memòria local (mònada ST)(anglès)
  218. mòdul Data.Array(anglès)
  219. Data.Ix(anglès)
  220. Altres tipus de vectors a GHC (anglès)
  221. Extensions comunes a GHC i Hugs Arxivat 2013-01-26 a Wayback Machine. (anglès)
  222. Classes multiparàmetre i dependències funcionals(anglès)
  223. Monomorphism_restriction(anglès)
  224. Polimorfisme
  225. Rank-N types(anglès)
  226. Lexically scoped type variables(anglès)
  227. Guia d'extensions de GHC - ExplicitForAll
  228. Switching off the Monomorphism restriction Arxivat 2021-01-31 a Wayback Machine.(anglès)
  229. Let Generalisation in GHC 7.2(anglès) Monomorfisme als lligams locals
  230. «Tipus de Dades Algebraics Generalitzats (GADTs)». Arxivat de l'original el 2010-04-10. [Consulta: 7 març 2010].
  231. System.IO.Unsafe - unsafePerformIO(anglès)
  232. The Reader monad(anglès)
  233. Classifying Types(anglès)
  234. 234,0 234,1 234,2 234,3 GHC - Heap objects(anglès)
  235. HaskellWiki - Kind (anglès)
  236. Haskell's kind system - a primer
  237. Unlifted Data Types(anglès)
  238. El tipus Type i els seus col·legues(anglès)
  239. Explicitly-kinded quantification Arxivat 2012-01-30 a Wayback Machine.(anglès)
  240. 240,0 240,1 240,2 Promoció de tipus(anglès)
  241. «El kind Constraint». Arxivat de l'original el 2015-09-05. [Consulta: 7 desembre 2015].
  242. Equality constraints Arxivat 2012-08-04 a Wayback Machine.(anglès)
  243. Constraint Kinds for GHC(anglès)
  244. «GHC - Famílies de tipus». Arxivat de l'original el 2012-08-04. [Consulta: 20 desembre 2011].
  245. HaskellWiki - Type Families(anglès)
  246. mòdul Data.MonoTraversable(anglès)
  247. Closed type families(anglès)
  248. Dependent Types in Haskell Arxivat 2015-12-22 a Wayback Machine.(anglès)