Haskell

De Viquipèdia
Dreceres ràpides: navegació, cerca
Per a altres significats, vegeu «Haskell (desambiguació)».
Haskell
Logo of Haskell
Paradigma de programació: programació funcional estandarditzat de semàntica no estricta i avaluació tardana
Aparegut l'any: 1990; fa 26 anys (1990)
Darrera versió estable: Haskell 2010[1]
Majors implementacions GHC, Hugs, NHC, JHC, Yhc, UHC
Dialectes: Helium, Gofer
Influenciat per: Clean,[2] FP,[2] Gofer,[2] Hope and Hope+,[2] Id,[2] ISWIM,[2] KRC,[2] Lisp,[2] Miranda,[2] ML and ML Estàndard,[2] Orwell, SASL,[2] SISAL,[2] Scheme[2]
Ha influenciat: Agda,[3] Bluespec,[4] C++11/Concepts,[5] C#/LINQ,[6][7][8][9] CAL,[cal citació] Cayenne,[6] Clean,[6] Clojure,[10] CoffeeScript,[11] Curry,[6] Elm, Epigram,[cal citació] Escher,[12] F#,[13] Frege,[14] Hack,[15] Idris,[16] Isabelle,[6] Java/Generics,[6] LiveScript,[17] Mercury,[6] Omega,[cal citació] Perl 6,[18] Python,[6][19] Scala,[6][20] Swift,[21] Timber,[22] Visual Basic 9.0[6][7]
Sistema operatiu: multiplataforma
Extensió dels fitxers: .hs, .lhs
Pàgina web: haskell.org

Haskell és un llenguatge de programació funcional estandarditzat de semàntica no estricta i avaluació tardana de les expressions (ang: lazy evaluation) en el moment que se'n demana el valor i pren el nom del matemàtic Haskell Curry.

Es diu que és un llenguatge funcional pur. El cert és que admet variables d'estat però permet encapsular-ne els canvis, o circumscriure'n els efectes col·laterals al nivell superficial.

Haskell destaca en la facilitat per al paral·lelisme, per aprofitar la potència dels processadors multicor.[23][24]

A finals dels anys 1980 es va constituir un comitè amb l'objectiu de recollir en un llenguatge les característiques dels múltiples llenguatges funcionals de l'època, Miranda, ML i altres.

La primera versió va sortir el 1990. La versió més estesa actualment és la que correspon a l'informe Haskell 98.[25] Tanmateix el compilador GHC incorpora l'estàndard Haskell2010 per defecte a partir de la versió 7.0[26]

A principi de 2006 va començar el procés per definir-ne una nova revisió amb el nom de Haskell' ("Haskell prima", ang:"Haskell prime").[27] Diversos compiladors incorporen extensions del llenguatge que per a fer-les servir caldrà precedir el codi font amb la pragma {-# LANGUAGE extensió1, extensió2, ... #-} o bé el senyal corresp. de compilació ( -Xextensió per al GHC). El wiki de "Haskell Prima" esmenta per cada extensió els compiladors que la implementen.[28]

Haskell 2010:[29] Actualment ha finalitzat el procés de discussió de les incorporacions a la nova versió de l'estàndard,[30][31] així com un nou procés d'evolució amb revisions (bi-)anuals del llenguatge.[32]

Haskell 201x en preparació.[33]

Crítiques:

  • Haskell té un desavantatge important en la dificultat de depuració, que obliga a un esforç especial en la prevenció de fallades:
    • El model d'execució de Haskell fa que no hi hagi traça de pila de crides. Però se n'ha desenvolupat una de simulada per quan es compila per l'ajustatge (profiling). Per aquest cas, cal disposar de compilacions per l'ajustatge de totes les biblioteques relligades.[34][35][36]
    • Les petades per crides a error de les funcions parcials (proveu head []) donen, en compilació de producció, informació molt escassa (ni situació de l'error (abans de GHC 8.0), ni la de la crida que incompleix la precondició). Per això es recomana no utilitzar-les en funcions parcials i convertir-les en totals amb resultat opcional caçant el cas no previst en l'anàlisi del retorn. Recentment des de GHC 7.10.2 hi ha la possibilitat d'obtenir el punt de crida d'origen mitjançant paràmetres implícits especials (Vegeu exemple). Alternativa més segura: evitar les funcions parcials (La biblioteca Safe ofereix alternatives a les funcions parcials del Prelude).[37]
    • Els errors en funcions parcials de col·leccions no imprimeixen els valors causants, perquè la textualització dels elements (Show elem) no s'exigeix.
    • L'ús de traces per depurar es revela un maldecap per la irregularitat en l'ordre d'impressió que segueix l'ordre d'execució i no el d'especificació, indeterminat (per tant aleatori) en codi funcional pur, subjecte a l'avaluació tardana en traces en expressions let del codi monàdic (seqüencial). A GHC 8.0 l'extensió Strict aporta avaluació estricta de les associacions a variables locals (ang:bindings), no les globals (top level).[38]
  • S'ha criticat que no hi hagi un sistema d'extensió de registres[39] i/o especialització. A l'intèrpret Hugs se'n va desenvolupar un anomenat TRex[40] (exemple) que GHC no ha incorporat.
En realitat es pot aconseguir facilitat en l'especialització de comportament, desacoblant la funcionalitat de l'estructura amb propietats (getters/setters) com es descriu a l'Encapsulament estil O.O.. Pròximament la implementació prevista de polimorfisme de registres amb camps específics estalviarà definir els getters/setters, perquè tindrem els accessors com a requeriment de context.[41][42]

Problemàtiques:

Vegeu secció #Problemàtiques.

Contingut

Programa Hola Món[modifica | modifica el codi]

El GHC (Compilador Haskell de Glasgow) és el compilador / intèrpret amb més possibilitats. Instruccions per obtenir-lo, les trobareu aquí.[43] o potser millor descarregant la Plataforma Haskell (La versió que incorpora la e./s. de caràcters no anglosaxons ja està disponible).

Per a Windows i altres podem descarregar-lo d'aquí.[43]

-- fitxer hola-mon.hs
main = putStrLn "Hola Món"
-- fi de fitxer

En un sistema Linux podem tenir GHC i altres compiladors de Haskell i runhaskell es pot configurar per executar una alternativa o altra amb un selector d'alternatives de sistema[44] o bé amb la interfície gràfica galternatives[45] (normalment apunta a runghc o runghc6). A MSWindows podem usar indistintament runhaskell o runghc.

Execució per GHC sense compilació prèvia[46]

$ runhaskell hola-mon      # cerca el nom de fitxer amb l'extensió .hs o bé .lhs

Hola Món

Amb l'intèrpret GHCi del mateix paquet. Denominació ghci o ghci6 segons instal·lació.

A l'intèrpret no serveix la mateixa sintaxi del nivell declaratiu dels programes, sinó només la dels blocs d'operacions d'entrada / sortida, que corresponen a l'encadenament mònadic de resultats, a banda de les comandes específiques[47] que comencen pel caràcter dos-punts i les expressions a avaluar.

Això canvia a partir de GHC 7.4.1 que admet tot tipus de declaracions a l'intèrpret GHCi.[48]

$ ghci
GHCi, version 6.10.1: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer ... linking ... done.
Loading package base ... linking ... done.
Prelude> putStrLn "Hola Mon"
Hola Mon
Prelude> let str = "abc"
Prelude> :type str
str :: [Char]
Prelude> str
"abc"
Prelude> let f x y = x + y
Prelude> :type f
f :: Num a => a -> a -> a
Prelude>

Avaluador en-línia, a l'intèrpret d'expressions funcionals tryhaskell.org.

 reverse "Hola"
 > "aloH"
  :: [Char]

Cercadors[modifica | modifica el codi]

  • Hayoo: on es pot consultar quines biblioteques exporten un identificador, a les biblioteques del rebost oficial (Hackage).
Permet cercar operadors no alfanumèrics com ara (>>=)
  • Hoogle: permet, a més a més, cerca de funcions per signatura. Exemples de cerca:
map
(a -> b) -> [a] -> [b]
Ord a => [a] -> [a]
Data.Map.insert

Característiques[modifica | modifica el codi]

fonamentat en el càlcul lambda[modifica | modifica el codi]

La reducció de l'aplicació d'una funció a un paràmetre mitjançant la substitució del paràmetre al cos de la funció, permet veure una aplicació parcial (de menys paràmetres que l'aritat de la funció) com a funció dels paràmetres que li manquen, i facilita l'ús d'aplicacions parcials 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'

-- (->) é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))  -- per tant encaixaria en un patró de tipus de funció (a -> r)

separació de codi funcional pur i codi amb efectes col·laterals[modifica | modifica el codi]

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 i per tant es podrien paraŀlelitzar
f x y = (x * 2) + (y * 4)  -- l'ordre d'execució dels operadors no-arrel 
                           -- queda a discreció de l'optimitzador del compilador, indeterminat.

-- codi impur (modifica o depèn de l'entorn o de l'estat). 
-- * L'ordre és rellevant
-- * El tipus del resultat acompanya al tipus de l'entorn que l'acció modifica o de quin estat depèn

acció :: IO Int  -- el tipus de l'acció/efecte és el de l'entorn parametritzat pel tipus del resultat
acció = do
   putStrLn "entreu nombre enter"
   hFlush stdout
   str <- getLine            --  (<-) obté el resultat de l'efecte de la dreta
   let x = read str :: Int   -- el bloc 'let' facilita la def. en base a expressions sense efectes col·laterals
       y = x * 2
   return y   -- ''return'' genera una dada efecte del tipus del bloc ''do'' amb el paràm. com a resultat

separació d'especificació i execució[modifica | modifica el codi]

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 | modifica el codi]

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.[49] 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[50] determina quan un terme serà sempre requerit en les expressions, i converteix la seva avaluació en primerenca (ang.: eager evaluation) .[49] 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.[51]

separació d'estructura i operacions (comparat amb la POO)[modifica | modifica el codi]

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" que 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'' dóna 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 on se'n requereixi l'ú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' sense paràmetres (aritat 0)
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] }

fer_més_savi = fer_anys. (aprendre_llenguatge Lleng_HASKELL)

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 | modifica el codi]

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 | modifica el codi]

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 (unió discriminada), distingint-los amb un constructor que ens permetrà diferenciar-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 | modifica el codi]

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

Genèrics[modifica | modifica el codi]

Els mòduls genèrics, paramètrics en tipus, d'altres llenguatges, aquí prenen la forma de classes multiparàmetre. Vegeu #Estructures TAD genèriques - Classes multiparàmetre.

Col·leccions heterogènies[modifica | modifica el codi]

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 de tipus diversos tals que implementin la classe corresponent a l'operació que s'hi vol aplicar.

Variables[modifica | modifica el codi]

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

Per al cas de concurrència amb baldes (blocant), les variables mudables MVar (amb monitor) que es buiden en consultar-les, ofereixen una triple funcionalitat de variables amb accés sincronitzat, bústia d'un sol element i semàfor binari. Per al cas de comunicació amb encuament, Chan i BoundedChan són bústies amb encuament i blocatge.

Per a 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 | modifica el codi]

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).

La seqüenciació (precedència temporal) de computacions amb efectes col·laterals es pot obtenir amb tres abstraccions diferents.

  • Mònada és una classe de tipus (col·lecció d'operacions), on (>>=) encadena una acció prèvia amb una funció (amb possibles efectes col·laterals) sobre el resultat precedent, mentre que (>>) ignora el resultat de l'acció precedent (k1 >> k2 = k1 >>= const k2). Els blocs do constitueixen una sintaxi específica per als encadenaments de mònades.
  • La classe de tipus Applicative, permet combinar, amb una funció o bé un constructor, els resultats individuals d'una cadena d'accions deslligades.
  • La classe de tipus Fletxa generalitza les Mònades i modela les accions com a morfismes encadenables, parametritzant els tipus del domini d'entrada i el del resultat. Per poder efectuar càlculs amb els resultats, les fletxes inclouen funcionalitat per tractar-los aparellats, actuant sobre els components del parell. Les clàusules proc en són una sintaxi específica. (exemple: Ent./Sort. amb fletxes de Kleisli)

Vegeu #Efectes

Composició d'efectes[modifica | modifica el codi]

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

Lèxic[modifica | modifica el codi]

Identificadors[modifica | modifica el codi]

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

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

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> print alçada
 1.5
 Prelude> let opinió = 2
 Prelude> let l'internauta=123
 Prelude> let coŀ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.[55]

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 | modifica el codi]

començant per lletra[modifica | modifica el codi]

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

començant per símbol[modifica | modifica el codi]

cas de guió baix
  • el comodí '_': patró que no interessa per al càlcul
  • precedint un identificador, com a _abc123 : variables esmentades en els patrons per a documentació però no utilitzades. El guió baix precedint la variable no utilitzada evita que el compilador mostri un missatge d'atenció (Warning) pel fet.[56]
començant per ':' (caràcter dos-punts) 
constructors no alfanumèrics binaris (habitualment emprats en posició infix),[57][58] 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[59]
començant per altres símbols
operadors

Sintaxi[modifica | modifica el codi]

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

comentaris[modifica | modifica el codi]

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

comentaris d'autodocumentació[modifica | modifica el codi]

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

Llenguatge de marcatge (els delimitadors determinen el tractament):

'identificador_enllaçat' 
/èmfasi/ 
__ressaltat__ 
@monoespaiat@ 
<http://exemple.com etiqueta de la URL>
<<imatge.png títol 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ó en html (opció -h):

haddock -h fitxers.hs -o docdir

{- poden sortir missatges de manca d'enllaç amb la documentació d'identificadors forans
  "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'' (ext.: .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 | modifica el codi]

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

  • variables
  • constructors (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.[63]

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 | modifica el codi]

De dues maneres possibles:

  • mitjançant 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}
  • segons el marge esquerre de la disposició (sagnat).[64] Els punt-i-coma al final de línia es poden estalviar.
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
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 | modifica el codi]

Qualsevol funció de dos o més operands es pot emprar en posició infix (entremig) si es posa 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

 mes x y = x + y

 resultat = 4 `mes` 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.[65]

operadors d'aplicació ($) i (&)[modifica | modifica el codi]

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 o el retorn són funcions polimòrfiques sense declaració de tipus, com s'explica a la ref.[66]
  • ($) té la menor de les precedències dels operadors i associativitat per la dreta.[65]
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 (enganxat a l'identificador com $ident, o bé al parèntesi com $(expr)) per avaluar un identificador o bé una expressió en temps de compilació.

Tipus i classes[modifica | modifica el codi]

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)

  {-# MINIMAL (==) | (/=) #-} -- indicació de quins mètodes cal implementar com a mínim, en definir-ne una instància

classes i clàusula deriving[modifica | modifica el codi]

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

classe context
(requereix visibilitat d'instàncies
de les classes esmentades
en l'àmbit d'ús)
descripció operacions
Eq α[70] Igualable (==) (/=)
Ord α[71] Eq α Ordenable (<), (<=), ..., (compare: amb resultat ternari Ordering), (max), (min)
Enum α[72] Enumerable (succ: successor) (pred: predecessor)
(fromEnum: enter ordinal corresponent)
(toEnum: corresp. a l'enter ordinal)
Bounded α[73] Acotat (minBound: cota inferior) (maxBound: cota superior)
Ix α[74] Ord α Que pot indexar (index: índex basat en 0), (inRange), (rangeSize), (range: llista compresos)

Classes {Show, Read} per a la representació textual de dades:

  • Els caràcters no ASCII (quina repr. depèn de la codificació) en un tipus String es mostren amb un codi numèric, així read. show == id independentment de la codificació.
  • Per l'entrada/sortida de texts no anglosaxona, cal fer servir el tipus Text del paquet del mateix nom.
classe descripció operacions
Show α[75] Textualitzable a String (show), (showList), (showsPrec).
Read α[76] Llegible des de String (readList), (readsPrec)
  • el mòdul Numèric proporciona un ventall més ampli de conversions entre text i nombres.[77]

Classes de tipus numèrics:

classe context descripció operacions
Num α[78] Numèric
(operacions comunes
a sencers, Racionals i Reals)
(+), (-), (*), (negate), (abs), (signum), (fromInteger)
Bits α[79] Num α Adreçable als bits (.&. :i), (.|. :o), xor, complement,
shift, rotate, ...
Real α[80] (Num α, Ord α) Conversions dels Reals representables (aproximats) (toRational: obtenció de l'equivalent Racional)
Integral α[81] (Real α, Enum α) Enter. Implementa divisió sencera (div), (mod), (quot: quocient), (rem: romanent),
(toInteger: a sencer de precisió il·limitada)

Els parells de funcions (div, mod) i (quot, rem) són similars però difereixen en els valors negatius (quot parteix cap a zero).

  • Classes per al suport de racionals i reals.
classe context descripció operacions
Fractional α[82] Num α Fraccionable (/), (recip: recíproc), (fromRational)
RealFrac α[83] (Real α, Fractional α) components / info
sobre fraccions
(truncate), (round),
(ceiling: (sostre) major sencer inferior), (floor: (terra) menor sencer superior),
(properFraction: fracció pròpia)
RealFloat α[84] (RealFrac α, Floating α) components / 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)), ...
Floating α[85] Fractional α càlculs en coma flotant
logaritmes i trigonometria
(sqrt), (**: exponenciació), (exp), (log: logaritme natural), (logBase),
(pi), (sin), (cos), (tan), (asin), (sinh), (asinh), ...

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

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

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

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

Tipus[modifica | modifica el codi]

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

El tipus String no és el millor per al tractament de text a l'entrada/sortida, s'utilitza per l'anàlisi gramatical i la serialització, i show string mostra els caràcters no anglosaxons imprimint el codi numèric.

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

numèrics
tipus context constructors / =expr mòdul descripció
Int, Int<N> N∈{8,16,32,64}[93] Data.Int Sencers (aritmètica del "Complement a dos")
(cas de Int: rang mínim [-2^29 .. 2^29-1][94])
Word, Word<N> N∈{8,16,32,64}[95] Data.Word Naturals, (aritmètica sense signe)
Integer Prelude Sencer de precisió il·limitada
Natural[96] Numeric.Natural Natural de precisió il·limitada
Float, Double Prelude Coma flotant
Ratio t[86] Integral t Generables amb l'operador (%)
(%) :: t -> t -> Ratio t
Data.Ratio Racionals genèrics (t * t)
concretables per a parells (numerador, denom.) d'enters de diferents precisions[97]
Rational[86] = Ratio Int Data.Ratio Racionals (Int * Int)
Uni, Deci, Centi,
Milli, Micro,
Nano, Pico[87]
Data.Fixed Coma fixa de representació sencera
valor = representació * resolució del tipus
Complex t[98] RealFloat t = !t :+ !t Data.Complex amb prefix '!' d'avaluació estricta
efectes
tipus context constructors / =expr
(a banda de return x)
mòdul descripció
Maybe t Nothing | Just x Data.Maybe paràm. opcionals
o bé efecte fallada (resultat opcional en operacions parcialment definides)
Either tipError tipResultat Left error | Right resultat Data.Either Opció dual Right (correcte) o Left (mancat)
, freq. usat per al resultat
de func. parcialment definida
amb info de l'error
[] tipElement [], (x : xs), [x], [x, y, ...] Data.List llistes o bé 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 Control.Monad.ST,
Data.STRef
encapsulament local d'efectes,
lectura/escriptura de variables STRef (cicle de vida lligat a l'àmbit)
avaluables en memòria local o bé global
excepcions
tipus 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
altres
tipus context constructors / exemples mòdul descripció
(t1,t2,...,tn) (,)
(,,)
(,,...,)
Data.Tuple tipus producte anònim (tupla)
data nomDeTipus Constructor t1 t2 .. tn tipus producte amb nom
Constructor1 t11 t12 .. t1n
| Constructor2 t21 t22 .. t2m
| ...
tipus suma (Unió discriminada)
Dl | Dt | Dc | Dj | Dv Enumeració
Constructor {camp1 :: t1, camp2,camp3 :: t2i3, ...} Registres (tipus producte amb accessors)
  • classes que implementen (a banda de Eq,[70] Show[75] i Read)[76]
bàsics
Ord[71] Enum[72] Ix[74] Bounded[73]
(ordenable) (enumerable) (que pot indexar) (acotat)
() -- Unit
Bool
Ordering
Char
numèrics
Ord[71] Enum[72] Ix[74] Bounded[73] Num[78] Bits[79] Real[80] Integral[81] Fractional[82] RealFrac[83] RealFloat[84] Floating[85]
(ordenable) (enumerable) (que pot indexar) (acotat) (numèric) (adreçable
als bits)
(real
convertible
a racional)
(enter
divisió sencera)
(fraccionable) (infos.
de fraccions)
(infos.
de coma flotant)
(càlculs i
trigonometria)
Int, Int<N>,
Word, Word<N>
No
-- precisió iŀ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 | modifica el codi]

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
(^) :: (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
(^^) :: (Fractional a, Integral ex) => a -> ex -> a

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

Els literals numèrics no s'associen a un tipus (domini), per la tinença o no del punt decimal, sinó a una classe: operacions on poden intervenir.

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''  
                   --   (ops. comunes de tots els tipus numèrics: (+) (-) (*) ...)
Prelude> :type 1.5
1.5 :: (Fractional t) => t   -- tipus t tal que implementa la signatura de ''Fractional''
                -- 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 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 vénen determinats pel tipus del primer paràmetre
  • explícitament, (1::Double)
  • provant una seqüència de tipus (clàusula default)[99]
-- 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 | modifica el codi]

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)
conversions[modifica | modifica el codi]

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

fromIntegral :: (Integral a, Num b) => a -> b   -- des de sencer

realToFrac :: (Real a, Fractional b) => a -> b  -- de Real (exclou nombres complexos) a Fraccionable (exclou enters)

fromRational :: Rational -> a                   -- des de racional
  • Des de Sencer (que implementa la classe Integral que defineix la divisió sencera)

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

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
  • Des de Real: són instància de Real els Float, Double, Int, IntN, Integer, Natural, Word, WordN, Racionals i els de Coma fixa.[101]
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).

# 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 constructor dels racionals
0.6666667

tipus algebraics[modifica | modifica el codi]

enumeracions[modifica | modifica el codi]

Unió de constructors d'aritat 0.

Cal afegir-hi la clàusula deriving perquè el compilador instancii les classes d'operacions bàsiques per al tipus definit.

 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 | modifica el codi]
data TRectangle = Rectangle Int Int -- (Int * Int) amb constructor Rectangle

El constructor equival a una funció que retorna un element del tipus a partir dels components. De fet a GHC (amb l'extensió GADTs) es pot escriure

{-# LANGUAGE GADTs #-}

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

Encaix dels paràmetres a partir del constructor

 perímetre :: TRectangle -> Int
 perímetre (Rectangle x y) = 2 * (x + y)
Registres[modifica | modifica el codi]

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 constructors es poden utilitzar sense els noms de camp, amb els components en l'ordre especificat a la definició.
  • 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.[102]
  • L'extensió NamedFieldPuns permet simplificar la duplicació per coincidència de nom de camp i variable {camp = camp} esmentant tan sols {camp} tant a l'encaix com a l'actualització.[103]
  • L'extensió RecordWildcards evita teclejar la resta dels camps a l'encaix i els incorpora amb nom de var. igual que el nom de camp com a (camp = camp,), si hi afegim una el·lipsi de dos punts: {camp1 = v1, ..}[104]
  • 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

Exemple:

 -- per evitar coŀlisions, 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

 -- actualització
 y = persona {pAlçada=1.72}

 -- encaix per nom -- ''p @ (Persona {pEdat = v_edat})'' es llegeix: ''p com (Persona quina pEdat és v_edat)''

 aniversari p @ Persona {pEdat = v_edat} =    -- el tipus de pEdat :: TPersona -> Int
   p { pEdat = v_edat +1}

 -- amb encaix simplificat {c} equival a {c = c} a GHC amb {-# LANGUAGE NamedFieldPuns #-}

 aniversari p @ Persona {pEdat} =   -- {pEdat} equival a {pEdat = pEdat},
                   -- aquí el pEdat visible és la variable del patró, en comptes de la funció accessora al camp
   p { pEdat = pEdat +1}

 -- ''NamedFieldPuns'' també desdobla {camp} com a {camp = camp} a l'actualització (dreta de la def.)
 estableixEdat pEdat p = p {pEdat}

Per la sintaxi simplificada a GHC vegeu[105]

Reversibilitat de tipus en registres d'un sol component[modifica | modifica el codi]

É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
Tuples[modifica | modifica el codi]

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

 ()     -- la tupla buida és un valor del tipus Unit (resultat buit en efectes coŀlaterals)
        -- i també designa el tipus esmentat.

 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 | modifica el codi]

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

 -- 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 | modifica el codi]

La següent construcció genera accessors que 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 #-}

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.
Pròximament l'extensió OverloadedRecordFields[42] farà innecessari la generació de propietats (getters/setters) per als accessors comuns en admetre'ls com a requeriment de context:[41]
{-# LANGUAGE OverloadedRecordFields #-}   -- pròximament
import Control.Function ((&))  -- (&) = flip ($)

data Punt2D = Punt2D {x,y :: Coord}
data Punt3D = Punt3D {x,y,z :: Coord}

-- operació per qualsevol registre que tingui un camp "x"
-- amb (HasField "x" ...) com a requeriment de context ja no caldrà definir Getters per a tots els camps
operacióAmbElCampX :: HasField "x" reg Coord => reg -> Coord   -- pròximament s/. l'ext. OverloadedRecordFields
operacióAmbElCampX reg = #x reg & algunaOpMés
tipus polimòrfics[modifica | modifica el codi]

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

 -- el tipus Maybe : opcionalitat de valor
 -- per a 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 per retornar el resultat o l'error
 data Either a b = Left a | Right b

 -- tipus recursius
 data Arbre a = Fulla a | Branca (Arbre a) (Arbre a)

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

àlies o sinònims de tipus[modifica | modifica el codi]

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

-- EBNF "type", nom_de_tipus, { " ", param}, "=", expressió_de_tipus.
type String = [Char]
type LaMevaEstructura = (String, Int, Double)
sinònims paramètrics[modifica | modifica el codi]

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

type LlistaNumerada a = [(Int, a)]

llistes[modifica | modifica el codi]

El tipus Llista amb constructors "[]" (llista buida, anomenat nil)[109] i ":" (tip_elem * llista_elem, anomenat cons)[109] Operacions al mòdul Data.List[110]

 data [a] = []  |  a : [a]  deriving (Eq, Ord)  -- el constructor ':' està definit ''infix''

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

 []    -- llista buida
 elem_cap : llista_cua   -- ':' és el constructor de llistes (en pos. ''infix'')

 [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 entre cometes revesses

 -- llistes infinites
 [1,3..] == [1, 3, 5, 7, 9, ...  
 iterate f x == [x, f x, f (f x), ...]   -- aplicació reiterada d'una funció
 repeat x               -- llista per repetició
 cycle xs               -- converteix la llista NO-BUIDA xs en circular -- funció parcial !!

Més operacions a contenidors

llista per comprensió[modifica | modifica el codi]

amb generadors i filtres separats per comes

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

 print $ take 5 s  -- imprimeix els primers
llista per comprensió monàdica[modifica | modifica el codi]

Vegeu-ho a Mònada_(programació_funcional)#Llista_per_comprensió_monàdica

llistes per comprensió a l'estil de SQL[modifica | modifica el codi]

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

tires de text[modifica | modifica el codi]

-- el tipus String (cat: Cadena) é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"[113]

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

Cadenes de text implementades amb vectors - El tipus Text[modifica | modifica el codi]

Més eficients en la lectura, i concatenació, menys en afegir caràcters d'un amb un (cons).

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

Estructura del tipus text:[115]

-- | 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 | modifica el codi]

L'extensió de llenguatge OverloadedStrings facilita que els literals de cadenes 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.[116]

{-# 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

Expressions[modifica | modifica el codi]

funcions[modifica | modifica el codi]

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 següents aparicions 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)

Expressions lambda com a funcions anònimes[modifica | modifica el codi]

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

\case { p1 -> e1; ...; pN -> eN }

-- equival a
\ x -> case x of { p1 -> e1; ...; pN -> eN }

Aplicació parcial[modifica | modifica el codi]

Vegeu ref.[118]

Una funció de dos paràmetres en càlcul lambda seria . En Haskell seria l'expressió

\x y -> E
-- equivalent a
\x -> \y -> E

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.

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

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

L'operador de la signatura (->) "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.

Funcions d'ordre superior[modifica | modifica el codi]

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

-- la funció com a paràmetre especifica la seva signatura entre parèntesis:
flip :: (a -> b -> c) -> b -> a -> c    -- (flip f) permet especificar els paràmetres actuals per a 'f' en ordre invers
flip f x y  =  f y x

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

Encaix del tipus d'una funció:

-- l'aplicació parcial del primer paràmetre, retorna una funció dels paràmetres restants
(a -> b -> c)  (a -> (b -> c))  -- per tant encaixaria com a tipus de funció (a -> r)
Secció (tall) d'un operador en infix[modifica | modifica el codi]

Expressió de l'operador en infix, mancada d'un dels dos operands, explicitant la manca envoltant-la de parèntesis. Vegeu ref.[120]

$ ghci
Prelude> :t (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

Patrons i guardes[modifica | modifica el codi]

casuística per encaix de patrons dels paràmetres[modifica | modifica el codi]

El nom de la funció precedeix cada cas.

 -- _ és el comodí
 llargada :: [a] -> Int
 llargada [] = 0            -- [] patró del constructor ''nil'' de llista buida.
 llargada (_:xs) = 1 + llargada xs    -- (_:_) patró del constructor ''cons'' com a (cap:cua)

!! GHC no avisa quan els encaixos són incomplets (no exhaustius),[121] 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

l'operador (@) - assignació del valor encaixat a una variable[modifica | modifica el codi]

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 | modifica el codi]

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

  f :: forall a. [a] -> (Int, [a])
  f xs = (n, zs)
    where
      (ys::[a], n) = (reverse xs, length xs) -- OK
      zs::[a] = xs ++ ys                     -- OK

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

Casuística de definició de funcions basada en condicions

 signe x
     | x > 0  = 1
     | x == 0 = 0
     | otherwise = -1
Definició per guardes d'encaix (ang:pattern guards)[modifica | modifica el codi]

Haskell2010. Condiciona el cos de la definició a una alternativa d'encaix que, en seqüència, simplifiquen l'especificació d'encaixos niuats.[123]

En comptes de

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 els encaixos subordinats en seqüència:

addLookup env var1 var2
   | Just val1 <- lookup env var1,
     Just val2 <- lookup env var2
       = val1 + val2
   | otherwise = cas_residual
Sinònims de patrons[modifica | modifica el codi]

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

  • 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 patrons amb valors
{-# LANGUAGE PatternSynonyms #-}

default (Int)

pattern Cap y <- y : _
pattern Cua ys <- _ : ys

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

main = print $ obtenirElCapDeLaCuaDeLaCua [1,2,3]
Patrons de vistes[modifica | modifica el codi]

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. També es pot fer servir en un case o en una definició amb let. Extensió ViewPatterns.[125]

  • 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 }
-- del codi de Data.Sequence

data ViewL a = EmptyL | (:<) a (Seq a)

viewl :: Seq a -> ViewL a
{-# LANGUAGE ViewPatterns #-}
import Data.Sequence

f :: Show a => Seq a -> String
f (viewl -> x :< xs) = "la seq. comença per " ++ show x
f _ = {- cas residual -} "la seq. és buida"  

-- equival a
f v
   | x :< xs <- viewl v = "la seq. comença per " ++ show x
   | otherwise = "la seq. és buida"

Definicions d'àmbit local[modifica | modifica el codi]

Vegeu "Let vs Where".[126]

amb where, estil declaratiu (ús abans de la definició) 
permet definir variables esmentades als patrons i guardes de la definició. Cada patró d'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)
estableix àmbits (let .. in) successius permetent redefinir un mateix símbol si és en àmbits diferents
-- ghci
Prelude>let x = 1; y = x in let x = y +1 in print x
2  -- resultat

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

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

Àmbit dinàmic - paràmetres implícits[modifica | modifica el codi]

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.[127]
{-# LANGUAGE ImplicitParams #-}

multiplicaAmbImplícit :: (?implícit :: Int) => Int -> Int
multiplicaAmbImplícit x = ?implícit * x

cridaIntermèdia :: (?implícit :: Int) => Int
cridaIntermèdia = multiplicaAmbImplícit 5

prova = let ?implícit = 2  -- espai local de la variable implícita (no apareix com a restricció de context)
        in cridaIntermèdia

main = print prova

Alternatives if, case[modifica | modifica el codi]

-- if del haskell98
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 coŀlaterals[modifica | modifica el codi]

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 = ...

Generació i tractament de seqüències[modifica | modifica el codi]

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 | modifica el codi]

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ó per obtenir opcionalment el següent
           llavor {-inicial-} -> 
           [item]
Seqüències - aplicació als elements d'una seqüència - La classe Functor[modifica | modifica el codi]

fmap de la classe Functor[128] permet l'aplicació d'una funció als elements de les seqüències que l'instancien.

class Functor t where
  fmap :: (a -> b) -> t a -> t b    -- aplica una funció als elements de la col·lecció ''t''
  ...
  • Els conjunts no l'instancien:
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,
  • per a tot morfisme i .
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.[129][130]
  • Tanmateix les implementacions de conjunts aporten la seva operació map particular, malgrat no implementar Functor.
  • Els tipus monomòrfics (Text, ByteString) tampoc. (El tipus ha de venir parametritzat amb el tipus de l'element)
  • El paquet mono-traversable aporta una solució a les col·leccions monomòrfiques o no, basada en #Famílies de tipus.
Seqüències - transformació d'una seqüència amb memòria[modifica | modifica el codi]

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')) -> 
                 llavor {-inicial-} -> 
                 [item] -> 
                 (llavor {-final-}, [item'])
Seqüències - reducció d'una seqüència a un valor o bé estructura (plegat) - La classe Foldable[modifica | modifica el codi]

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

foldl :: (llavor -> item -> llavor {-següent-}) -> 
         llavor {-inicial-} -> 
         [item] -> 
         llavor {-final-}

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

foldl de Data.List ha estat substituïda en el mòdul Prelude per foldl de Data.Foldable

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

  • La classe Foldable generalitza la reducció per a les col·leccions
class Foldable t where
  fold :: Monoid a => t a -> a  -- plegat per a coŀleccions d'elements que implementen Monoide
  foldMap :: Monoid m => (a -> m) -> t a -> m  -- plegat amb mapeig a un tipus que implementa Monoide
  foldr, foldr' :: (a -> b -> b) -> b -> t a -> b     -- plegat per la dreta, foldr' estricte
  foldl, foldl' :: (b -> a -> b) -> b -> t a -> b     -- plegat per l'esquerra, foldl' estricte
  ...
  -- plegats especials
  toList :: t a -> [a]  -- foldMap (\x -> [x])
  null :: t a -> Bool   -- existeix algun element
  length :: t a -> Int  -- compta els elements
  elem :: Eq a => a -> t a -> Bool -- existeix algun element igual que l'esmentat ?
  maximum, minimum :: forall a. Ord a => t a -> a  -- obté el superior/inferior per a tot element de la coŀlecció NO buida
  sum :: Num a => t a -> a  -- totalitza la suma dels elements
  product :: Num a => t a -> a -- totalitza el producte dels elements

Operadors[modifica | modifica el codi]

  • Associativitat per l'esq. / per la dreta / o no associatiu, especificant {infixl | infixr | infix}
  • Precedència (0..9). Vegeu taula.[65]

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 | modifica el codi]

Vegeu ref.[119]

(.) 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.[66]
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 | modifica el codi]

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 (&) definit a Data.Function desde GHC 7.10.1

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

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

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

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

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

Currificació[modifica | modifica el codi]

  • 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 | modifica el codi]

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

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[133] 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 | modifica el codi]

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

Especialització, instanciació per tipus[modifica | modifica el codi]

L'especificació de requeriment de classes base en una definició de classe, propaga el "requeriment de visibilitat d'instàncies de les classes base en el context d'ús" a les signatures dels mètodes de la classe definida.

-- per aquells tipus t que implementin les classes ''Base''
class (ClasseBaseA t, ClasseBaseB t, ...) => ClasseDerivada t where ...
{-# LANGUAGE NamedFieldPuns #-}  -- simplificació en literals de registres (interpreta 'camp' 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 | modifica el codi]

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

Clàusules deriving independents per la derivació d'instàncies amb requeriments[modifica | modifica el codi]

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

{-# LANGUAGE StandaloneDeriving #-}

data Foo a = Bar a | Baz String

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

newtype -- tipus derivats[modifica | modifica el codi]

newtype defineix un tipus amb la mateixa representació interna que el tipus base (el compilador esborra el constructor).[137]

-- alternatives de tipus derivats
data Foo1 = Foo1 Int    -- defineix el tipus Foo1 amb una referència d'avaluació tardana (''lazy'') a un Int
data Foo2 = Foo2 !Int   -- defineix el tipus Foo2 amb una referència d'avaluació estricta a un Int

-- defineix el tipus Foo3 amb un component de valor Int desempaquetat en el constructor (UNPACK evita un nivell d'indirecció)
data Foo3 = Foo3 {-# UNPACK #-} !Int  -- a partir de GHC 7.8 UNPACK ve per defecte per als comp. estrictes de mida petita.

-- defineix el tipus Foo4 amb la mateixa representació dels Int
newtype Foo4 = Foo4 Int

newtype permet fer abstracció d'un tipus preexistent mitjançant un nou tipus, sobre el qual cal explicitar a la clàusula deriving les operacions que es vulgui heretar. L'extensió GeneralizedNewtypeDeriving (propera secció) permet ampliar les instàncies heretables més enllà de les especificades per l'H98: (Eq, Ord, Enum i Bounded).

newtype defineix la nova abstracció mitjançant una estructura d'un sol component, amb possible forma de registre.

Queda definida una relació entre ambdós tipus. El constructor del registre constitueix una aplicació del domini del tipus del component al nou, i la funció accessora (nom del camp) constitueix l'aplicació inversa com es mostra a l'exemple.

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

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

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

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

tail :: TLlistaNoBuida a -> [a]
tail = Prelude.tail. obtenirLlista

-----------------------------------
obtenirLaLlistaMenor :: Ord a => [a] -> [a] -> Maybe (TLlistaNoBuida a)
obtenirLaLlistaMenor l1 l2 = do
         nb1 <- validaLlistaNoBuida l1
         nb2 <- validaLlistaNoBuida l2
         if nb1 < nb2 then return nb1 else return nb2

default (Int) -- tipus per defecte dels literals

main = do
         case obtenirLaLlistaMenor [1..3] [2..4] of
           Just llistaNoBuida -> (putStrLn. show) llistaNoBuida
           Nothing -> putStrLn "alguna de les llistes era buida"

ús dels newtypes predefinits[modifica | modifica el codi]

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 coŀleccions basats en propietats.

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

import Data.Function ((&))        -- (&) = flip ($)
import Control.Category ((>>>))   -- f >>> g = g. f

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

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

mostra = [1,2,3]

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

etiquetar tipus bàsics - GeneralizedNewtypeDeriving[modifica | modifica el codi]

  • newtype es pot utilitzar per diferenciar valors de tipus primitius, per evitar l'error de transposició de paràmetres del mateix tipus (intercanviar-ne l'ordre per equivocació).
  • també per redefinir les implementacions de classes (instàncies), cal fer-ho sobre un tipus derivat.[134]

Haskell98 permet heretar instàncies de les classes Eq, Ord, Enum i Bounded en un newtype esmentant-les a la clàusula deriving.[138]

Per evitar reinstanciar les altres classes, l'extensió GeneralizedNewtypeDeriving permet heretar-les afegint-les a la clàusula deriving. Però hi ha particularitats quan es vol estendre el mecanisme a les classes multiparàmetre. Vegeu la ref.[138]

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- fent que el compilador discrimini diferents elements que es poden comptar

newtype TPomes = Pomes Int deriving (Eq, Show, Ord, Num)
newtype TPeres = Peres Int deriving (Eq, Show, Ord, Num)

valor = Pomes 2 + Peres 3   -- sumem TPomes i TPeres, error de tipus !!

dóna l'error:

  Couldn't match expected type `TPomes'
      against inferred type `TPeres'

Vegeu també

  • "Keyword arguments in Haskell"[139] (cat: "pas de paràmetres etiquetat per evitar errors de transposició de paràmetres")
  • DrIFT[140] Preprocessador de Haskell que automatitza la instanciació de classes no suportades pels compiladors a la clàusula "Deriving"
  • Data.Derive[141] Alternativa a DrIFT amb alguns avantatges. Manual.[142]

Exemple de redefinició d'instàncies (sobre tipus derivats amb newtype)[modifica | modifica el codi]

Redefinició de Monoide per a la suma i el producte.

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 pel H98 (Eq, Ord, Enum, Bounded).[138]

-- Del codi font de Data.Monoid

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Data.Coerce (coerce)  -- coerció segura. Vegeu https://wiki.haskell.org/GHC/Coercible

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

instance Num a => Monoid (Sum a) where 
        mempty = Sum 0
        mappend = coerce ((+) :: a -> a -> a)
--        Sum x `mappend` Sum y = Sum (x + y)

-- | 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
        mempty = Product 1
        mappend = coerce ((*) :: a -> a -> a)
--        Product x `mappend` Product y = Product (x * y)

tipus derivats de funcions[modifica | modifica el codi]

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 | modifica el codi]

Tipus existencial[modifica | modifica el codi]

É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 components dels elements amb les operacions que comparteixen.

Vegeu[143] [144] 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.[145][146]
  • Altres compiladors de Haskell fan servir la paraula reservada exists com a quantificador existencial, el EHC/UHC segons la ref.,[147] i el JHC també,[148] en comptes del forall del GHC.
 -- la variable del quantificador no ha d'aparèixer a l'esquerra del '='
 data T = forall a. ConstructorT { v::a, f:: (a->Int)}

 unValorIntT = ConstructorT { v = 1::Int, f = identitat}
 unValorFloatT = ConstructorT { v = 1.5::Float, f = part_sencera}

 -- afegint-li requeriments
 data T = forall a. (Ord a, Bounded a) => ConstructorT { v::a, f:: (a->Int)}

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

 {-# LANGUAGE ExistentialQuantification #-}
 data TObj = forall a. (Show a) => Obj a

 unValorNumèric = Obj 1
 unValorString = Obj "abc"

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

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

Admet restriccions existencials, a més d'altres possibilitats.[108] 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 | modifica el codi]

Vegeu.[150] 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 TObj = forall a. (Show a) => Obj a

 xs :: [TObj]
 xs = [Obj 1, Obj "foo", Obj 'c']

 mostra :: [TObj] -> String
 mostra [] = ""
 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

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 | modifica el codi]

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

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

{-# LANGUAGE ViewPatterns, ScopedTypeVariables #-}

import Data.Dynamic

-- control dinàmic del tipus del 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)

dóna

$ 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

Efectes[modifica | modifica el codi]

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.

  • efecte simple: quan sempre hi ha resultat (representable amb (Efecte a)).
  • efecte fallada: quan no sempre s'obté resultat (mònades que implementen MonadZero[152], 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. (representable amb una llista: ([] a)). Vegeu la classe MonadPlus.

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 : resultat opcional de funcions parcialment definides, evitant les crides a error i la consegüent petada.
  • Either err a: resultat opcional de funcions parcialment definides amb informació de l'error.

Combinació o bé encadenament de resultats dels efectes[modifica | modifica el codi]

  • La classe Applicative permet combinar els resultats d'una seqüència d'efectes amb una funció o bé un constructor.
  • La classe Mònada preveu encadenar un efecte amb una funció efecte sobre el resultat precedent (>>=), o bé amb un altre efecte ignorant el resultat de l'anterior (>>). Vegeu Mònada (programació funcional)
  • La classe Arrow (cat:Fletxa) generalitza les mònades prenent les funcions amb efecte de la mònada com a morfismes, afegint el tipus de l'entrada com a paràmetre al tipus, modelant la seqüència d'efectes com un encadenament de morfismes sobre els resultats. Per efectuar càlculs amb els resultats de dos efectes morfismes, caldrà aparellar-ne els resultats amb el tractament de parells que les fletxes inclouen. Vegeu Fletxa (programació funcional)

Generació d'una dada efecte partint d'un valor interpretable com a resultat[modifica | modifica el codi]

  • A la classe Applicative (mòdul Control.Applicative) "pure" eleva un valor a la categoria d'efecte aplicatiu (que implementa Applicative).
pure :: tipResultat -> efecte tipResultat

-- exemple
instance Applicative Maybe where
  pure = Just 
  ...
  • A la classe Mònada (mòdul Control.Monad) "return" eleva un valor a la categoria d'efecte monàdic (que implementa Monad).
return :: tipResultat -> efecte tipResultat

Actualment la classe Applicative és base per a la classe Monad, i return = pure

  • A la classe Arrow (mòdul Control.Arrow) "arr" eleva una funció a la categoria d'efecte fletxa (morfisme).
arr :: (tipEntrada -> tipResultat) -> efecte tipEntrada tipResultat

Sequenciació d'efectes[modifica | modifica el codi]

Per repetir un seguit d'efectes de manera que s'obtingui el mateix resultat, cal establir la seqüència de les operacions.

  • La classe Applicative (mòdul Control.Applicative) permet combinar el resultat d'un efecte amb una funció resultant de l'efecte precedent. Per combinar diversos efectes s'eleva a la categoria d'efecte una funció de tants paràmetres com el nombre d'efectes a combinar.
class Applicative efecte where
  -- (<*>) combina el resultat del segon efecte amb la funció resultat del primer
  (<*>) :: efecte (a -> b) -> efecte a -> efecte b  

-- 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
  • La classe Mònada (mòdul Control.Monad) encadena un efecte amb una funció efecte sobre el resultat de l'efecte precedent.
class Monad efecte where
  (>>=) :: 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

-- (>>) es pot expressar en termes de (>>=)
    m_x >> m_y  =  m_x >>= (const m_y)  -- const és la funció constant (const x _ = x)

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

Vegeu també Combinadors aplicatius partint de mònades

Encadenament de morfismes:

   -- 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)

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

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

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 (morfisme) 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.[156]

Compiladors que suporten Fletxes: GHC,[157] Hugs[158]

Entrada / Sortida - Mònades[modifica | modifica el codi]

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 (), element neutre dels efectes, 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) :: IO Int

 -- fail missatge  -- cridada internament, cas que els encaixos del segon paràmetre de (>>=) no siguin exhaustius.
 -- des de GHC 8.0 la funció  'fail' se separa de la classe Monad i va a la classe MonadFail que IO instancia.

Blocs Do: notació especial de les expressions monàdiques[modifica | modifica el codi]

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.

-- exemple de bloc 'do'
bloc = do
    x <- getLine     -- (<-) separa sintàcticament el resultat de l'efecte que el produeix
    putStrLn x
    putStrLn "Fet"
    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 posteriors
                  putStrLn x >>  
                  putStrLn "Fet" >>
                  return x
                  ) -- el parèntesi només és per explicitar que l'abast d'una lambda és el màxim
Clàusula let dins el bloc do[modifica | modifica el codi]

És similar al let..in de les expressions, però inserit en la seqüència. Les seves definicions sobre expressions pures (sense efectes) són visibles per als efectes posteriors i poden referir-se a resultats dels efectes precedents en el bloc do.

main = do
          print "Entreu un mot: "
          x <- getLine
          print "Entreu-ne un altre: "
          y <- getLine

          -- subbloc ''let'' de definicions pures (sense efectes) sobre resultats d'efectes precedents
          let { z = x ++ y; w = z ++ "." }

          -- equivalent amb sagnat
          let z = x ++ y
              w = z ++ "."  -- cal alinear el bloc a la primera variable definida

          putStrLn ("La concatenació és: " ++ show w)

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 | modifica el codi]

Permet l'avaluació seqüencial dels elements d'una coŀlecció travessable d'esquerra a dreta si són efectes o bé de les imatges del mapeig dels elements amb una funció d'efectes. Vegeu el mòdul Data.Traversable.[159]

  • Traversable i efectes aplicatius
class (Functor t, Foldable t) => Traversable t
  ...
  -- 'sequenceA' avalua seqüencialment els elements d'una coŀlecció d'efectes aplicatius (que implementen Applicative)
  sequenceA :: Applicative efecte => t (efecte a) -> efecte (t a) 

  -- 'traverse' avalua seqüencialment les imatges d'aplicar una funció d'efectes aplicatius als elements d'una coŀlecció
  traverse :: Applicative efecte => (a -> efecte b) -> t a -> efecte (t b)
  • Traversable i efectes monàdics
class (Functor t, Foldable t) => Traversable t
  ...
  -- 'sequence' avalua seqüencialment els elements d'una coŀlecció d'efectes monàdics (que implementen Monad)
  sequence :: Monad efecte => t (efecte a) -> efecte (t a)

  -- 'mapM' avalua seqüencialment les imatges d'aplicar una funció d'efectes monàdics als elements d'una coŀlecció
  mapM :: Monad efecte => (a -> efecte b) -> t a -> efecte (t b)
  • sequence i mapM també es poden obtenir del mòdul Control.Monad on estaven definits originalment.[160]
  • forM equival a mapM amb els paràmetres intercanviats.
  • hi ha versions amb un guió baix al final per descartar resultats quan no interessen (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 línies 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. 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.

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
        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 | modifica el codi]

Per posar més d'una línia d'instruccions 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.[161]

Recursivitat en els blocs do[modifica | modifica el codi]

Vegeu Mònada_(programació_funcional)#Recursivitat_en_els_blocs_do

excepcions[modifica | modifica el codi]

  • 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[162]
  • 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 però no les IOException. També inclou un transformador per al tractament encapsulable en codi funcional pur.[163]
  • La biblio safe-exceptions augmenta la millora la biblio exceptions amb el tractament d'excepcions asíncrones i està integrada al paquet classy-prelude (Prelude alternatiu, sense funcions parcials, de la plataforma web yesod).[164]

La funció evaluate de Control.Exception avalua una "expressió funcional pura llançadora d'excepcions" com a efecte (el llançament d'excepcions invalida la continuació) en la mònada IO (efectes superficials), avaluant l'expressió a WHNF (#Weak Head Normal Form).[165]

evaluate :: a -> IO a

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

  • 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 es recomana catch o bé catches.

Generadors d'excepcions[modifica | modifica el codi]

  • 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, cal consultar-ne 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àmetreIŀlegal String | EUnaAltraExcepció String
     deriving (Show, Typeable)

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

Llançadors[modifica | modifica el codi]

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

excepcions en la gestió de recursos[modifica | modifica el codi]

bracket[modifica | modifica el codi]

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 la primera part es passa com a paràmetre a les altres dues.
 bracket :: IO a ->    -- (adquisició de recurs), ex.: (openFile nom_fitxer mode) :: IO Handle
      (a -> IO b) ->  -- (alliberament del recurs), per ex.: hclose :: Handle -> IO ()
      (a -> IO c) ->  -- (computació amb el recurs), 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)
try - per caçar excepcions síncrones[modifica | modifica el codi]

try avalua un efecte IO que pot llançar excepcions retornant èxit o fallada en un tipus Either. (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), dóna:

correcte: Infinity

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

finally[modifica | modifica el codi]

Assegura l'exec. de la segona part malgrat excepcions en la primera.[168]

onException
com finally però només executa el segon bloc en cas d'excepció, que és rellançada en acabar.
finally :: IO a -> IO b -> IO a
onException :: IO a -> IO b -> IO a

-- es fa servir habitualment en posició ''infix''
([try] computació) `finally|onException` (coses_a_fer_en_acabar)

catch -- per caçar excepcions (especialment les asíncrones)[modifica | modifica el codi]

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

-- excepcions del Haskell98.        
{-# LANGUAGE CPP #-}  -- codi CPP

#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'' avalua com a efecte IO una expr. funcional 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[170]

  • Gestors d'excepció segons el tipus. Requereix l'extensió de llenguatge ScopedTypeVariables.
-- excepcions del Haskell2010

{-# 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

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

-- el model d'aplicació successiva de 'catch's té una pega: 
--   les excep. llançades des dels handlers poden ser caçades pels 'catch's posteriors

f = acció `catch` (\ (excep :: ArithException) -> tractaExcepció_Arith 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'

-- per això es proposa una altra funció: 'catches' que no captura les excepcions llançades dins els handlers
f = acció `catches` [Handler (\ (excep :: ArithException) -> tractaExcepció_Arith excep),
                     Handler (\ (excep :: IOException)    -> tractaExcepció_IO excep),
                     Handler (\ (excep :: SomeException)  -> tractaExcepció_Altres excep)
                     ]

Vegeu catches.[171]

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

La funció error assenyala la fallada del programa[modifica | modifica el codi]

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 atura el programa i mostra el missatge.

error missatge   -- és millor evitar-ne l'ús en funcions parcials xq no dóna cap pista de la crida culpable de l'error.
  • 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.[172]
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 la funció error ha estat reanomenada errorWithoutStackStrace en espera d'una revisió més a fons.

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,[173]
  • o bé amb la crida err equivalent a error del paquet file-location[174] 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ó
import "file-location" FileLocation (err')    -- Obsolet a partir de GHC 8.0, doncs ''error'' ja mostra la situació

-- 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!"  -- $() avalua en temps de compilació

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 | modifica el codi]

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

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

assert condició expr --
     -- si Fals, genera 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 = ...
  • Millora retornant l'error en un Either
{-# LANGUAGE DeriveDataTypeable #-}
import Control.Exception (assert)
import Data.Typeable

data ElMeuError = ElMeuError String   
                                deriving (Typeable, Show)

instance Exception ElMeuError  -- implementa la classe Exception

funcióParcial :: a -> b -> Either ElMeuError c
funcióParcial p1 p2     
    | precondició p1 p2 = assert (postcondició p1 p2 resultat) $ Right resultat
    | otherwise         = Left $ ElMeuError "funcióParcial: la precond. falla"
  where
    resultat = ...
obtenció del punt de crida origen de la violació de la precondició[modifica | modifica el codi]

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

Vegeu ref.[176] (Des de GHC 7.10.2[177]) 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.[178]

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

Exemple:

{-# LANGUAGE CPP #-}

#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" -- la impressió del CallStack "?callStack" és automàtica a GHC >= 8.0
  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 ⊥[modifica | modifica el codi]

Valor intern que atura el programa prematurament.[179] Per exemple l'última opció d'un case és sempre ⊥, que encalla el programa quan els encaixos no són exhaustius i no hi ha cap encaix vàlid per al terme encaixat.

En anglès s'anomena "bottom" (com a verb: fondejar, embarrancar, encallar!).

  • En català es podria anomenar "fondeig" (amarrar una barca al fons en una cala, immobilitzant-la).
Implementació pendent - Undefined[modifica | modifica el codi]

És un valor de fondeig (⊥) per poder explicitar el tipus d'una funció pendent d'implementar i poder així passar la compilació. Crida a la funció error.

identificador patrons = undefined :: tipus

Canvis d'estat - Variables[modifica | modifica el codi]

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 és efímer (limitat a l'àmbit de l'efecte ST).

Caldrà distingir el tipus de la mònada especificant el tipus resultant de l'expressió o bé 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).
  • 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[180] IO newIORef x Data.IORef vars. globals no sincronitzades,
cicle de vida no acotat
STRef a[181] ST newSTRef x Data.STRef vars. per a canvis d'estat encapsulables,
cicle de vida lligat a l'àmbit de ST
MVar a[182] IO newMVar x
-- var buida per a un tipus T
newEmptyMVar :: IO (MVar T)
Control.Concurrent.MVar vars. globals sincronitzades amb monitor
que es buiden en consultar-les, també serveixen com a
* bústia de comunicació per baldes (ang:locks)
* semàfor binari
TVar a[183] 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[184] 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 | modifica el codi]

Les referències IORef,[185][186] 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[187] (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ó

     if x then putStrLn "Blanc"
          else putStrLn "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

encapsulament d'efectes col·laterals (amb variables d'àmbit local STRef)[modifica | modifica el codi]

La mònada ST[188][189] (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[190] a objectes de cicle de vida restringit a l'àmbit i avaluació de l'efecte.

en memòria local[modifica | modifica el codi]
  • Els efectes locals produïts en un fil d'execució, venen parametritzats amb una variable (de nom 's') (l'espai d'avaluació de l'efecte), que es deixa lliure per indicar l'espai propi del fil d'execució. (ST s tipusDelResultat)
  • 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 | modifica el codi]
  • 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.[191]
 -- 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

accés sincronitzat amb variables MVar (Mailbox variable)[modifica | modifica el codi]

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

vectors mudables[modifica | modifica el codi]

Vegeu vectors d'elements d'allotjament directe

Entrada/Sortida i avaluació tardana - Els Iterats (ang:Iteratees)[modifica | modifica el codi]

A les funcions que utilitzen bracket, l'avaluació tardana del resultat pot causar el tancament del recurs abans d'hora.[192]

withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
withFile name mode = bracket (openFile name mode) hClose   -- aplicació parcial de 'bracket'

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

-- imprimeix nombre de línies llegides
incorrecte = do
    dades <- withFile "test.txt" ReadMode hGetContents
    putStrLn $ show $ length $ lines dades

El fitxer es tanca abans que putStrLn n'obtingui les dades. El resultat és la sortida buida. El compilador no avisa de l'error !!

La versió correcta és ficar el consumidor dins el bloc del withFile.

correcte = do
    nombreDeLínies <- withFile "test.txt" ReadMode $ \hd -> do
        dades <- hGetContents hd
        return $ length $ lines dades
    putStrLn $ show nombreDeLínies

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

  • 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. A cada iteració es reacciona a l'entrada de l'element (o bé troç de fitxer (ang:chunk)) com si fos un plegat (reducció), actualitzant l'estat i combinant l'element (o bé el troç de fitxer), 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 i fan 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[193]
En anglès, donat un verb afegint-li el sufix -er tenim un nom subjecte d'una acció com ara del verb employ el substantiu Employer.
El sufix -ee obté de l'acció un subjecte passiu, així el parell (Employer, Employee) es podria traduir per (Contractador, Contractat)
Així els termes tècnics {Iteratee, Enumeratee} en català serien {Iterat, Enumerat}.

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

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

Mòduls[modifica | modifica el codi]

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.

Els mòduls a importar s'interpreten com a noms de fitxer sense extensió, els compostos per parts separades per un punt com a camí CarpetaA.CarpetaB.NomDelFitxer.

El compilador cerca per cada nom de mòdul a importar que no estigui a la seva BDD, un fitxer del mateix nom amb una de les possibles extensions del Haskell (.hs, .lhs) o també dels preprocessadors habituals (de lèxic o de gramàtica o altres) que generaran el fitxer del mòdul.

encapsulament[modifica | modifica el codi]

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òdulX {- reexporta el contingut d'aquest mòdul -}
  ) where

import MòdulA (A,B,C) as MòdulX  -- detalla elements que volem reexportar com a MòdulX.

-- 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
...

Per exportar operadors constructors de tipus i no confondre'ls amb un operador funció, 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.[196]

{-# LANGUAGE ExplicitNamespaces #-}

module N( 
  f, 
  type (++)    -- amb 'type' exporta el constructor de tipus (++) definit i no la funció (++) de concat. de 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 | modifica el codi]

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 adjuntar-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
  TipusDeRegistre( campA, campB),           -- només publica els esmentats

  Classe4(..),                              -- classe amb accés públic a tots els mètodes
  UnaAltraClasse( mètodeA, mètodeB),        -- els mètodes no publicats només son accessibles en el mòdul

  ) where
...

Importació[modifica | modifica el codi]

 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 mencionats
 import qualified MòdulB as B  -- ''qualified'': qualificació obligatòria: B.ident
 import MòdulB as B [hiding (...] -- ''as B'': qualificació opcional per als identificadors d'origen ambigu
Importació d'instàncies (implementacions de classes)[modifica | modifica el codi]
 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.[197]
Importacions especificant el paquet desitjat[modifica | modifica el codi]

Cal esmentar la pragma {-# 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 | modifica el codi]

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
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 | modifica el codi]

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") reflexa 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 (.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[198]

És corrent reanomenar els noms jeràrquics a un nom curt

 import qualified Control.Monad.ST.Strict as ST

mòduls amb referències mútues (mútuament recursius)[modifica | modifica el codi]

Vegeu-ho a Compilador Haskell de Glasgow#Mòduls amb referències mútues (mútuament recursius)

arrencada[modifica | modifica el codi]

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 a retornar

            (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[199] en cas d'haver d'afegir, tot seguit, paràmetres pel programa

arrencada amb opcions[modifica | modifica el codi]

Vegeu-ho a GHC.

funcions i tipus predefinits i biblioteques bàsiques[modifica | modifica el codi]

El mòdul de funcions predefinides es coneix com a "Prelude". Vegeu Prelude estàndard.[200][201] Prelude del GHC.[202]

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

Podeu consultar a quins mòduls pot pertànyer un identificador mitjançant els cercadors de l'API del Haskell Hayoo o bé Hoogle

Precisió dels tipus bàsics[modifica | modifica el codi]

Vegeu-ho a GHC#Precisió dels tipus bàsics

Vectors[modifica | modifica el codi]

Vectors per interfície[modifica | modifica el codi]

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

Immutables, per implementació i allotjament dels elements[modifica | modifica el codi]

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 | modifica el codi]

Ineficients en espai.

data Array idx elem  -- Elements allotjats indirectament (encapsulats en memòria dinàmica, avaluació tardana)[210]
data UArray idx elem -- Amb U de Unboxed: elements no encapsulats, allotjats directament, avaluació estricta.[211]
-- 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)
nou_v = vec // [(indx, nou_valor)]   -- còpia completa
amb actualització per aplicació de diferències. DiffArrays[modifica | modifica el codi]
  • Vegeu DiffArrays.[212] 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 modificacions aplicatives hagi sofert.[213]
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 ''nou_v'' serà més ràpid que al del seu antecessor ''vec''
nou_v = vec // [(indx, nou_valor)]

Mudables, per implementació i allotjament[modifica | modifica el codi]

  • Vectors d'accés aleatori com a efecte IO (objectes amb cicle de vida no acotat), en memòria global.[214]
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 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).[215]
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 | modifica el codi]

La classe Array combinada amb la Ix (índexs), especificades en el Haskell98, implementen vectors immutables amb elements d'avaluació tardana.[74]

 -- 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)
 nou_v = 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 | modifica el codi]

Caldrà que l'enumerat implementi la classe dels índexs Ix (A GHC: al mòdul Data.Ix)[216]

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.[217][218] Entre aquests vegeu més avall #Vectors mudables de dades no encapsulades.

Vectors mudables de dades no encapsulades[modifica | modifica el codi]

Amb allotjament directe (no encapsulat) dels elements.

vect. mudables amb cicle de vida lligat a l'àmbit (efecte encapsulable ST)[modifica | modifica el codi]
  • 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 | modifica el codi]
  • 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

Estructures TAD genèriques - Classes multiparàmetre[modifica | modifica el codi]

L'equivalent dels mòduls genèrics d'altres llenguatges (paramètrics en tipus), es concreta afegint variables de tipus a les classes com a paràmetres o bé com a tipus associats (famílies de tipus).

Els paràmetres de tipus que depenen d'altres paràmetres es poden expressar mitjançant

  • o bé (estil antic) amb dependències funcionals.
  • o bé (estil modern) amb els tipus dependents com a àlies de tipus paramètrics (funcions de tipus dels paràmetres de tipus independents dels quals depèn).

Amb dependències funcionals[modifica | modifica el codi]

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

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 una sola instància per cada ocurrència del grup determinador.

Exemple:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-}
import Data.Function ((&))  -- (&) = flip ($) -- infixl 1  -- desde GHC 7.10.1

class Col_leccio tip_col tip_elem | tip_col -> tip_elem where
  nova_col :: tip_col
  inserta :: tip_elem -> tip_col -> tip_col
  és_membre :: tip_elem -> tip_col -> Bool

-- per a tip_col = [tip_elem]

instance Eq tip_elem => Col_leccio [tip_elem] tip_elem where
  nova_col     = []
  inserta elt xs = elt : xs
  és_membre elt []  = False
  és_membre elt (x:xs)
    | elt == x  = True         -- tip_elem requereix Eq
    | otherwise  = és_membre elt xs

main = do
     let v1 = (nova_col::[Int]) & inserta 1 & inserta 2
     print $ és_membre 2 v1
     print $ és_membre 5 v1
--

Paràmetres dependents com a funció de tipus - Àlies de tipus associats a la classe[modifica | modifica el codi]

Des de GHC 6.10.1. els paràmetres de tipus de la classe que siguin dependents, es poden especificar com una funció de tipus dels paràmetres independents, mitjançant una definició d'àlies de tipus (clàusula type) anomenada àlies de tipus associat a la classe. Cal l'extensió de llenguatge TypeFamilies.[220]

De l'exemple anterior, el tipus tip_elem determinat funcionalment per tip_col, passa a especificar-se mitjançant una funció de tipus amb forma d'àlies paramètric type Element tip_col, i el tipus concret s'especifica a la instància, per encaix de tip_col.

{-# LANGUAGE TypeFamilies #-}
import Data.Function ((&))  -- (&) = flip ($) -- infixl 1  -- desde GHC 7.10.1

class Col_leccio tip_col where
  type Element tip_col   -- ''àlies de tipus'' associat, indexat al param. de la classe
              -- Element és una funció de tipus de tip_col
              -- és abstracte (sense def. "= ..."); pendent de concretar a la instància.
  nova_col :: tip_col
  inserta :: Element tip_col -> tip_col -> tip_col
  és_membre  :: Element tip_col -> tip_col -> Bool

-- per a tip_col = [tip_elem]

instance Eq tip_elem => Col_leccio [tip_elem] where
                                         -- instància de (Element tip_col)
  type Element [tip_elem] = tip_elem     -- l'encaix del tipus facilita la definició

  nova_col = []
  inserta elt xs = elt : xs
  és_membre elt []  = False
  és_membre elt (x:xs)
    | elt == x  = True
    | otherwise  = és_membre elt xs

main = do
  let v1 = (nova_col :: [Float])
           & inserta 2
           & inserta 1.5

  print $ és_membre 2 v1
  print $ és_membre 5 v1
  --

Paraŀlelisme amb els functors de ML[modifica | modifica el codi]

Paraŀlelisme 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

ok = 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)

ok2 = conj_buit & afegeixLlista [1,2]
                & llistar

main = do
        print ok
        print ok2

Temes avançats[modifica | modifica el codi]

Monomorfisme, polimorfisme i quantificadors explícits[modifica | modifica el codi]

Vegeu ref.[221]

polimorfisme universal[modifica | modifica el codi]

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

 -- 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)

Vegeu també

  • Rank-2 types, ($) and the Monomorphism restriction[66]
  • Extensió {-# LANGUAGE NoMonomorphismRestriction #-}[224]
  • Des de GHC 7.2 Monomorfisme als lligams locals (let|where), excepte que s'expliciti la generalització.[225]

Tipus de Dades Algebraics Generalitzats (GADTs)[modifica | modifica el codi]

Permet diversificar el tipus de dades retornat pels constructors en tipus polimòrfics (amb variables de tipus). Té aplicació en els llenguatges incrustats per a camps d'aplicació específics, anomenats DSL (Domain Specific Language). Requereix l'extensió de llenguatge 'GADTs'.

Per això s'especifica la signatura de cada constructor com una funció dels components, especificant el tipus resultant. Vegeu[226]

  • 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 tipus (Terme Int)
  Successor :: Terme Int -> Terme Int
  EsZero   :: Terme Int -> Terme Bool             -- retorna un tipus diferent
  SiCondició :: Terme Bool -> Terme a -> Terme a -> Terme a  -- retorna el tipus paramètric
  Parell   :: Terme a -> Terme b -> Terme (a,b)      -- retorna un tipus diferent

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 | modifica el codi]

É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.[227]

{-# 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 evitar la ''memoització''
--   de les crides sense paràmetres en el codi funcional

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

Vegeu Compilador Haskell de Glasgow#Tractament d'un entorn canviant, amb la mònada Reader

Classificacions dels tipus[modifica | modifica el codi]

GHC utilitza la següent nomenclatura:[229][230]

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
Un tipus és Lifted si conté fondeig (⊥) (ang:bottom,[179] encallable, non-terminating) entre els seus elements. El tipus Lifted és el dels objectes de codi avaluables per encaix, on el valor (⊥) atura el programa si els encaixos són incomplets (no exhaustius).[230]
  • 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.[230]
  • Només els tipus encapsulats (boxed) poden ser Lifted. Però hi ha tipus boxed Unlifted com ara ByteArray#.[230]
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 | modifica el codi]

El kind (cat:mena) és una mena de tipus per als constructors de tipus, o, dit d'altra manera, un tipus d'ordre superior.[231]

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#).[232][233]
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ó (ordre superior)

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".[234] 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.[235]

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

{-# 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 | modifica el codi]

kind que defineix el conjunt de tipus per les restriccions de context que han de complir.

Hi ha 3 menes de restriccions:[236]

  • 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)[237] -- 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.[238]

{- 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 | modifica el codi]

Són una generalització dels tipus associats a les classes,[239]

Famílies de tipus estructurals[modifica | modifica el codi]

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.[240] 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 = XCons Char (XList Char) | XNil
                           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 tipus per encaix[modifica | modifica el codi]

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 Foldable (per la reducció) i Traversable (per al mapeig amb una funció d'efectes) que en depenen.
El paquet mono-traversable hi aporta una solució basada en famílies de tipus, que permet un tractament universal de les col·leccions, siguin o no monomòrfiques.[241]
{-# LANGUAGE TypeFamilies #-}
type family Element t                       -- família de tipus Element de 't'
type instance Element Text = Char           -- instància de tipus Element de '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    -- mapeig en un contenidor monomòrfic o no

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 -> (Element t, t)

-- instancia una Pila basada en llista
instance Pila [a] where
  type Element [a] = a    -- instància de la família de tipus
  buida = []
  apila = (:)  -- cons
  ésBuida = null
  daltIDesapila (x : resta) = (x, resta)
  daltIDesapila [] = error "daltIDesapila: la llista era buida!"

Famílies de tipus tancades[modifica | modifica el codi]

Una família de tipus amb un conjunt tancat (no ampliable) d'instàncies es pot expressar amb una clàusula type family ... where .[242]

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

tipus dependents de valors[modifica | modifica el codi]

Vegeu ref.[243] Exemple a Compilador_Haskell_de_Glasgow#Tipus_dependents_de_valors

Afinant el rendiment[modifica | modifica el codi]

Modes d'avaluació d'expressions[modifica | modifica el codi]

En Forma Normal tots els elements estan completament avaluats

Els termes Head Normal Form i Weak Head Normal Form són normalitzacions d'expressions del càlcul lambda.

En català està explicat al llibre pdf "Introducció a la programació funcional" de la Universitat de Girona.[244]

En anglès, la definició del càlcul lambda segueix el document original d'Alonzo Church.

Terminologia del càlcul lambda (en notació Haskell):

  • E1 E2: aplicació de l'expressió E1 sobre el terme E2. L'aplicació és associativa per l'esquerra (E1 E2 E3) ≡ (E1 E2) E3
  • \ x -> E: abstracció lambda, quin cos pot contenir la variable lligada 'x' i altres de lliures. La var. lligada es refereix a l'argument de la lambda de var. coincident més pròxima que tapa el nom d'altres lambdes de nom coincident. L'abstracció lambda és associativa per la dreta.
(\ x -> \ y -> E)  (\ x -> (\ y -> E))

Cal tenir en compte que

(\ x -> M N)  (\ x -> (M N))       -- el cos de l'abstracció abasta el màxim cap a la dreta
(\ x y -> E)  (\ x -> \ y -> E)    -- abreviació d'abstraccions
  • redex: contracció de l'anglès "reducible expression"
  • conversió alfa: es pot canviar el nom de la variable d'una abstracció lambda mentre el nom nou no coincideixi amb el de cap variable lliure del seu cos alterant-ne la condició.
  • beta-redex: expressió reduïble mitjançant una reducció beta.
  • eta-redex: expressió reduïble mitjançant una reducció eta.

Head Normal Form[modifica | modifica el codi]

Una expressió lambda està en "forma normal en capçalera" (HNF) si

  1. ja no té beta-redexs en posició de capçalera, perquè s'han aplicat totes les reduccions beta possibles en aquesta posició (Forma normal beta).
  2. l'expressió en posició de capçalera no ha d'ésser tampoc una abstracció lambda amb el cos reduïble,[245] per què aquest cas es tipifica com a "forma normal dèbil en capçalera" (#Weak Head Normal Form).[246]
reducció beta del càlcul lambda en notació Haskell[modifica | modifica el codi]

Una expressió és beta-reduïble (beta-redex) si consisteix en l'aplicació d'un paràmetre a una abstracció lambda (funció anònima d'una variable com a mínim). És de la forma

  (\ x -> E) P   -- expressió 'beta'-reduïble
-- la reducció 'beta' és la substitució de la variable 'x' pel paràmetre P en l'expressió E
-- el resultat ('reducte') en notació del càlcul lambda: E[x:= P]
  • La notació d'índexs de De Bruijn evita el problema de coŀlisió de noms en les substitucions. En comptes de noms les variables lligades es refereixen a l'argument per un índex d'aniuament de la variable respecte a l'abstracció lambda referida.
\ x -> \ y -> \ z -> x y z
-- amb notació de l'índex de De Bruijn seria
\ -> \ -> \ -> v3 v2 v1         -- la variable es refereix a l'argument per l'índex d'aniuament d'abstraccions

Una expressió beta reduïble està en posició de capçalera si és de la forma (en notació Haskell)

\ x1 x2 ... xi -> (\ y -> E) P1 P2 ... Pj  -- per a tot i >= 0, j >= 1

La seva reducció seria

\ x1 x2 ... xi -> E[y:= P1] P2 ... Pj
reducció eta del càlcul lambda en notació Haskell[modifica | modifica el codi]

Amb notació Haskell, de la definició del càlcul lambda

-- reducció 'eta': l'expressió
(\ x -> E x)              -- la variable de l'abstracció coincideix amb el paràmetre més allunyat del cos.
-- és substituïble per 
E
-- sempre que 'x' no sigui una variable lliure de E
definició tàcita o point-free[modifica | modifica el codi]

La programació tàcita o point-free (sense arguments) estalvia arguments en les definicions aplicant la reducció eta del càlcul lambda.

-- definició
arrelQuad x = sqrt x

-- en forma de lambda
arrelQuad = \x -> sqrt x 

-- per la reducció eta, atès que x no és una variable lliure de l'expressió (sqrt)
arrelQuad = sqrt   -- versió tàcita o 'point-free' (estalviant arguments)

Weak Head Normal Form[modifica | modifica el codi]

Vegeu [246] i [247]

Una expressió lambda amb el cos reduïble està en WHNF però no en HNF. El terme va ser encunyat per Simon Peyton Jones per explicitar la diferència entre HNF i el que la reducció de grafs fa a la pràctica: posposar la reducció del cos.[246]

Una expressió està en WHNF si està en HNF o bé és una expressió lambda amb el cos reduïble.[246]

Una expressió en WHNF està en una de les formes següents:[247]

  • Un Constructor, eventualment aplicat a expressions no reduïdes dels components
  • Una funció primitiva simple (ang: built-in, que no es basa en definicions) aplicada a menys paràmetres del que correspon a la seva aritat (per ex. "(+) 1" no es pot reduir, no hi ha cap definició de (+) on substituir el primer paràmetre, fins que disposem de tots els que li manquen)
  • Una abstracció lambda

Exemples de situacions d'avaluació a WHNF

  • evaluate de Control.Exception avalua com a efecte una expressió funcional llançadora d'excepcions, forçant l'avaluació a WHNF.
  • L'avaluació estricta de paràmetres quan es prefixen amb (!) els paràmetres formals (extensió BangPatterns) es fa a WHNF.

Forma Normal[modifica | modifica el codi]

Una expressió està en forma normal si no s'hi poden aplicar més reduccions.[244] Està completament avaluada (en profunditat).

Una expressió que comença per un constructor està en HNF, i això evita l'avaluació de les expressions dels components, quan l'avaluació es fa amb seq. Perquè quedin avaluades, cal, o bé definir-les estrictes als tipus, o bé avaluar amb deepseq o l'equivalent $!! a "forma normal".

La classe NFData (contracció de NormalFormData) del paquet deepseq[248] (a partir de GHC 7.4.1 forma part del conjunt bàsic de biblioteques de GHC) defineix la signatura d'una funció rnf (inicials de Reduce to Normal Form) per l'avaluació en profunditat. Per crear una instància de NFData per a un tipus algebraic, caldrà una implementació de rnf que apliqui recursivament rnf a les variables d'encaix corresponents als components dels constructors.

class NFData a where
  rnf :: a -> ()   -- rnf ha d'avaluar el paràmetre en profunditat a Forma Normal
import Control.DeepSeq

data NomDelTipus = Cons1 T1 T2 | Cons2 T3

instance NFData NomDelTipus where
  rnf (Cons1 t1 t2) = (rnf t1) `seq` (rnf t2) `seq` ()
  rnf (Cons2 t3) = (rnf t3) `seq` ()

Automatitzable amb el travessament genèric de les estructures algebraiques.[249]

{-# LANGUAGE DeriveGeneric #-}

import Control.DeepSeq
import Control.DeepSeq.Generics (genericRnf)
import GHC.Generics

data NomDelTipus = Cons1 T1 T2 | Cons2 T3 deriving Generic

instance NFData NomDelTipus where rnf = genericRnf

Avaluació estricta explícita[modifica | modifica el codi]

A part de l'optimització en estrictesa dels compiladors (vegeu article "Haskell és un llenguatge estricte")[50] es pot forçar l'avaluació estricta de les següents maneres:

Aplicació estricta (Crida per valor)[modifica | modifica el codi]

Avaluació del paràmetre abans de la substitució (càlcul lambda). Amb l'operador d'aplicació estricta ($!) com a alternativa de ($) d'avaluació tardana.

 f $! x = x `seq` (f x)
 -- (seq) avalua el primer operand (a HNF) i retorna el segon

Aplicació estricta a Forma Normal[modifica | modifica el codi]

($!!) amb doble signe d'exclamació, avalua l'operand en profunditat (amb deepseq) a Forma Normal, per als casos on l'operand és un tipus que implementa la classe NFData. La generació d'instàncies de NFData per a tipus algebraics es pot fer amb mecanismes de derivació genèrica (amb recorregut genèric dels tipus algebraics).

import Control.DeepSeq

-- ($!!) :: NFData a => (a -> b) -> a -> b     -- infixr 0 $!!

f $!! x = x `deepseq` (f x)

Estrictesa als components dels tipus de dades[modifica | modifica el codi]

data T = C T1 !T2 ...  -- sense prefix al component => aval. tardana, amb (!) avaluació estricta

Quan en una definició de tipus de dades es prefixa el tipus del component amb '!' l'avaluació de l'aplicació del constructor a una expressió corresponent al component es fa amb ($!) i si no hi ha el prefix '!' es fa amb ($).[250]

Vegeu Eficiència i rendiment en tipus de dades[251]

  • La nova extensió de llenguatge StrictData de GHC 8.0 capgira el mode d'avaluació per defecte dels components, prefixant amb el caràcter titlla aquells que volguem d'avaluació tardana.[252]
{-# LANGUAGE StrictData #-}            -- des de GHC 8.0
data T = C ~T1 T2 ...   -- sense prefix al comp. => aval. estricta, amb (~) avaluació tardana

Estrictesa als paràmetres formals[modifica | modifica el codi]

Avaluació estricta als paràmetres formals amb l'extensió {-# LANGUAGE BangPatterns #-}[253]

{-# LANGUAGE BangPatterns #-}
f !x !y = 2 * x * y         -- avalua estrictament (!) els paràmetres actuals (a WHNF)

{-# LANGUAGE Strict #-}            -- des de GHC 8.0
f x y = 2 * x * y       -- amb Strict avaluació estricta no-irrefutable per defecte, prefix (~) per l'avaluació com abans

Estrictesa als patrons d'encaix[modifica | modifica el codi]

Lligams amb avaluació estricta a les variables dels patrons

let !v = expr   ...      -- avalua estrictament l'expressió (amb (seq))
let (!x, !y) = (exprX, exprY)   -- avalua estrict. les expr. de les variables del patró
  • La nova extensió de llenguatge Strict de GHC 8.0 capgira el mode d'avaluació per defecte d'un mòdul a estricte, marcant amb el caràcter titlla (~) per obtenir l'avaluació tardana.[252]

foldl': iteracions amb plegat per l'esquerra estricte[modifica | modifica el codi]

l'operació foldl (foldLeft) permet fer "transformacions reiterades sobre una estructura de tipus a amb els valors d'una seqüència [b] ((a -> b -> a)), de manera equivalent als bucles for de la prog. imperativa tradicional.

 foldl :: (a -> b -> a) -> a -> [b] -> a   -- essent ''a'' l'estructura i ''[b]'' la llista de valors a iterar
-- versió tardana, cost O(n) en allotjament d'operacions pendents d'avaluar
foldl op estructInicial (x:xs) = foldl op (op estructInicial x) xs

-- versió estricta, avaluació primerenca
foldl' op estructInicial (x:xs) = let aplega_x = op estructInicial x
                                  in aplega_x `seq` foldl' op aplega_x xs
-- (seq) avalúa el primer operand i retorna el segon
-- si el tipus de ''aplega_x'' és algebraic (conté constructors (aturen l'avaluació)))

L'ús de tipus algebraics en l'operació, aturant l'avaluació en els constructors, implica la generació d'una pila de thunks (expressions pendents d'avaluar) que poden comportar el sobreiximent de la pila.

Treballar a espai constant de la pila[modifica | modifica el codi]

El llibre RealWorldHaskell ofereix un exemple on la operació nucli del plegat estricte foldl' plega sobre un parell, és a dir un acumulador algebraic (constructor del parell amb les seves expressions).[254] Els constructors en posició de capçalera (vegeu #Head Normal Form) estan en HNF, per tant les expressions dels components no són avaluades. Això fa que s'amuntegui la seva avaluació pendent a la pila.

  • o bé s'avalua estrictament els components de l'acumulador compost dins la operació del plegat
  • o bé es pot definir una versió de foldl'/foldr' que avaluï estrictament, però en profunditat a #Forma Normal.
import Control.DeepSeq (NFData, ($!!))
import Data.Foldable

-- de la definició de foldl' i foldr', substituint ($!) per ($!!) de DeepSeq per avaluar a Forma Normal

foldl'' :: (Foldable t, NFData b) => (b -> a -> b) -> b -> t a -> b
foldl'' f z0 xs = foldr f' id xs z0
      where f' x k z = k $!! f z x    -- ($!) substituït per ($!!)

foldr'' :: (Foldable t, NFData b) => (a -> b -> b) -> b -> t a -> b
foldr'' f z0 xs = foldl f' id xs z0
      where f' k x z = k $!! f x z    -- ($!) substituït per ($!!)

Estrictesa en les crides recursives[modifica | modifica el codi]

Vegeu "Recursivitat final al Haskell".[255]

  • La "recursivitat final mòdulo cons" (recursivitat diferida):[256] quan la crida recursiva és el component d'una dada que es retorna, no serà avaluada perquè els constructors de Tipus de dades algebraics aturen l'avaluació HNF.
duplicaLlista :: [a] -> [a]
duplicaLlista [] = []
duplicaLlista (x : xs) = x : duplicaLlista xs  -- crida recursiva "mòdulo cons"
  • En cas de recursivitat final normal, el compilador optimitza alguns casos per evitar generar una pila d'expressions pendents d'avaluar (thunks)[51] però cal assegurar-se'n. Vegeu "Limitations of strictness analysis".[257]

Avaluació tardana explícita en els patrons d'encaix[modifica | modifica el codi]

Els encaixos de patrons s'avaluen normalment de manera estricta. Per avaluar un encaix de manera tardana (lazy) cal prefixar-lo amb el caràcter titlla '~'.[258][49]

L'avaluació tardana de l'encaix (vegeu exemple tot seguit) converteix l'encaix en irrefutable (no rebutjable, no parcial) evitant l'avaluació d'altres opcions.

L'ús de l'avaluació tardana explícita amb tipus amb més d'un constructor és perillós:[259]

f :: [a] -> [a]
f ~(x:xs) = x:xs
-- és traduït internament a
f ys = head ys : tail ys  -- compte! funcions parcials!!
-- l'encaix (f ys), encaix amb variable, sempre té èxit.

-- si, per filtrar el cas [] no definit anteriorment, l'avaluem primer ...
f [] = []
f ~(x:xs) = x:xs  -- l'avaluació tardana aquí és estúpida perquè el paràmetre 
                  -- ja ha estat avaluat estrictament per l'opció []

-- si haguéssim posat el cas [] després: 
f ~(x:xs) = x:xs
f [] = []
-- el segon encaix (f []) no s'avalua mai, perquè l'anterior és irrefutable!
-- L'ordre és rellevant!!! El compilador ens avisarà del codi inabastable.

Programació de nivell baix[modifica | modifica el codi]

Tipus primitius que es passen per valor (no encapsulats, ang:unboxed)[modifica | modifica el codi]

Vegeu-ho a GHC

Tuples no encapsulades per al retorn de múltiples valors[modifica | modifica el codi]

Vegeu-ho a GHC

Especialització d'operacions sobrecarregades[modifica | modifica el codi]

Vegeu-ho a GHC

Referència ràpida de la biblioteca[modifica | modifica el codi]

A GHC.

Assegurar la Qualitat[modifica | modifica el codi]

Proves unitàries
  • Biblioteca QuickCheck de comprovació de predicats sobre sèries de valors aleatoris amb acotació progressiva del domini del problema, mitjançant la instanciació de classes específiques (Arbitrary).[262][263]
Verificació formal estàtica

Problemàtiques[modifica | modifica el codi]

L'ús simultani de diferents biblioteques que utilitzin diferents versions d'una mateixa tercera biblioteca[264] comporta:

Un cop has generat l'executable, el següent maldecap són les petades de les funcions parcials, que criden a error per als valors per als quals no estan definides, (head []), sense cap pista del culpable:

Les crides recursives i els plegats poden generar piles d'expressions pendents d'avaluar, fent petar la pila, si no s'explicita correctament l'avaluació estricta dels acumuladors i dels components de les estructures intermèdies:

La modificació de les preferències en temps d'execució dóna lloc al:

Perquè la imatge en memòria del programa no creixi més del compte cal tenir en compte:

Exemples[modifica | modifica el codi]

Encapsulament estil O.O.[modifica | modifica el codi]

Cercant una aproximació funcional (amb immutabilitat) a l'encapsulament de la orientació a objectes i simulació d'herència de propietats del component:

  • Exemple. Els tipus d'objecte TPunt2D i TPunt3D (en terminologia Java classes (d'objectes) sense operacions), implementen (els en terminologia Java interfaces), el primer IFun2D i el segon IFun3D que requereix també IFun2D.

Un mòdul per cada tipus, encapsulant dades i operacions.

  • Es pot evitar duplicar les implementacions d'alguns mètodes desacoblant-los de l'estructura, definint classes de propietats (mètodes getters/setters), i classes basades en aquestes, implementant els mètodes per defecte a la classe en un mòdul a banda, i sobreescrivint-los quan convingui a la instanciació invalidant el mètode per defecte. (ang.:overriding)
-- fitxer ClassesPropietats.hs  -- (getters/setters)
module ClassesPropietats (PropXY(..), PropZ(..)) where

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

class PropZ t where
  getZ :: t -> Int
  setZ :: Int -> t -> t
{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
-----------------------------------------------------------------------
-- Interfícies de funcionalitat
-- fitxer ClassesBasadesEnPropietats.hs 
module ClassesBasadesEnPropietats (IFun2D(..), IFun3D(..)) where
 
import ClassesPropietats
 
-- funcionalitat 2D
 
class (PropXY t) => IFun2D t where
  desplaça2D :: Int -> Int -> t -> t

instance (PropXY t) => IFun2D t where      -- instanciació genèrica (ext. UndecidableInstances)
  desplaça2D dx dy punt = setXY (x+dx) (y+dy) punt
    where
      (x,y) = getXY punt
 
-- funcionalitat 3D
 
class (IFun2D t, PropZ t) => IFun3D t where
  desplaça3D :: Int -> Int -> Int -> t -> t
  
instance (IFun2D t, PropZ t) => IFun3D t where  -- instanciació genèrica (ext. UndecidableInstances)
  desplaça3D dx dy dz punt = setZ (z+dz) $ desplaça2D dx dy punt
    where
      z = getZ punt
-- fitxer Punt2D.hs per al tipus TPunt2D
{-# LANGUAGE NamedFieldPuns #-} -- sintaxi de literals de registres simplificada
module Punt2D (
    TPunt2D,   -- tipus opac (no exportem els constructors)
    nouPunt2D,  -- el separador coma al final s'accepta
    ) where

import ClassesPropietats
import ClassesBasadesEnPropietats

data TPunt2D = Punt2D { x,y ::Int} deriving (Eq, Show)

-- generadors
nouPunt2D :: Int -> Int -> TPunt2D
nouPunt2D = Punt2D   -- definició tàcita (estalviant arguments)

-- propietats
instance PropXY TPunt2D where
  getXY Punt2D {x,y} = (x, y)  -- el literal dels camps lliga amb més precedència que l'aplicació (no cal agrupar-lo amb el constructor.
                               -- {x, y} a l'encaix, equival a {x = x, y = y} per l'extensió NamedFieldPuns
  setXY x y punt = punt { x, y}    -- {x, y} al cos equival a {x = x, y = y} per l'extensió NamedFieldPuns
-- fitxer Punt3D.hs per al tipus TPunt3D
{-# LANGUAGE NamedFieldPuns #-} -- sintaxi de literals de registres simplificada
module Punt3D (TPunt3D {- tipus opac -}, nouPunt3D) where

import ClassesPropietats
import ClassesBasadesEnPropietats

data TPunt3D = Punt3D { x, y, z :: Int} deriving (Eq, Show)

-- generador
nouPunt3D :: Int -> Int -> Int -> TPunt3D
nouPunt3D = Punt3D 

-- propietats
instance PropXY TPunt3D where
  getXY Punt3D {x,y} = (x, y)
  setXY x y punt = punt { x, y}    -- {x, y} al cos equival a {x = x, y = y} per l'extensió NamedFieldPuns

instance PropZ TPunt3D where
  getZ Punt3D {z} = z    -- a l'encaix {z} equival a {z = z}
  setZ z punt = punt {z}   -- {z} equival a {z = z}

prova:

-- fitxer Main.hs
module Main where

import ClassesPropietats
import ClassesBasadesEnPropietats
import Punt2D
import Punt3D
import Data.Function ((&))  -- (&) = flip ($) -- infixl 1  -- desde GHC 7.10.1

p2D = nouPunt2D 1 2
        & setXY 3 4
        & desplaça2D 1 1

p3D = nouPunt3D 1 2 3
        & desplaça2D 2 2
        & desplaça3D 1 1 1

main = do
        putStrLn $ "p2D: " ++ show p2D
        putStrLn $ "p3D: " ++ show p3D

execució

runhaskell Main
  • Amb la prevista incorporació del polimorfisme de "registres amb camps específics" com a requeriment de context, les classes basades en accessors de camps, es derivaran automàticament.[265][266]

Col·leccions amb components heterogenis - Emulació del despatx dels mètodes virtuals de la O.O.[modifica | modifica el codi]

Vegeu ref.[150] El #Tipus existencial dóna solució al tractament de col·leccions heterogènies incorporant els elements heterogenis com a components amb quantificació existencial (aquells tipus per als quals existeixi una instància de les classes de tipus de l'operatòria comuna), sense trencar l'homogeneïtat de les llistes.

module Forma (CForma(..)) where

class CForma t where
  perímetre :: t -> Double
  àrea :: t -> Double
  nom :: t -> String
module Cercle (TCercle, cercleNou) where

import Forma (CForma(..))

-- HasCallStack: sinònim de restricció per afegir explícitament 
-- la localització d'origen de la crida a la pila per al cas de bolcat (GHC 8.0)
import GHC.Stack (HasCallStack)  

type TRadi = Double

data TCercle  = Cercle TRadi deriving (Eq, Show)

cercleNou :: HasCallStack {- afegeix el punt de crida a la pila -} => TRadi -> TCercle
cercleNou r 
  | r > 0 = Cercle r
  | otherwise = error $ "cercleNou: radi <= 0" -- no cal afegir el CallStack al missatge a GHC 8.0

instance CForma TCercle where
  perímetre (Cercle r) = 2 * pi * r
  àrea (Cercle r) = pi * r ^ 2
  nom _ = "Cercle"
module Rectangle (TRectangle, rectangleNou) where

import Forma (CForma(..))
import GHC.Stack (HasCallStack)  

type TCostat = Double

data TRectangle = Rectangle TCostat TCostat deriving (Eq, Show)

rectangleNou :: HasCallStack => TCostat -> TCostat -> TRectangle
rectangleNou x y 
  | x > 0 && y > 0 = Rectangle x y
  | otherwise = error $ "rectangleNou: algun dels costats <= 0"

instance CForma TRectangle where
  perímetre (Rectangle x y) = 2 * (x + y)
  àrea (Rectangle x y) = x * y
  nom _ = "Rectangle"
 -- fitxer existquant.hs
 {-# LANGUAGE ExistentialQuantification #-}

import Forma (CForma(..))
import Cercle (cercleNou)
import Rectangle (rectangleNou)

import Control.Monad as Monad
import Text.Printf
import Data.Function ((&))  -- (&) = flip ($) -- infixl 1  -- desde GHC 7.10.1

-- tipus existencial (designa un cjt. de tipus amb característiques pròpies)
--   característica: incorpora un component d'aquells tipus ''t'' que implementin CForma

data TForma = forall t. CForma t => Forma t

-- implementació del tipus existencial amb
--   despatx dels mètodes propis als mètodes del component

instance CForma TForma where
  perímetre (Forma component) = perímetre component
  àrea (Forma component) = àrea component
  nom (Forma component) = nom component

 -- llista de valors del tipus existencial
formes :: [TForma]
formes = [Forma (cercleNou 2.4), Forma (rectangleNou 3.1 4.4)]

 -- forM_ (forEach en altres lleng.) mapeja seqüencialment als elems. de la llista una func.
 --                  amb efectes col·laterals de tipus mònada (m tipResultat), descartant resultats.
main = do
  Monad.forM_ formes $ \forma ->
      printf format (forma & nom) (forma & perímetre) (forma & àrea)
  where
    format = "Forma %s, perímetre %.2f, àrea %.2f\n"
runhaskell existquant.hs

dóna la següent sortida:

Forma Cercle, perímetre 15.08, àrea 18.10
Forma Rectangle, perímetre 15.00, àrea 13.64

Entrada / Sortida amb descriptor regionalitzat[modifica | modifica el codi]

withFile / withBinaryFile (de System.IO) obre el fitxer, en passa el descriptor a la funció que executa l'entrada/sortida (Handle -> IO r) i, en retornar el control o en cas d'excepció, tanca el fitxer alliberant recursos. Basat en la clàusula bracket a la secció #excepcions.

  • Evita l'ús del descriptor "handle" (mànec) del fitxer més enllà del retorn (recurs circumscrit a l'àmbit o "regionalitzat").

Vegeu també mònades per a l'allotjament de recursos en regions apilades[267][268]

 -- Fitxer prova.hs (decodifica nombres i n'imprimeix el factorial)
 module Main(main) where

  import qualified Control.Monad as Monad
  import qualified Numeric
  import Control.Exception (try)
  import Text.Printf
  import System.IO
  import System( getArgs)

  llegirSencerDecimal :: String -> Maybe (Int, String)
  llegirSencerDecimal text =
    case Numeric.readDec text of -- retorna llista d'interpretacions sintàctiques
      [(n_descodificat, resta)] -> Just (n_descodificat, resta)  -- una sola interpretació
      [] -> Nothing       -- cap ni una => no hi ha dígits
      _ -> Nothing       -- diverses interpretacions => entrada ambigua

  -- Integer: precisió arbitrària, Int: precisió s/. paraula de la màquina

  fact :: Int -> Integer
  fact 0 = 1
  fact n | n > 0 = fact_rf 1 n

   where fact_rf :: Integer -> Int -> Integer
         fact_rf acum n | n > 1  = fact_rf (acum * toInteger n) (n-1)
                        | n == 1 = acum

  imprimeix_fact n resta_de_linia = do
        printf "n=%d => fact n =%d" n (fact n)   -- escriu n, (fact n)
        case resta_de_linia of
             [] -> putStrLn ""   -- escriu salt de línia
             (sep:_) | sep `elem` "," -> putStrLn " (seguit de separador)"
             _ -> putStrLn " (hi ha text enganxat al nombre)"

  analitza_linia_i_tracta_nombre :: Handle -> IO (Either IOError ())
  analitza_linia_i_tracta_nombre h1 = try  -- retorna excepció en un tipus Either per controlar-la més tard
  (do
    línia <- hGetLine h1
    case llegirSencerDecimal línia of
      Just (n, resta) -> imprimeix_fact (abs n) resta
      Nothing -> putStrLn "error de descodificació o nombre inexistent"
  )

  processa_linia_fitxer:: Handle -> IO ()
  processa_linia_fitxer h1 = do
         resultat <- analitza_linia_i_tracta_nombre h1
         case resultat of    -- controla excepció al resultat de tipus Either IOError ()
           Left excepcio -> do
                -- tasques de finalització
                -- hClose h1     -- tanca fitxer (amb withFile ja no caldrà)
                ioError excepcio -- dispara excepció
           Right _valor -> return ()

  processa_nom_fitxer nom = catch
       ( do
          withFile nom ReadMode $ \handle ->
           -- llaç fins que es dispari una excepció per fi de fitxer
           Monad.forever $ processa_linia_fitxer handle
       )
       (\excep -> putStrLn $ show excep
       )

  main = do
    args <- getArgs
    case args of
      [] -> print "afegiu nom_del_fitxer\n"
      -- mapM_ mapeja seqüencialment la funció efecte als arguments, descartant resultats
      _ -> Monad.mapM_ processa_nom_fitxer args

 -- fi de prova.hs
-- fitxer nums.txt
0
1ABC
2,
10
15—fi de nums.txt
runhaskell prova.hs nums.txt

produeix la sortida següent:

n=0 => fact n =1
n=1 => fact n =1 (hi ha text enganxat al nombre)
n=2 => fact n =2 (seguit de separador)
n=10 => fact n =3628800
n=15 => fact n =1307674368000
nums.txt: hGetLine: end of file

Expressions regulars[modifica | modifica el codi]

Vegeu-ho a Compilador Haskell de Glasgow#Expressions regulars

Concurrència simple amb MVars - Productor-consumidor[modifica | modifica el codi]

Vegeu-ho a Haskell concurrent

Canals amb cues d'entrada (Chan) - Client-servidor[modifica | modifica el codi]

Vegeu exemple a Haskell concurrent

Paral·lelisme de tasques - Compilació per a processadors multicor[modifica | modifica el codi]

Només a GHC. Vegeu Compilador Haskell de Glasgow#Paral·lelisme de tasques - Compilació per a processadors multicor.

Concurrència condicionada amb variables transaccionals (TVar) - Mònada STM (transaccions de memòria per programari)[modifica | modifica el codi]

Vegeu exemple a Haskell concurrent

Exemples de Fletxes (seqüenciació d'efectes basada en l'encadenament de morfismes)[modifica | modifica el codi]

Ent./Sort. amb fletxes de Kleisli (mònades elevades a fletxes)[modifica | modifica el codi]

Vegeu exemple a Fletxa (programació funcional)#Ent./Sort. amb fletxes de Kleisli (mònades elevades a fletxes)

Extreure informació d'un document XML[modifica | modifica el codi]

Vegeu exemple a Fletxa (programació funcional)

Quasi-Quotations: Plantilles per a expressions, patrons, tipus i declaracions[modifica | modifica el codi]

Vegeu Compilador_Haskell_de_Glasgow#Quasi-Quotations (Plantilles)

Paral·lelisme[modifica | modifica el codi]

Vegeu Compilador_Haskell_de_Glasgow#Paral·lelisme de tasques - Compilació per a processadors multicor

Amb Estratègies[modifica | modifica el codi]

Vegeu a GHC: Estratègies i també Reducció en paral·lel mitjançant estratègies

Amb la mònada Par[modifica | modifica el codi]

Vegeu Haskell_concurrent#Futuribles -- Encadenament de càlculs asíncrons - la mònada Par

Paralel·lisme d'accions IO - El functor aplicatiu Concurrently[modifica | modifica el codi]

Vegeu Haskell_concurrent#Operacions d'Entrada/Sortida asíncrones i simultànies

Amb paral·lelisme de dades DPH[modifica | modifica el codi]

Vegeu QuickSort amb Data Parallel Haskell

Ús de les instruccions SIMD de dades vectoritzades[modifica | modifica el codi]

Vegeu SIMD - Single Instruction, Multiple Data

Paral·lelisme a GPUs/CPUs MIMD[modifica | modifica el codi]

Vegeu MIMD - Multiple Instruction, Multiple Data - Paral·lelisme a GPUs/CPUs

Gestor de projectes i empaquetatge de biblioteques i aplicacions[modifica | modifica el codi]

El Haskell utilitza un gestor de projectes per resoldre les dependències d'altres biblioteques i facilitar-ne la compilació on s'instal·len, amb independència del compilador emprat.

Aquest gestor es diu Cabal[269] (Common Architecture for Building Applications and Libraries)[270] i utilitza arxius de descripció de projecte amb l'extensió ".cabal". Vegeu "Com crear un paquet executable o bé biblioteca"[271][272]

El nom del paquet consta de nom-versió, per ex. shakespeare-css-1.0.1.2, on la versió major són els dos primers components numèrics (un canvi en la versió major indica trencament de compatibilitat amb l'API anterior), la versió menor és el tercer (identifica la API, un canvi significa un augment de l'API sense trencament)), i el quart indica modificacions sense variar l'API.[273]

Els paquets es descarreguen en directoris dins del $HOME/.cabal, els executables de les aplicacions quedaran a $HOME/.cabal/bin que caldrà afegir a la variable d'entorn PATH.

Els paquets ja instal·lats per GHC es gestionen amb el programa ghc-pkg.

Al Linux podem començar treballant amb les biblioteques precompilades en paquets de la distribució de Linux. Si us cal una versió més recent del compilador, caldrà instal·lar-lo prèviament i, en acabat, instal·lar #The Haskell Platform, contràriament al Windows on el paquet #The Haskell Platform l'incorpora.

El rebost oficial de biblioteques i aplicacions és el Hackage[274]

  • instal·lació manual: Si no es disposa de l'executable cabal del paquet cabal-install, per instal·lar un paquet cal descarregar-lo, buscar el fitxer Setup.hs o bé Setup.lhs i executar
runhaskell Setup clean             # neteja arxius producte de compilacions anteriors
runhaskell Setup configure —help      # mostra opcions generals i específiques del paquet
runhaskell Setup configure —user —altres-opcions
runhaskell Setup build
[sudo] runhaskell Setup install      # sudo si configures amb —global en comptes de —user
                                     #, per actualitzar el rebost del sistema
  • instal·lació automatitzada d'un paquet del rebost Hackage:

Amb "cabal" descarrega, configura, compila i instal·la d'una sola tacada, després d'instal·lar automàticament els paquets dels quals depèn. Preguntes freqüents aquí.[275]

 # l'ajuda és per comanda
 cabal help install
 # --with-compiler=... permet seleccionar el compilador
 # <versió> = <digit> {'.' <digit>} ['.' '*']
 # <paquet_o_dependència> :== nom | nom-versió | "nom < versió" | "nom == versió"
 cabal install paquet1 paquet2  --opcions
  • per llistar els paquets que ja tenim a GHC, "ghc-pkg list". No existeix encara l'equivalent "hugs-pkg"[276]
  • en cas de problemes per biblioteques que no compilen bé, és útil limitar-ne la versió amb l'opció—constraint=dependència
--constraint="nom_biblio < versió" o --constraint=nom_biblio==versió (l'API s'identifica per 3 components de la versió)
exemples (les cometes només fan falta si hi ha espais o bé
           símbols de redirecció de l'intèrpret de comandes {<,>}):
cabal install QuickCheck --constraint=BiblioDeLaQualDepen==2.3.4.*
  • a partir de cabal-install v.1.16 cabal admet paral·lelisme en la instal·lació (opció -j)
cabal install -j "wx == 0.13.*"

Conflictes de dependències[modifica | modifica el codi]

Tanmateix cal tenir en compte que l'ús simultani de biblioteques que facin servir diferents versions d'un mateix paquet és molt problemàtic.[277] Cal evitar absolutament la comanda cabal upgrade que ens abocaria al problema esmentat.[277]

Entorn aïllat de desenvolupament amb cabal sandbox[modifica | modifica el codi]

cabal amb el verb sandbox[278] crea un dipòsit de biblioteques específic del projecte en el subdirectori amagat ".cabal-sandbox". Una Sandbox (capsa de sorra literalment) és un entorn tancat per a proves per evitar que els errors que s'hi produeixin repercuteixin a l'exterior. Aquesta funcionalitat es va desenvolupar al programa cabal-dev[279] que ara ha quedat obsolet.

  • els executables queden al directori ./.cabal-sandbox/bin que caldrà afegir a la var. d'entorn d'executables PATH.
  • s'hi poden associar subprojectes amb la comanda
cabal sandbox add-sources camí/al/subprojecte

Per a més info. consulteu cabal sandbox --help.

El rebost Stackage - LTS (Long Term Support) Haskell[modifica | modifica el codi]

Stackage,[280] contracció de "Stable, Vetted Hackage", vol ser una solució al problema de les dependències, promovent l'actualització del paquet o l'exclusió de les noves imatges del seu rebost, per evitar la multiplicitat de versions en les dependències.

Stackage ofereix imatges congelades (ang:snapshot) d'un conjunt de biblioteques que compilen sense problemes (compilació verificada respecte d'una versió de GHC i de les dependències), facilitant la llista de les versions d'aquestes biblioteques que utilitzarem com a restricció del projecte (fitxer "cabal.config" de la carpeta del projecte) per evitar relligar amb versions noves que puguin sorgir trencant la compatibilitat.

Les imatges etiquetades "Nightly" tenen versions més recents de biblioteques, però poden ometre aquelles endarrerides respecte al suport de dependències que hagin modificat l'API i les que en depenguin, mentre que les etiquetades LTS Haskell (Long Term Support) són més completes però no tan recents.[281][282][283]

És una iniciativa[284] dels creadors de l'entorn de desenvolupament web Yesod, auspiciada per FPComplete.[285]

Creació del fitxer de projecte .cabal[modifica | modifica el codi]

El fitxer de projecte .cabal està relacionat un a un amb la carpeta que el conté.

cabal init
interroga l'usuari a la cònsola de comandes i genera un esquelet de projecte que caldrà editar posteriorment per afegir-hi els noms dels fonts i els paquets dels quals depèn. Caldrà afegir-hi manualment la llista de mòduls exportats si és una biblioteca (secció exposed-modules), dels mòduls no exportats (secció other-modules) i de les dependències (secció build-depends).
leksah 
Entorn gràfic d'usuari (IDE) que conté un formulari d'edició del fitxer de projecte.

Un cop creat, la comanda cabal sense nom de paquet treballa sobre el fitxer de projecte del directori de treball:

cabal sandbox init -- genera un rebost de biblioteques específic del projecte
                   -- els executables quedaran a la carpeta ".cabal-sandbox/bin"
cabal sandbox add-sources camí/al/subprojecte  -- afegir subprojecte
cabal sandbox delete  -- elimina el rebost específic

cabal clean        -- neteja tots els fitxers generats per la compilació
cabal configure    -- estableix opcions i comprova compatibilitat de les dependències
cabal build        -- compila
cabal install      -- configura, compila, relliga i instal·la
cabal freeze       -- fixa les versions de les dependències com a restricció
                   -- al fitxer "cabal.config" de restriccions a la carpeta del projecte
cabal repl         -- engega l'intèrpret 'ghci' havent carregat mòduls i opcions esmentades al fitxer de projecte

Vegeu també "Com escriure (i empaquetar) un programa en Haskell".[286] També "Actualitzant un paquet a noves versions de GHC i biblioteques".[287]

Gestió dels paquets instaŀlats[modifica | modifica el codi]

Amb ghc-pkg.[288]

$ ghc-pkg list 

$ ghc-pkg check            # comprova dependències trencades per la reinstaŀlació d'aquestes

Política de versions[modifica | modifica el codi]

Vegeu ref.[289]

Els paquets s'identifiquen amb un número de versió amb dígits separats per un punt, amb un mínim de tres components A.B.C on A.B s'anomena versió major i C la versió menor.

  • Cal incrementar el nombre de versió major A.B quan:
  • l'actualització elimina alguna entitat
  • o bé s'han modificat tipus, entitats, definicions d'estructures (data), o classes
  • o bé s'han afegit instàncies òrfenes (les que no acompanyen les definicions ni de la classe ni del tipus)
  • o bé s'han eliminat instàncies de classes
  • Cal incrementar la versió menor C quan:
  • s'afegeixen noves definicions, tipus, classes, instàncies no òrfenes i mòduls.

Eines relacionades amb el gestor de projectes[modifica | modifica el codi]

cabal-progdeps
[290] llista les dependències en un directori de projecte
yackage
[291] servidor de rebost "hackage" local per a desenvolupament

The Haskell Platform[modifica | modifica el codi]

The Haskell Platform vol ser un instal·lador empaquetant el compilador GHC (només a Windows, a Unix i derivats cal descarregar-lo prèviament), una biblioteca i un paquet d'eines incorporant paquets de rutines[292] del rebost Hackage.[274] amb encaix verificat respecte a la versió de la biblioteca Base[153] inclosa. (Preguntes freqüents)[293]

Entorns de desenvolupament i altres eines[modifica | modifica el codi]

hlint
Aquí[294] analitzador de codi amb suggeriments de millora. Avisa de construccions redundants i proposa alternatives al codi.
Leksah
Aquí.[295] Entorn gràfic de desenvolupament (IDE) basat en GTK, útil per estudiar un paquet. Genera fitxers de projecte en format .cabal. Permet obtenir info. a partir del menú de context, dels elements que marqueu d'un programa. Instruccions d'instal·lació aquí[296] Permet la depuració de programes (Truc: cal definir-hi un Workspace i un Package perquè s'activi la depuració).
eclipseFP
Aquí.[297] Endollable per a l'entorn de desenvolupament (IDE) Eclipse
Visual Haskell 2005
Entorn de Microsoft Visual Studio per al Haskell. Hi va haver un desenvolupament per al Visual Studio 2005 inacabat segons les fonts.[298] Malgrat que el compilador GHC el desenvolupa Microsoft Research a Cambridge (Regne Unit), no hi ha eines de suport específiques del Microsoft Windows.
ThreadScope
Analitzador de paral·lelisme al GHC. Vegeu GHC#Eines.
hpc (haskell program coverage)
Anàlisi de cobertura del codi. Genera traces d'execució i, després de diverses execucions, permet mostrar en un informe html, amb marcatge de colors, quines parts del codi no s'han executat mai i quines expressions booleanes són sempre certes o sempre falses. Vegeu GHC#Eines.
hp2any
perfil d'ús de memòria en temps real i diferit. Vegeu GHC#Eines.
hgettext
eina que permet extreure cadenes de text del codi font per preparar la internacionalització amb traduccions múltiples.[299]

Implementacions[modifica | modifica el codi]

Vegeu[300]

GHC ("Glasgow Haskell Compilation System")
Compilador[301] i intèrpret (GHCi)[302] que va més enllà del Haskell98.

Incorpora "concurrència","[303]paral·lelisme","[304]Memoria Transaccional per Software"[305][306] i opcions d'optimització. Pot generar codi nadiu, codi C, o codi optimitzat mitjançant LLVM.[307]

A partir de la versió 6.10 incorpora "Data Parallel Haskell"[308][309] per al tractament paral·lel de vectors.

L'intèrpret ghci genera codi intermedi i admet l'execució mixta de codi intermedi i codi nadiu.

Hugs
Intèrpret.[310] Implementació quasi completa del Haskell 98. Incorpora registres extensibles anomenats TRex.[311]
nhc98
Compilador[312] per a màquines de 32 bits. Intèrpret "Hi". Muntador "Hmake".
UHC / EHC
Utrecht Haskell Compiler / Essential Haskell Compiler[313] amb rerefons de compilació per a llenguatge C, Java, LLVM i CLR.[314]
YHC 
York Haskell Compiler[315] amb rerefons de compilació a Javascript[316] i traçador de crides Hat[317]
JHC
Jhc Haskell Compiler[318] Compilador de John Meacham.[319]
LHC
LHC Haskell Compiler[320] Projecte derivat del JHC.

Extensions de noms de fitxer[modifica | modifica el codi]

Vegeu enllaç[321]

.hs
Haskell source
.lhs (Literate haskell source)
font documentat segons Programació literària immergint el codi dins la documentació:
  • o bé dins un doc. de text, prefixant les línies de codi amb '>' (anomenat estil rastre d'ocell assimilant la forma '>' a les seves empremtes)
  • o bé dins un doc. LaTeX situant el codi entre \begin{code} i \end{code} Vegeu lhs2tex
.cabal
descripció de paquet Cabal
.x
doc. de lèxic, entrada per al generador d'analitzadors de lèxic Alex (de l'estil del lex) (genera *.hs)
.lx
doc. Alex literari (a l'estil del .lhs)
.y
doc. de gramàtica, per al generador d'analitzadors gramaticals Happy (de l'estil del yacc) (genera *.hs)
.ly
doc. Happy literari (a l'estil del .lhs)
.gh
preprocessador Generic Haskell
.gc
doc. d'entrada al GreenCard (descripció enllaç amb codi C) adaptat al FFI (Foreign Function Interface) normatiu per a Haskell2010[30]
.chs
doc. d'entrada al convertidor c2hs
.hsc
doc. d'entrada al convertidor hsc2hs

Plataformes, aspectes[modifica | modifica el codi]

plataformes virtuals[modifica | modifica el codi]

sobre Xen
sobre LLVM (Low Level Virtual Machine)
  • GHC pot compilar via LLVM per aprofitar-ne les optimitzacions.[325] Comproveu-hi les "Plataformes suportades"[326]
sobre JVM
  • Frege: llenguatge quasi Haskell adaptat als tipus de dades i entorn del Java que funciona sobre el JDK de Java-7.[327][328][329]
  • UHC Jazy: Rerefons JVM per al compilador UHC. (implementació parcial).[330]
sobre JavaScript
  • Haste: modificació de GHC amb rerefons JavaScript.[331]
  • GhcJs: modificació de GHC amb rerefons JavaScript.[332]
  • Yrc2Js: Rerefons a Javascript del compilador YHC.[333]
  • UHC: Rerefons (en desenvolupament) de l'"Utrecht Haskell Compiler"[334]
  • Elm: llenguatge de programació reactiva funcional amb sintaxi quasi Haskell, que compila a Javascript, per a la composició d'interfícies gràfiques sobre navegadors web.
  • PureScript: llenguatge quasi Haskell d'avaluació estricta amb sortida JavaScript.[335]
sobre CLR

Altres plataformes[modifica | modifica el codi]

A sistemes d'Apple (Mac OS, iOS)

Dialectes del Haskell[modifica | modifica el codi]

DDC (The Disciplined Disciple Compiler)
(cat: El Compilador del Deixeble Disciplinat): versió d'avaluació estricta, contràriament a Haskell (en desenvolupament).[339]
OOHaskell
biblioteca d'orientació a objectes d'Oleg Kyseliov i Ralf Lämmel (versió del 2005 no actualitzada).[340]
O'Haskell
versió orientada a objectes[341] de Johan Nordlander. Treball del 2001 evolucionat a Timber
Timber
evolució de O'Haskell, per a la programació reactiva de sistemes encastats.[342] (del mateix autor Johan Nordlander). Limitacions i problemàtiques[343]
Hume
llenguatge funcional estricte per a sistemes encastats de recursos limitats, amb concurrència per pas de missatges i característiques CSP on la sintaxi del nivell d'expressions és la del Haskell.
Incorpora limitació de costos de recursos per procés, en temps, memòria dinàmica i mida de la pila amb les corresponents excepcions.
CAL
aproximació a Haskell sobre la JVM integrat dins Open Quark de Business Objects,[344][345][346] companyia francesa absorbida[347] pel gegant alemany del programari SAP AG
Jaskell
llenguatge sobre la Java VM, funcional i d'avaluació tardana, força diferent en sintaxi.[348]
Frege
llenguatge quasi Haskell d'avaluació tardana sobre la JVM, amb particularitats del Java (literals Java, mètodes als tipus de dades, ...) sobre Java-7[327][328]
Elm
llenguatge quasi Haskell estricte de programació reactiva funcional per generar interfícies gràfiques a la web, compilant a JavaScript. Genera una estructura que s'automodifica per recomposició reaccionant a diversos esdeveniments i canvis de valors.
Idris
llenguatge estricte amb sintaxi quasi haskell amb tipus dependents de valors i tipus com a objectes de primer ordre. Incorpora mecanismes de certificació estàtica de l'estil dels llenguatges dits comprovadors de teoremes com ara Coq i Agda.
PureScript
llenguatge quasi haskell estricte per compilar a JavaScript, amb modificacions per adaptar-se al JavaScript.[335]

Relacionat[modifica | modifica el codi]

Referències[modifica | modifica el codi]

  1. Plantilla:Cite mailing list
  2. 2,00 2,01 2,02 2,03 2,04 2,05 2,06 2,07 2,08 2,09 2,10 2,11 2,12 Peyton Jones 2003, p. xi
  3. Norell, Ulf. «Dependently Typed Programming in Agda». Gothenburg: Chalmers University, 2008. [Consulta: 9 febrer 2012].
  4. Hudak et al., 2007, p. 12-38,43.
  5. Stroustrup, Bjarne; Sutton, Andrew «Design of Concept Libraries for C++». Falta indicar la publicació, 2011.
  6. 6,0 6,1 6,2 6,3 6,4 6,5 6,6 6,7 6,8 6,9 Hudak et al., 2007, p. 12-45–46.
  7. 7,0 7,1 Meijer, Erik. «Confessions of a Used Programming Language Salesman: Getting the Masses Hooked on Haskell». OOPSLA 2007.
  8. Meijer, Erik. «C9 Lectures: Dr. Erik Meijer – Functional Programming Fundamentals, Chapter 1 of 13». Channel 9. Microsoft, 1 octubre 2009. [Consulta: 9 febrer 2012].
  9. Drobi, Sadek. «Erik Meijer on LINQ». InfoQ. C4Media Inc. [QCon SF 2008], 4 març 2009 [Consulta: 9 febrer 2012].
  10. Hickey, Rich. «Clojure Bookshelf». Listmania!. Amazon.com. [Consulta: 9 febrer 2012].
  11. Heller, Martin. «Turn up your nose at Dart and smell the CoffeeScript». JavaWorld. InfoWorld, 18 octubre 2011 [Consulta: 9 febrer 2012].
  12. «Declarative programming in Escher». [Consulta: 7 octubre 2015].
  13. Syme, Don; Granicz, Adam; Cisternino, Antonio. Expert F#. Apress, 2007, p. 2. «F# also draws from Haskell particularly with regard to two advanced language features called sequence expressions and workflows 
  14. Wechsung, Ingo. «The Frege Programming Language». [Consulta: 26 febrer 2014].
  15. http://www.wired.com/2014/03/facebook-hack/
  16. «Idris, a dependently typed language». [Consulta: 26 octubre 2014].
  17. «LiveScript Inspiration». [Consulta: 4 febrer 2014].
  18. «Glossary of Terms and Jargon». Perl Foundation Perl 6 Wiki. The Perl Foundation, 28 February. [Consulta: 9 febrer 2012].
  19. Kuchling, A. M. «Functional Programming HOWTO». Python v2.7.2 documentation. Python Software Foundation. [Consulta: 9 febrer 2012].
  20. Fogus, Michael. «MartinOdersky take(5) toList». Send More Paramedics, 6 agost 2010. [Consulta: 9 febrer 2012].
  21. Lattner, Chris. «Chris Lattner's Homepage». Chris Lattner, 2014-06-03. [Consulta: 3 juny 2014]. «The Swift language is the product of tireless effort from a team of language experts, documentation gurus, compiler optimization ninjas, and an incredibly important internal dogfooding group who provided feedback to help refine and battle-test ideas. Of course, it also greatly benefited from the experiences hard-won by many other languages in the field, drawing ideas from Objective-C, Rust, Haskell, Ruby, Python, C#, CLU, and far too many others to list.»
  22. «Timber/History». [Consulta: 7 octubre 2015].
  23. [Parallel Haskell: Projecte de dos anys per promocionar-ne l'ús en el món real](anglès)
  24. Haskell - Concurrència i paral·lelisme(anglès)
  25. Haskell98 Online Report(anglès)
  26. GHC - Notes de l'edició(anglès) GHC 7.0+ pren per defecte l'estàndard de llenguatge Haskell2010
  27. Haskell prima - Propostes per a l'evolució del llenguatge (anglès)
  28. Extensions del Haskell i compiladors que les implementen(anglès)
  29. Haskell2010 Online Report(anglès)
  30. 30,0 30,1 Anunciant Haskell 2010(anglès) Acord final sobre Haskell 2010
  31. Característiques del Haskell 2010 (anglès)
  32. Anunci del nou procés Haskell Prima, i del Haskell 2010 (anglès)
  33. Propostes per a Haskell 201x(anglès)
  34. Haskell wiki - Depuració(anglès)
  35. HaskellWiki - Stack Overflow(anglès)
  36. Simon Marlow - Why can't I get a Stack trace(anglès)
  37. El paquet safe(anglès)
  38. L'extensió Strict(anglès)
  39. Extensible Records(anglès)
  40. Extensió TRex de l'intèrpret Hugs
  41. 41,0 41,1 Well-typed - Polymorphism over record fields(anglès)
  42. 42,0 42,1 OverloadedRecordFields(anglès)
  43. 43,0 43,1 Obtenir el compilador GHC
  44. Linux - alternatives(anglès)
  45. GAlternatives - GUI per al selector d'alternatives a Linux(anglès)
  46. Comanda runghc (anglès)
  47. GHCi - guia d'usuari de l'intèrpret(anglès)
  48. GHCi - Declaracions de tipus i classes a l'intèrpret
  49. 49,0 49,1 49,2 Haskell.org - Lazy vs. Non-strict (anglès) Avaluació tardana i avaluació no-estricta
  50. 50,0 50,1 Haskell is a strict language
  51. 51,0 51,1 HaskellWiki - Thunk(anglès)
  52. Generalising Monads to Arrows(anglès)
  53. Haskell 2010(anglès) Secció 2.4 Identifiers and Operators
  54. Estructura lèxica del Haskell98 (anglès)
  55. Extensió UnicodeSyntax(anglès)
  56. H2010 - Identificadors i variables(anglès)
  57. 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.
  58. Haskell 2010(anglès) La producció consym està continguda en la qop dels operadors binaris infix (secció 3.4 Operator Applications)
  59. haskellWiki - Implicit parameters(anglès)
  60. Referència de sintaxi de Haskell2010(anglès)
  61. Haddock - Com documentar(anglès)
  62. Invocant Haddock - opcions(anglès)
  63. 63,0 63,1 Haskell 2010(anglès) Secció 1.4 Namespaces
  64. IEC.DLC - Sagnat
  65. 65,0 65,1 65,2 Fixity declarations(anglès) Precedències i associativitat dels operadors
  66. 66,0 66,1 66,2 Rank-2 types, ($), and the monomorphism restriction (anglès)
  67. Guia de classes estàndards - Instàncies derivades (anglès)
  68. Instàncies derivades - especificació (anglès)
  69. Prelude del H98 (Predefinits)(anglès) La majoria de les classes instanciables amb la clàusula deriving són aquí
  70. 70,0 70,1 Data.Eq(anglès)
  71. 71,0 71,1 71,2 71,3 Data.Ord(anglès)
  72. 72,0 72,1 72,2 Prelude#Enum(anglès)
  73. 73,0 73,1 73,2 Prelude#Bounded(anglès)
  74. 74,0 74,1 74,2 74,3 Data.Ix - classe dels índexs (anglès)
  75. 75,0 75,1 Text.Show(anglès)
  76. 76,0 76,1 Text.Read(anglès)
  77. Numeric(anglès)
  78. 78,0 78,1 Prelude#Num(anglès)
  79. 79,0 79,1 Data.Bits(anglès)
  80. 80,0 80,1 Prelude#Real(anglès)
  81. 81,0 81,1 Prelude#Integral(anglès)
  82. 82,0 82,1 Prelude#Fractional(anglès)
  83. 83,0 83,1 Prelude#RealFrac(anglès)
  84. 84,0 84,1 Prelude#RealFloat(anglès)
  85. 85,0 85,1 Prelude#Floating(anglès)
  86. 86,0 86,1 86,2 Data.Ratio(anglès)
  87. 87,0 87,1 Data.Fixed - reals de coma fixa(anglès)
  88. GHC - Extensions a la clàusula deriving(anglès)
  89. Data.Bool(anglès)
  90. Data.Char(anglès)
  91. Data.String(anglès)
  92. El paquet text(anglès)
  93. Data.Int(anglès)
  94. Haskell2010 - Especificació dels enters(anglès)
  95. Data.Word(anglès)
  96. Numeric.Natural(anglès)
  97. mòdul Data.Ratio(anglès)
  98. Data.Complex(anglès)
  99. Ambiguous Types, and Defaults for Overloaded Numeric Operations(anglès)
  100. Data.Word
  101. Instàncies de la classe Real
  102. L'extensió DuplicateRecordFields(anglès)
  103. Extensió NamedFieldPuns(anglès)
  104. Extensió RecordWildcards(anglès)
  105. Extensions de GHC - Record Puns(anglès) simplificació de sintaxi en l'encaix dels camps dels registres
  106. Tuples a Haskell98(anglès)
  107. Data.Tuple
  108. 108,0 108,1 Sinònims de tipus liberals(anglès)
  109. 109,0 109,1 Llistes - Nil i Cons(anglès)
  110. Data.List(anglès)
  111. Generalised (SQL-Like) List Comprehensions (anglès)
  112. FPComplete.com - List comprehension extensions (anglès)
  113. How to pick your string library in Haskell(anglès) Com escollir la biblioteca de cadenes de caràcters en Haskell
  114. biblioteca "text" de tires com a vectors de caràcters de 16 bits(anglès)
  115. Estructura del tipus Text a Data.Text.Internal(anglès)
  116. Overloaded string literals(anglès)
  117. Lambda-case(anglès)
  118. Aplicació parcial
  119. 119,0 119,1 El mòdul Data.Function
  120. Secció (tall) d'un operador binari en infix (anglès)
  121. GHC Guia d'usuari - senyal de compilació: -fwarn-incomplete-patterns(anglès) deshabilitada per defecte
  122. Pattern type signatures (anglès)
  123. Pattern guards(anglès)
  124. GHC users guide - Pattern synonyms(anglès)
  125. ghc users guide - View patterns(anglès)
  126. HaskellWiki - Let vs Where
  127. GHC - Implicit parameters(anglès)
  128. Functor al mòdul Data.Functor(anglès)
  129. Set is not a Functor(anglès)
  130. Sets, Functors and Eq confusion(anglès)
  131. Depuració al Haskell(anglès)
  132. Debug.Trace - Imprimir traces des del codi funcional(anglès)
  133. Debug.Trace.traceStack
  134. 134,0 134,1 134,2 134,3 haskellwiki - Orphan instance(anglès)
  135. haskellWiki Multiple instances(anglès)
  136. GHC - Stand-alone deriving(anglès)
  137. Haskellwiki - newtype
  138. 138,0 138,1 138,2 Extensió GeneralizedNewtypeDeriving (anglès)
  139. Keyword arguments in Haskell(anglès)
  140. DrIFT (anglès)
  141. Data.Derive (anglès)
  142. Manual de Data.Derive
  143. Tipus existencials (anglès)
  144. tipus existencials en Haskell(anglès)
  145. Haskell GHC - Perquè existencial(anglès)
  146. Quantificació existencial
  147. Tipus existencial a EHC/UHC amb la paraula reservada exists(anglès)
  148. Tipus existencial al JHC amb la paraula reservada exists(anglès)
  149. 149,0 149,1 149,2 Existentially quantified - Record constructors(anglès)
  150. 150,0 150,1 Tipus existencial (anglès)
  151. mòdul Data.Dynamic - tipatge dinàmic bàsic(anglès)
  152. MonadPlus reform proposal(anglès)
  153. 153,0 153,1 Biblioteca Base de GHC - vegeu mòduls marcats Base(anglès)
  154. Control.Category(anglès)
  155. 155,0 155,1 Control.Arrow(anglès)
  156. Introducció planera a "Haskell XML Toolbox" (anglès)
  157. Fletxes (Arrows) a GHC
  158. Fletxes (Arrows) a l'intèrpret Hugs
  159. mòdul Data.Traversable(anglès)
  160. mapM de Control.Monad(anglès)
  161. when i unless de Control.Monad(anglès)
  162. Throw / Catch, restriccions (anglès)
  163. Biblioteca exceptions(anglès)
  164. Biblioteca safe-exceptions(anglès)
  165. Control.Exception.evaluate(anglès)
  166. Catching exceptions(anglès)
  167. Crides d'error en Ent./Sort.(anglès)
  168. finally de Control.Exception(anglès)
  169. Novetats de la 7.6.1 - biblio. base(anglès) "The deprecated Control.OldException module has now been removed"
  170. Control.Exception(anglès)
  171. Control.Exception.catches
  172. Configuracions del RunTimeSystem(anglès)
  173. HaskellWiki - Debugging - Locating a failure in a library function(anglès)
  174. $(err') de File-location(anglès)
  175. Assercions(anglès)
  176. Paràmetres implícits especials(anglès)
  177. Notes de la versió 7.10.2(anglès)
  178. GHC 8.0.x Migration Guide(anglès)
  179. 179,0 179,1 Undefined: Bottom (No finalització)
  180. Data.IORef(anglès)
  181. Data.STRef(anglès)
  182. Control.Concurrent.MVar(anglès)
  183. Control.Concurrent.STM.TVar(anglès)
  184. Control.Concurrent.STM.TMVar(anglès)
  185. IORef's - referències mudables dins la mònada IO
  186. Top level mutable state (anglès) Estat de nivell global d'un programa
  187. Data.IORef atomicModifyIORef(anglès) L'ús d' atomicModifyIORef per protegir més d'una IORef està desaconsellat
  188. La mònada ST (anglès) efecte de canvis d'estat
  189. La mònada ST - referència(anglès)
  190. STRef's - referències a valors mudables dins la mònada ST
  191. Control.Monad.ST - Converting ST to IO(anglès)
  192. 192,0 192,1 Iteratee I/O(anglès)
  193. Viccionari anglès - El sufix -ee(anglès)
  194. Deterministic allocation and freeing of scarce resources(anglès) Allotjament determinista i alliberament de recursos escassos.
  195. Introducció als conduits(anglès)
  196. Explicit namespaces in import/export
  197. Importació d'instàncies
  198. Noms de mòdul jeràrquics(anglès)
  199. Running a compiled program (anglès) Paràmetres per al Run Time System
  200. H2010 Standard Prelude - funcions i tipus predefinits(anglès)
  201. H98 Standard Prelude - funcions i tipus predefinits(anglès)
  202. Prelude del GHC(anglès)
  203. API's de les biblioteques bàsiques del Haskell(anglès)
  204. Interfície dels vectors immutables(anglès)
  205. Interfície dels vectors mudables(anglès)
  206. Paquet vector (anglès)
  207. mòdul Data.Array.Parallel(anglès)
  208. Vectors de tractament paral·lel (paral·lelisme de dades)(anglès)
  209. Vectors en memòria amb accés de nivell baix(anglès)
  210. Vectors d'elements encapsulats(anglès)
  211. Vectors d'elements Unboxed (No encapsulats)(anglès)
  212. HaskellWiki - DiffArrays(anglès)
  213. Hackage - DiffArrays - Vectors per diferència(anglès)
  214. Vectors en memòria global (mònada IO)(anglès)
  215. Vectors en memòria local (mònada ST)(anglès)
  216. Data.Ix(anglès)
  217. Altres tipus de vectors a GHC (anglès)
  218. Extensions comunes a GHC i Hugs (anglès)
  219. Classes multiparàmetre i dependències funcionals(anglès)
  220. Famílies de tipus - Tipus associats(anglès)
  221. Monomorphism_restriction(anglès)
  222. Polimorfisme
  223. Rank-N types(anglès)
  224. Switching off the Monomorphism restriction(anglès)
  225. Let Generalisation in GHC 7.2(anglès) Monomorfisme als lligams locals
  226. Tipus de Dades Algebraics Generalitzats (GADTs)
  227. System.IO.Unsafe - unsafePerformIO(anglès)
  228. The Reader monad(anglès)
  229. Classifying Types(anglès)
  230. 230,0 230,1 230,2 230,3 GHC - Heap objects(anglès)
  231. HaskellWiki - Kind (anglès)
  232. Unlifted Data Types(anglès)
  233. El tipus Type i els seus coŀlegues(anglès)
  234. Explicitly-kinded quantification(anglès)
  235. 235,0 235,1 Promoció de tipus(anglès)
  236. El kind Constraint
  237. Equality constraints(anglès)
  238. Constraint Kinds for GHC(anglès)
  239. GHC - Famílies de tipus
  240. HaskellWiki - Type Families(anglès)
  241. mòdul Data.MonoTraversable(anglès)
  242. Closed type families(anglès)
  243. Dependent Types in Haskell(anglès)
  244. 244,0 244,1 244,2 Univ. de Girona - Introducció a la prog. funcional
  245. FOLDOC - Head Normal Form(anglès)
  246. 246,0 246,1 246,2 246,3 FOLDOC - Weak Head Normal Form(anglès)
  247. 247,0 247,1 HaskellWiki - Weak Head Normal Form
  248. El paquet deepseq(anglès)
  249. el mòdul Control.DeepSeq.Generics(anglès)
  250. H2010 Language report - Strictness Flags(anglès)
  251. Eficiència i rendiment en tipus de dades (anglès)
  252. 252,0 252,1 GHC - Strict i StrictData(anglès)
  253. BangPatterns - Avaluació estricta dels paràmetres (anglès)
  254. RealWorldHaskell - Optimitzacions - Strictness and Tail recursion(anglès)
  255. Haskellwiki - Tail recursion
  256. Tail recursion modulo cons
  257. Limitations of strictness analysis (anglès)
  258. Lazy pattern match(anglès)
  259. Lazy pattern match - Implications(anglès)
  260. Haskell Wiki - Guia d'usuari de HUnit(anglès)
  261. paquet HUnit(anglès)
  262. Haskell Wiki - Introducció al QuickCheck(anglès)
  263. biblio. QuickCheck(anglès)
  264. Solving the diamond dependency problem(anglès) Solucionant el problema de la dependència en diamant
  265. Polymorphism over record fields(anglès)
  266. OverloadedRecordFields - MagicClasses(anglès)
  267. Regionalització de recursos(anglès)
  268. Paquets amb Mónades per a la regionalització
  269. HaskellWiki Cabal(anglès)
  270. Repeat after me: "Cabal is not a Package Manager"(anglès)
  271. Com crear un paquet Cabal
  272. Cabal - Com instal·lar un paquet Cabal (anglès)
  273. Package versioning policy(anglès) Política de versionament dels paquets
  274. 274,0 274,1 Hackage - Rebost oficial d'aplicacions i biblioteques empaquetades amb Cabal (anglès)
  275. Cabal - Preguntes freqüents(anglès)
  276. Afegint paquets a una instal·lació Hugs (anglès)
  277. 277,0 277,1 Cabal FAQ - Conflictes de dependències(anglès)
  278. An introduction to cabal sandboxes(anglès)
  279. cabal-dev (anglès)
  280. HaskellWiki - Stackage(anglès)
  281. [1](anglès)
  282. [2](anglès)
  283. What's the point of Stackage LTS?(anglès)
  284. yesodweb.com - Stable, Vetted Hackage(anglès)
  285. FPComplete Haskell Center(anglès)
  286. Com escriure (i empaquetar) un programa en Haskell(anglès)
  287. Actualitzant paquets a noves versions de GHC i biblioteques(anglès)
  288. HaskellWiki - ghc-pkg(anglès)
  289. Haskell wiki - Package versioning policy (anglès)
  290. cabal-progdeps (anglès)
  291. yackage (anglès)
  292. Docum de la Haskell Platform(anglès)
  293. The Haskell Platform FAQ (anglès)
  294. Paquet hlint que genera el programa del mateix nom(anglès)
  295. Leksah - Entorn gràfic de desenvolupament (IDE) per a Haskell (anglès)
  296. Instal·lació de l'entorn de desenvolupament Leksah(anglès)
  297. Endollable per a l'entorn de desenvolupament Eclipse (anglès)
  298. Fòrum StackOverflow - Visual Haskell(anglès)
  299. HaskellWiki - Internacionalització dels programes en Haskell(anglès)
  300. Implementacions de Haskell (anglès)
  301. Compilador GHC "Compilador Haskell de Glasgow" (anglès)
  302. Intèrpret del Glorious Compilador Haskell de Glasgow (anglès)
  303. Concurrència con Haskell(castellà)
  304. Glasgow Parallel Haskell(anglès)
  305. Un Tast de Haskell - Memoria Transaccional per Software (anglès)
  306. Haskell Wiki - Memoria Transaccional per Software(anglès)
  307. Rerefons del GHC (anglès)
  308. HaskellWiki - Data Parallel Haskell(anglès)
  309. Nested Data Parallellism in Haskell(anglès)
  310. Hugs intèrpret de Haskell (anglès)
  311. Registres extensibles TRex de l'intèrpret Hugs (anglès)
  312. Compilador nhc98 (anglès)
  313. Utrecht Haskell Compiler (anglès)
  314. UHC/EHC rerefons de compilació (anglès)
  315. York Haskell Compiler(anglès)
  316. Generació de codi Javascript des d'YHC(anglès)
  317. Traçador de crides Hat(anglès)
  318. Jhc Haskell Compiler (anglès)
  319. Web de John Meacham creador del compilador JHC(anglès)
  320. LHC Haskell Compiler(anglès)
  321. Preprocessadors de Haskell(anglès)
  322. wiki de HaLVM: The Haskell Lightweight Virtual Machine(anglès)
  323. Run Haskell on Xen - No Operating System Required!(anglès)
  324. Adam Wick - The Haskell Lightweight VM
  325. The Compilador Haskell de Glasgow and LLVM(anglès)
  326. Rerefons LLVM de GHC(anglès)
  327. 327,0 327,1 El llenguatge Frege(anglès)
  328. 328,0 328,1 Wiki del projecte Frege(anglès)
  329. Hola Món amb Frege
  330. UHC Jazy - rerefons a la màq. virtual Java(anglès)
  331. El compilador Haste (anglès)
  332. El compilador GhcJs (anglès)
  333. YHC/Javascript (anglès)
  334. Rerefons Javascript del compilador UHC(anglès)
  335. 335,0 335,1 PureScript.org(anglès)
  336. rerefons CLR a UHC(anglès)
  337. Running Haskell on the CLR(anglès)
  338. Anunciant GHC per a l'IOS (anglès)
  339. El Compilador del Deixeble Disciplinat DDC(anglès)
  340. Llenguatge OOHaskell(anglès)
  341. Llenguatge O'Haskell(anglès)
  342. Llenguatge Timber(anglès)
  343. Timber - Limitacions i problemàtiques(anglès)
  344. Open Quark(anglès)
  345. OpenQuark (llenguatge CAL sobre JVM)(anglès)
  346. Viquipèdia anglesa - Quark Framework (implement. de Haskell per a la JVM)(anglès)
  347. Bussiness Objects a la wikip. francesa (francès)
  348. Jaskell(anglès)

Enllaços externs[modifica | modifica el codi]

A Wikimedia Commons hi ha contingut multimèdia relatiu a: Haskell Modifica l'enllaç a Wikidata