Compilador Haskell de Glasgow

(S'ha redirigit des de: GHC)

ghc, ordre d'ordinador que respon a la sigla de "Glasgow Haskell Compiler", en català Compilador Haskell de Glasgow, és un compilador nadiu de codi lliure per al llenguatge de programació funcional Haskell, el qual va ser originalment desenvolupat a la universitat de Glasgow per Simon Peyton Jones i Simon Marlow. El desenvolupament continua actualment sota l'auspici del Departament d'Investigació de Microsoft (Microsoft Research), al Regne unit on treballa Peyton Jones. GHC ha pres humorísticament el sobrenom "Glorious Haskell Compiler".

Compilador Haskell de Glasgow
Modifica el valor a Wikidata

Tipuscompilador, programari lliure, programari de codi obert, programari lliure i de codi obert i compilador optimitzador Modifica el valor a Wikidata
Versió inicial1989 Modifica el valor a Wikidata
Versió estable
9.10.1 (10 maig 2024) Modifica el valor a Wikidata
Llicènciallicència BSD de 3 clàusules Modifica el valor a Wikidata
Part deHaskell Platform (en) Tradueix Modifica el valor a Wikidata
Característiques tècniques
Sistema operatiuLinux, FreeBSD, NetBSD, OpenBSD, Solaris, macOS, Microsoft Windows i DragonFly BSD Modifica el valor a Wikidata
Escrit enC i Haskell Modifica el valor a Wikidata
Equip
Desenvolupador(s)Universitat de Glasgow Modifica el valor a Wikidata
Més informació
Lloc webhaskell.org… (anglès) Modifica el valor a Wikidata
Free Software DirectoryGhc Modifica el valor a Wikidata

GitHub: ghc

El nom ghc segueix el costum antic d'anomenar les ordres amb un identificador curt d'una a tres lletres, on la 'c' designa compilador, la 'h' designa el llenguatge i la primera lletra, la ciutat de la universitat que el desenvolupà originàriament (com també ho fa mmc: "Compilador Mercury de Melbourne",[1] o bé yhc:"Compilador Haskell de York"[2] o bé uhc:"Compilador Haskell d'Utrecht").[3]

El compilador també està escrit en Haskell (una tècnica coneguda com a bootstrapping), però el nucli de sistema per a Haskell està escrit en C i una adaptació del C--[4][5] (versió de C, no per a l'ús humà sinó específica per a sortida de compiladors, com a interfície independent del maquinari) referida com a Cmm en el codi del compilador.[6] L'última versió del compilador compleix amb l'estàndard més nou del llenguatge, que és, ara per ara, el Haskell 2010.[7] GHC està disponible per a moltes plataformes, incloent Windows i la majoria de sistemes Unix (com les diferents distribucions de GNU/Linux), Mac OS X i la majoria d'arquitectures de processador.

Aquest incloïa, fins a la versió 6.10.x, la majoria d'extensions al Haskell estàndard, fins i tot la biblioteca STM, que serveix per fer transaccions atòmiques (o tot o no res) de dades compartides entre diferents fils d'execució.

A partir de GHC 6.12 les biblioteques ja no venen amb el compilador sinó que es distribueixen en edicions diferides, dins la Plataforma Haskell ("The Haskell Platform").[8][9] La corresponent a GHC 6.12.1 ja està disponible. Entre altres millores, suporta tractament d'entrada/sortida per a caràcters no anglosaxons, segons la codificació del sistema subjacent[10]

HaskellStack subordina GHC

Stackage és un rebost que per cada versió de GHC guarda instantànies d'un conjunt de biblioteques compatibles amb compilació verificada, evitant la problemàtica de seleccionar per cada biblioteca una versió que encaixi en els requeriments de les restants.

HaskellStack (2015) possibilita el desenvolupament multiversió respecte de GHC, i descarrega automàticament la versió de GHC corresponent al rebost Stackage més recent que inclogui les dependències (biblioteques) d'una aplicació si hi ha fitxer de projecte .cabal al directori, proporcionant una eina que subordina i encapsula diverses versions de GHC i dels rebostos per al desenvolupament d'aplicacions en capsa de proves (Sandbox).

També proporciona un conjunt de plantilles d'aplicacions (comanda: stack templates) per engegar projectes nous (stack new projecte plantilla).[11][12][13]

Biblioteques estàndard i correspondències de versions

modifica

Biblioteques de la darrera versió.[14]

Normalment la propietat "since version X.X.X.X" de la doc. de les funcions ofereix la versió inicial de la biblioteca on es van definir.

Per saber quina versió mínima de GHC suporta una biblioteca estàndard, cal consultar les taules.

  • taula de versions corresp. a GHC de 6.6 a 7.2[15]
  • taula de versions més recents.[16] Es pot consultar versions anteriors al llistat d'actualitzacions de la pàg.

Compilar codi antic Haskell98 amb GHC

modifica

Per compilar projectes antics en Haskell98 cal tenir en compte que

  • el llenguatge per defecte ja no és el mateix (ara és Haskell2010), caldrà afegir al fitxer de projecte (.cabal):
Default-language: Haskell98
  • especificar la biblioteca haskell98 a les dependències

Si no fa servir excepcions, potser en tindreu prou amb els mòduls de la biblio haskell98

Build-Depends:	haskell98, ...

Si fa servir excepcions caldrà incloure el paquet base, i desambiguar, a les importacions, la biblioteca d'origen d'alguns mòduls Prelude, Numeric, ... presents a totes dues, com s'explica més avall.

Build-Depends:	base, haskell98, ...
  • el sistema d'excepcions ja no és el mateix.
  • Haskell98 agrupa les excepcions en un tipus Exception, unió discriminada dels diferents tipus d'excepcions. La pega és que és un grup tancat.
-- Haskell98
data Exception 
 = 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
 ...
  • Haskell2010 modela les excepcions en una interfície, permetent caracteritzar com a excepcions, diferents tipus on l'usuari pot especificar els camps que convingui, requerint que han d'implementar les interfícies de les classes de tipus Exception, Show i Data.Typeable.
  • La implementació de la classe Exception (del H2010) es pot derivar automàticament si el tipus implementa Show, afegint la derivació de la classe Data.Typeable i esmentant l'extensió de llenguatge DeriveDataTypeable.
  • Hi ha un tipus genèric per recollir les excepcions (del H2010) no tractades, SomeException definit de manera existencial amb l'excepció com a component.
-- Haskell2010
data SomeException = forall e. Exception e => SomeException e

-- classe x convertir en excepció un tipus definit x l'usuari
class (Typeable e, Show e) => Exception e where
 toException :: e -> SomeException
 fromException :: SomeException -> Maybe e

Exemple de tractament d'excepcions al H98 i H2010 aquí.

Les excepcions antigues, des de GHC 6.10.1 van passar al mòdul Control.OldException del paquet base,[17] s'hi van conservar fins a la versió 7.4.2[18] i ha estat eliminat a la versió GHC-7.6.1.[19]

Primer de tot cal descarregar[20] el compilador de GHC-7.4.2, l'últim que conté el mòdul Control.OldException, configurar i instal·lar, en una carpeta de l'usuari (subdir. de $HOME):

# en un sistema UNIX
cd carpeta-d-extracció-del-paquet-GHC-7.4.2

./configure --prefix=$HOME/carpeta-d-instal·lacio
make install

# establir variables d'entorn
GHC_VER=7.4.2
GHC_HOME=$HOME/carpeta-d-instal·lacio/ghc-$GHC_VER
export GHC=$GHC_HOME/bin/ghc # camí de GHC per a fitxers de comandes (.sh | Makefile)
export PATH=$GHC_HOME/bin:$PATH # camí d'executables
export LD_LIBRARY_PATH=$GHC_HOME/lib/ghc-$GHC_VER/:$LD_LIBRARY_PATH # camí de biblioteques de càrrega dinàmica (.so | .dll)
export LIBRARY_PATH=$GHC_HOME/lib/ghc-$GHC_VER/:$LIBRARY_PATH # camí de biblioteques per a compilació
  • Caldrà reanomenar les importacions de Exception i també Control.Exception
import Control.OldException ... -- en comptes de Control.Exception
  • Després, en compilar un mòdul H98 apareix un error respecte el Prelude (mòdul de predefinits):
 Ambiguous module name ‘Prelude’:
 it was found in multiple packages: base haskell98

Una manera de desambiguar-ho és explicitant a cada mòdul l'origen del Prelude desitjat.

-- Al fitxer de projecte (.cabal) indicar:
Extensions: NoImplicitPrelude -- no volem el Prelude per defecte
 PackageImports -- volem especificar el paquet als "import"
-- als fitxers fonts:
import "haskell98" Prelude

Gestió de memòria

modifica

Vegeu doc.[21] GHC compila per defecte amb memòria dinàmica il·limitada. Per establir-ne límits vegeu el senyal de compilació -M.[22] Disposa d'un recollidor de memòria brossa generacional (per defecte dues gen.), amb control d'envelliment (nombre de recol·leccions abans de passar a la generació següent) de dos passos per generació.[23]

L'allotjament es fa dins de blocs reutilitzables, partions d'un megabloc que és el tros que es reclama al sistema quan cal i que només li retornen en havent acabat.[24]

Megabloc
bloc d'un megabyte, que l'allotjador de memòria demana de cop.
Capability
fil d'execució lligat a un del sistema, un per cada processador elemental, sobre els quals s'executen els fils d'execució lleugers del planificador del Run Time System multiprocessador (quan es fa servir l'opció -threaded).
Nursery (maternitat)
zona d'allotjament de dades noves (una per capability/P.E.), del recollidor de memòria brossa generacional.

A l'inici les dades s'allotgen a la maternitat (ang:nursery) (conjunt fix de blocs) i se'n disposa d'una per capability.[25] després passen a la generació 0, i següents[26] tantes com s'estableixi a les opcions de l'intèrpret d'ordres per al RunTimeSystem.

Per la generació més vella, es pot triar el sistema de recollida de la brossa, entre el mètode de Cheney (aturar la recollida i copiar dades a un espai net) per defecte i el mètode de compactació que estalvia espai però és més lent. Les altres generacions fan servir el mètode de Cheney.[27]

  • Per forçar la recollida de brossa quan convingui: System.Mem.performGC[28]
  • Referències dèbils (ang:weak pointer) per a taules de memoïtzació[29][30] i també per a MVar's (Mutable variable) amb mkWeakMVar.[31]

Fent servir biblioteques de relligat dinàmic

modifica

A partir de GHC >= 6.12[32][33][34] Cal que GHC s'hagi compilat amb ./configure --enable-shared

 # relliga amb les versions compartides (no estàtiques) (bib.so o bé bib.dll segons sistema op.) del RunTimeSystem i dels paquets

 ghc --make -dynamic Main.hs

Paral·lelisme

modifica

Novetats al bloc butlletí "Parallel Haskell Digest"[35]

Paral·lelisme de tasques

modifica

Primitives de paral·lelisme - Compilació per a processadors multicor

modifica

Vegeu-ho a Haskell concurrent#Primitives de paral·lelisme - Compilació per a processadors multicor

Encadenament de càlculs asíncrons - la mònada Par
modifica

Vegeu-ho a Haskell concurrent#Encadenament de càlculs asíncrons - la mònada Par

El paquet meta-par ofereix una versió de la mònada Par per compartir recursos de hardware amb un planificador de tasques SMP.[36]

Paral·lelisme en operacions IO - El functor aplicatiu Concurrently
modifica

Vegeu-ho a Haskell concurrent#Operacions d'Entrada/Sortida asíncrones i simultànies

Paral·lelisme de dades

modifica

Vectors pluridimensionals de procés paral·lel (Regular parallel arrays)

modifica

Un primer nivell de tractament paral·lel s'ofereix mitjançant el paquet Repa ("Regular parallel arrays")[37][38] de tractament en paral·lel de vectors pluridimensionals. Exemple més avall.

Data Parallel Haskell

modifica

GHC implementa el, més complex, paral·lelisme de dades niuat, trad. de "Nested Data Parallelism"[39][40][41] basat en treballs de Guy E. Blelloch.[42]

Per a proves cal instal·lar el paquet dph-examples.

cabal install dph-examples

#si la versió de GHC és la 7.4.x i dona error en compilar el paquet ''bmp'',
# caldrà provar forçant la versió anterior a la que peta.
cabal install dph-examples "--constraint= bmp < 1.2.3.1"

Cal fixar-se en la versió instal·lada (diferent segons la versió de GHC) i descarregar-ne les fonts per investigar.

# per obtenir-ne la versió instal·lada
ghc-pkg list | grep dph-examples

Per desenvolupar-hi cal tenir en compte que...

  • Cal separar les operacions per mòduls segons si són o no vectoritzades
  • A la interfície el tipus ha de ser (PArray t)
  • El tipus vector ([: t :]) és NO-polimòrfic i cal especificar el tipus de l'element, com ara [: Double :], les operacions sobre els tipus primitius no estan sobrecarregades i per cada tipus cal importar-ne el mòdul d'operacions corresponent (per ex.: Data.Array.Parallel.Prelude.Double).

Vegeu exemple més avall.

SIMD - Single Instruction, Multiple Data - Ús de les instruccions de CPU vectoritzades

modifica

Aprofitament de les instruccions de dades vectoritzades dels processadors (MMX, 3DNow!, SSE, Altivec, AVX, ...) per al procés a CPU de corrents de dades multimèdia.

Tipus i operacions primitius SIMD a GHC.Prim.[43]

Pàg. inicial sobre SIMD a GHC (Hi ha una branca de GHC per a desenvolupament en SIMD, però els paquets esmentats més avall i l'exemple funcionen en versions estàndard de GHC utilitzant el rerefons de compilació LLVM).[44]

Paquets d'interfície SIMD: simd,[45] primitive-simd.[46]

Exemple amb dades vectoritzades de 4 Floats (4x32 bits), per a un ordinador amb SIMD sse2 de 128 bits (Pentium 4 o posterior). Compilat amb GHC v. 7.10.1. Requereix l'ús del rerefons de compilació LLVM (compilar amb l'opció -fllvm).

 # la versió del paquet "simd" del Hackage té un error a la funció unVectorizeUnboxedX4
 # cal baixar la del GitHub
 git clone https://github.com/mikeizbicki/simd
 cd simd
 cabal install --allow-newer # (--allow-newer): ignora els límits superiors de les dependències
{-# OPTIONS_GHC -fllvm #-} -- opció per compilar amb el rerefons de compilació LLVM
{-# LANGUAGE PackageImports, ExistentialQuantification, StandaloneDeriving #-}
import "vector" Data.Vector.Unboxed as V
import "simd" Data.SIMD (X4, vectorizeUnboxedX4, unVectorizeUnboxedX4)
import Control.Monad as M

-- Hi ha versions de tipus (X<n> a), n ∈ {4,8,16} per a paquets SIMD de n elements de tipus 'a'
-- amb instàncies de les classes numèriques definides per als tipus (Xn Float), (Xn IntN), ...

-- vectorizeUnboxedX4 :: V.Vector a -> V.Vector (X4 a) -- simd-vectoritza (empaqueta els elements) un vector Unboxed
-- unVectorizeUnboxedX4 :: V.Vector (X4 a) -> V.Vector a -- inversa de vectorizeUnboxedX4

-- hi ha un error a la funció unVectorizeUnboxedXn del paquet simd-0.1.0.1 (a tots els mòduls SIMD<n>),
-- però ja està arreglat a la versió que hi ha al 'github'

default (Int, Float)

mostra :: Int -> V.Vector Float
mostra n = V.fromList [1 .. fromIntegral n]

mkFloatVec :: V.Vector Float -> V.Vector (X4 Float) -- comprova i simd-vectoritza un vector normal de 'floats'
mkFloatVec vec
 | V.length vec `mod` 4 == 0 = vectorizeUnboxedX4 vec
 | otherwise = error "mkVec: el nombre d'elements ha d'omplir els paquets completament"

data Obj = forall a. Show a => Obj a -- objectes presentables

deriving instance Show Obj -- la derivació d'instàncies dels objectes existencials va separada

main = do
 let v1 = mkFloatVec v0
 v2 = V.map (/ 2.0) v1
 v3 = unVectorizeUnboxedX4 v2 -- cal la versió del GitHub perquè funcioni correctament

 -- com que v0..v3 són de tipus diferents, farem una llista d'objectes presentables
 M.forM_ [Obj v0, Obj v1, 
 Obj v2, Obj v3] print -- print = show >>> putStrLn
 where
 v0 = mostra 8

dona:

Obj [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0]
Obj [(1.0,2.0,3.0,4.0),(5.0,6.0,7.0,8.0)]
Obj [(0.5,1.0,1.5,2.0),(2.5,3.0,3.5,4.0)]
Obj [0.5,1.0,1.5,2.0,2.5,3.0,3.5,4.0]

Paral·lelisme GPGPU

modifica

En l'execució de rutines sobre vectors de dades, ús de la GPU per l'execució simultània del nucli d'iteració sobre els elements dels paràmetres vectors, repartint automàticament valors del cursor (que indexa els paràmetres vectors) als diferents processadors elementals.

  • Biblioteca Accelerate[47][48] amb suport per a rerefons CUDA,[49] OpenCL[50] i RePa[51] (per a CPUs), i genera nuclis (d'iteracions en paral·lel) per a GPU/CPU partint d'un llenguatge incrustat (EDSL) en Haskell. La biblioteca Accelerate va ser desenvolupada a la Universitat de Nova Gal·les del Sud (Austràlia) amb els auspicis de NVIDIA, desenvolupant inicialment per a dispositius NVIDIA-CUDA.[47]
  • Biblio. OpenCLWrappers facilita l'ús de lligams amb biblioteques externes que implementin l'estàndard OpenCL,[52] article: OpenCL from Haskell[53]
  • Simultanejant càrregues de treball en CPU i GPU. Vegeu "How to write hybrid CPU/GPU programs with Haskell".[54]
Exemple GPGPU d'ús de la biblioteca Accelerate
modifica
  • Mòdul principal amb intèrpret de simulació del llenguatge incrustat (EDSL: "embedded domain specific language").[55]
  • Mòduls per al paral·lelisme a GPUs
  • RerefonsCUDA de NVIDIA.[56] Requereix tenir instal·lat el compilador NVCC del ToolKit de NVIDIA.[57]
  • Rerefons OpenCL de HIPERFIT.[58] Cal tenir instal·lat el kit de desenvolupament (SDK) del fabricant de la GPU. L'estàndard OpenCL està implementat tant per NVIDIA[59] com per AMD-ATI, però AMD a més d'implementar-lo en dispositius GPU, tracta la CPU multicor com un altre dispositiu de paral·lelisme.[60]
  • Rerefons OpenCL basat en el llenguatge per al paral·lelisme Icc-Cilk de Intel.[61]
  • Mòduls per al paral·lelisme a CPUs multicor
  • Rerefons biblioteca RePa (Regular Parallel Arrays).[62]
  • Rerefons via LLVM.[63]
cabal install accelerate # instal·la paquet base amb simulador (intèrpret del llenguatge de domini específic)

# si teniu targeta gràfica NVIDIA amb capacitat GPGPU i heu instal·lat el kit de desenvolupament CUDA
CUDA_HOME=/usr/local/cuda-7.0 
cabal install cuda \ 
 --extra-include-dirs=$CUDA_HOME/include \ 
 --extra-lib-dirs=$CUDA_HOME/lib64

cabal install accelerate-cuda

La versió per a OpenCL és al GitHub i el funcionament bàsic és idèntic.

{-# LANGUAGE CPP, PackageImports #-}
import Data.Array.Accelerate as A

-- biblioteques "accelerate" de procés paral·lel

#define PROC_PAR_CUDA 1 /* maquinari NVIDIA */
#define PROC_PAR_HIPERFIT_OPENCL 2 /* maquinari que suporta l'estàndard OPENCL */
#define PROC_PAR_INTEL_OPENCL 3 /* maquinari INTEL */ 
#define PROC_PAR_INTERPRET 0 /* sense maquinari de procés paral·lel (simulació per l'intèrpret) */

#define BIBLIO PROC_PAR_CUDA /* assigneu la vostra */ 

#if BIBLIO == PROC_PAR_CUDA
import qualified "accelerate-cuda" Data.Array.Accelerate.CUDA as ProcPar

#elif BIBLIO == PROC_PAR_HIPERFIT_OPENCL
import qualified "accelerate-opencl" Data.Array.Accelerate.OpenCL as ProcPar

#elif BIBLIO == PROC_PAR_INTEL_OPENCL
import qualified "accelerate-icc-opencl" Data.Array.Accelerate.OpenCL as ProcPar

#elif BIBLIO == PROC_PAR_INTERPRET
-- intèrpret del llenguatge simulant un procés paral·lel (per manca de maquinari)
import qualified "accelerate" Data.Array.Accelerate.Interpreter as ProcPar
#endif

-- `Acc` (:: Type -> Type) designa el context del dispositiu accelerador

-- `use` (:: a -> Acc a) carrega un paràmetre a la memòria del dispositiu accelerador

-- `run` (:: Acc a -> a) executa el càlcul a l'accelerador, i descarrega el resultat

-- vectors de dades amb indicador de dimensionalitat (tipus Shape)
xs, ys :: Array DIM1 Float
xs = A.fromList (Z :. 2) [1, 2]
ys = A.fromList (Z :. 2) [2, 2]

-- type Scalar a = Array DIM0 a

-- producte escalar de vectors carregats al dispositiu accelerador (Acc a)
prodEscalar :: Acc (Array DIM1 Float) -> Acc (Array DIM1 Float) -> Acc (Scalar Float)
prodEscalar xs ys = A.fold (+) 0 (A.zipWith (*) xs ys)

-- prova per al dispositiu de paral·lelisme principal (n'hi pot haver diversos GPU's o bé CPU)

-- càlcul al dispositiu: carrega dades amb 'use'; 'run' executa i descarrega el resultat
càlcul :: Scalar Float
càlcul = ProcPar.run $ prodEscalar (use xs) (use ys)

main = print càlcul

Cal compilar amb la versió multi-tasca (opció -threaded) del RunTimeSystem

$ ghc --make -threaded prova.hs
$ ./prova
Array (Z) [6.0]

Concurrència

modifica
  • MVar's - Mutable/Mailbox Variable—variables protegides, aprofitant la concurrència basada en bústies d'un sol element.
  • forkIO / forkOS
  • Software Transactional Memory
  • Futurs—Encadenament de càlculs asíncrons - la mònada Par
  • Operacions d'Entrada/Sortida asíncrones i simultànies
  • Cloud Haskell (computació distribuïda, a l'estil del sistema d'actors distribuïts de l'Erlang)
  • Biblioteca Actor
  • Communicating Haskell Processes (CSP)

Vegeu Haskell concurrent.

Metaprogramació - Template Haskell

modifica

Extensió de GHC que permet l'anàlisi i generació de codi o bé Haskell o bé a expressions del tipus algebraic de l'arbre d'operacions (AST) d'un programa, com els generats internament pel compilador de Haskell.[64]

Avaluació en temps de compilació

modifica

El prefix "$" a un terme o bé a una expressió entre parèntesis, $terme o bé $(expr), s'anomena crida splice o d'inserció, i vol dir que cal avaluar l'expressió en temps de compilació i substituir-la, en el codi, pel seu valor. Exemple: #Punts de control al codi amb Loch-TH

Quasi-Quotations (Plantilles per a gramàtiques DSL)

modifica

Construcció[65] que s'avalua en temps de compilació, que permet incrustar texts convertibles a codi, escrits en gramàtiques anomenades "DSL" (Domain Specific Language), específiques per a una temàtica o domini d'aplicació, generant, per metaprogramació, expressions inter-operables amb les del mòdul que l'allotja, per exemple, plantilles HTML amb interpolació d'expressions vàlides en l'àmbit d'avaluació de la plantilla.[66]

Sintaxi
Les acotacions QuasiQuotation utilitzen una sintaxi anomenada Claus d'Oxford [|... |] o bé [prefix|... |] on el nom del prefix indica el registre que aporta els traductors del text acotat per les claus.[67]
Context
Una acotació QuasiQuotation pot descriure, segons el lloc on s'utilitza, una expressió, o bé un patró, o bé un tipus, o bé una llista de declaracions a nivell de mòdul, i, per traduir-la, s'utilitzarà el traductor corresponent al context, component del tipus descrit tot seguit.
Tipus
Un tipus quasiQuoter es defineix com un registre amb traductors opcionals per cadascun dels 4 possibles contextos en què es pretengui inserir, assignant als altres el valor undefined.[68]
La seva definició ha d'aportar funcions per la traducció del text a expressions del llenguatge Template Haskell que incorpora combinadors per generar clàusules de codi Haskell (metaprogramació).[69][70][71]
-- tipus del registre que aporta els traductors d'una plantilla on s'esmenta com a prefix [elMeuTraductor|...|]
data QuasiQuoter = 
 QuasiQuoter { -- ha d'incloure algun dels camps següents:
 quoteExp :: String  Q Exp, -- traductor en context d'expressions (metaprogramació d'expressions)
 quotePat :: String  Q Pat, -- traductor en context de patrons (metaprogramació de patrons)
 quoteType :: String  Q Type, -- traductor en context de tipus (metaprogramació de tipus)
 quoteDec :: String  Q [Dec] -- traductor en context de declaracions (metaprogramació de declaracions)
 }
elMeuTraductor = QuasiQuoter { quoteExp = tradCasDExpressions, quotePat = tradCasDePatrons, ...}

Exemple senzill: document tot seguit

modifica

Traductor simple per incloure textos multilínia en expressions (estil hereDoc o document-tot-seguit), com a literals, amb la funció stringE de T.H.[72]

  • les funcions de traducció per als contextos que no interessen es deixen undefined.
  • l'ús i la definició han d'estar en mòduls separats.
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-missing-fields #-} -- que GHC no es queixi pel registre QuasiQuoter incomplet

module ElMeuDocTotSeguit where
 import Language.Haskell.TH (stringE)
 import Language.Haskell.TH.Quote (QuasiQuoter(..))

 docTotSeguit = QuasiQuoter {quoteExp = stringE} -- aporta només el traductor per al context d'expressions
-- ús
{-# LANGUAGE QuasiQuotes #-}
module Main where
 import ElMeuDocTotSeguit (docTotSeguit)

-- format a GHC 7+ :
 elMeuTextPluriLínia = [docTotSeguit|text de la primera línia
segona línia i següents
|]

-- format a GHC 6.12 (cal prefixar el traductor amb '$'): [$docTotSeguit|text|]

Exemple: Incrustació de HTML amb interpolació d'expressions

modifica

De les biblioteques del Yesod.[66]

La plantilla shamlet tradueix, a expressions del Template Haskell, un llenguatge de marques afegint automàticament les marques de tancament segons el sagnat.

Permet la interpolació d'expressions Haskell delimitades amb claus prefixades per un caràcter que determina la funcionalitat.

  • Les claus "#{" ... "}" inclouen expressions avaluables quin tipus resultant ha de ser convertible implementant la classe Text.Blaze.ToMarkup.
  • Les claus "^{" ... "}" permeten la inclusió per referència d'altres sub-plantilles de tipus coincident amb la que les allotja.
{-# LANGUAGE PackageImports, QuasiQuotes, OverloadedStrings #-}
module Plantilla (pàgina) where

-- "Hamlet" fa referència a un trosset de Html (és un joc de paraules amb les lletres de "HTML")
import "shakespeare" Text.Hamlet (Html, shamlet)
import Data.Text (Text)

nom = "Biel" :: Text
títol = "Títol de la pàgina" :: Text

data TAmic = Amic {amicNom::String, amicEdat:: Int}
elsAmics = [Amic "Joan" 30, Amic "Montse" 40]

-- Plantilla per al cos de la pàgina
-- amb #{expr} interpola l'avaluació de l'expressió per a tipus que implementin la classe Text.Blaze.ToMarkup

cosDeLaPàg :: [TAmic] -> Html
cosDeLaPàg amics = [shamlet|
 <p>Hola el meu nom és #{nom}
 $if null amics
 <p>Ho sento, no hi ha amics.
 $else
 <p>Els meus amics són:
 <ul>
 $forall amic <- amics
 <li>#{amicNom amic} que  #{amicEdat amic} anys
|]

-- Plantilla principal
-- amb ^{expr} interpola una crida a una altra plantilla QQ del mateix tipus de resultat.

pàgina :: Html
pàgina = [shamlet|
 !!!
 <html>
 <head>
 <title>#{títol}
 <body>^{cosDeLaPàg elsAmics}
|]
{-# LANGUAGE PackageImports #-}

import Plantilla (pàgina)
import "blaze-markup" Text.Blaze.Renderer.Text (renderMarkup)
import qualified Data.Text.Lazy.IO as TLIO

main = TLIO.putStrLn $ renderMarkup pàgina

En aplicar una composició sobre una estructura, per ex., (reducció. filtre p'. map f. filtre p).

Malgrat que, en ser un llenguatge d'avaluació tardana, no hi ha la problemàtica de la desforestació, hi ha propostes per fusionar-ne les iteracions.

L'ús de la pragma RULES Arxivat 2009-04-22 a Wayback Machine. permet que el compilador, en fase de pre-procés, refaci (reescrigui) operacions de composició.[73]

Stream fusion

modifica

Fusió de bucles en la composició d'operacions sobre estructures mitjançant la conversió de l'estructura en un corrent de dades (ang: Stream)[74] i especificant l'operació per cada element del corrent.

El corrent incorpora la possibilitat de tenir elements sense dades, a conseqüència del filtratge.

Les funcions com ara els filtres s'han d'aplicar començant per l'operació de conversió stream sobre l'estructura, que la converteix en corrent de dades, i acabant per la inversa unstream.

En la composició de funcions, l'aparició juxtaposada de les conversions unstream al final d'una funció amb la inversa stream de l'inici de la següent és eliminada pel compilador mitjançant pragmes RULES aportades per la implementació.[75][76]

Vegeu biblioteca Stream-fusion al rebost Hackage[77]

Referència ràpida de la biblioteca

modifica

Vegeu l'API del GHC[78] i la de la plataforma[9] Haskell.

contenidors

modifica

Vegeu ref.[79]

Recordatori d'algunes operacions
S'esmenten les ops. ometent el paràm. de l'estructura sobre la qual s'aplica (Col·lecció -> Col·lecció). Si l'estructura va primer, l'operació s'esmenta en forma d'aplicació parcial infix (`op` p2 p3..). Cas de consultes o bé resultats opcionals, s'especifica ocasionalment el tipus del retorn. Respecte als tipus de les funcions, consulteu les refs.
llistes amb optimització de fusió
modifica
  • el paquet stream-fusion[77] aporta llistes amb funcions fusionables pel mètode stream fusion.
conjunts i diccionaris
modifica
  • Set ε[80] i Map κ ε[81] del paquet "containers" implementen conjunts i diccionaris basats en arbres de cerca binària balancejats amb cost de cerca O(log n).
  • IntSet[82] i IntMap ε[83] també de "containers" implementen Set i Map sobre el domini Int amb implementació basada en arbres de prefix (Tries) sobre la seqüència de bits amb cost de cerca, com a màxim el nombre de bits del tipus Int.
  • el paquet hashmap[84] aporta una implementació de conjunts[85] i diccionaris[86] basada en IntSet i IntMap de containers, que requereix una aplicació del domini de les claus al tipus Int (funció resum (de hash en anglès) classe Hashable)[87] i desant al conjunt/diccionari o bé un sol element/parell o bé, en en cas de col·lisions, un cistell dels elements/parells quines claus col·lideixen, implementat com a Set/Map del paquet containers. El paquet hashmap ha quedat desaconsellat en favor del paquet unordered-containers.
  • el paquet unordered-containers[88] implementa conjunts[89] i diccionaris[90] (HashSet i HashMap) quines claus han d'implementar una funció resum (classe Hashable) però no cal que implementin Ord (ordenable). El cistell de col·lisions en té prou amb la implementació de Eq per distingir-les.
seqüències finites de dos caps, amb accés més ràpid que les llistes
modifica
  • Seq ε de Data.Sequence[91] implementa seqüències finites amb dos caps (ang:double ended queue) i accés constant O(1) en afegir i recuperar per ambdós costats. viewl proporciona la vista per l'esquerra i viewr la de la dreta.
Cost d'accés aleatori inferior a les llistes: O(log(min(i,n-i))) per a l'accés indexat;
Cost de la concatenació: O(log(min(n1,n2)));
Cost de la consulta de llargària O(1). Avantatge sobre les llistes (on és O(n)) per prevenir l'accés indexat fora de rang.
vectors pluridimensionals
modifica
  • Amb vector fem referència a seqüències d'accés aleatori (accés a qualsevol element amb cost constant O(1))
  • El tipus Array shape ε del paquet Repa[37][92] aporta vectors pluridimensionals d'elements no-encapsulats (unboxed) (allotjats directament)
    • Els elements del Repa han d'implementar
class (Show a, Unbox a) => Elt a
 zero :: a
 one :: a
 ...
    • Els tipus dels índexs del Repa prenen una forma semblant a una pila de dimensions, i implementen la classe Shape:
type DIM0 = Z -- zero dimensions: escalar
type DIM1 = DIM0 :. Int -- una dimensió, ex. Z :. 3
type DIM2 = DIM1 :. Int -- dues dimensions, ex. Z :. 3 :. 3
...
  • Data.Array.Repa utilitza la terminologia dels llenguatges on l'avaluació tardana és explícita, anomenant vectors diferits (ang:delayed) les aplicacions no-avaluades, per exemple amb map, forçant-ne l'avaluació amb la funció force (com a l'OCaml) que avalua, paral·lelitzant els càlculs.

A ghci:

Prelude>:m +Data.Array.Repa
Prelude Data.Array.Repa> let v22 = fromList (Z :. 2 :. 2) [1,2,3,4] :: Array DIM2 Int
Prelude Data.Array.Repa> let consultaV22 i j = v22 ! (Z :. i :. j)
Prelude Data.Array.Repa> consultaV22 1 0
3
Prelude Data.Array.Repa> extent v22
(Z :. 2) :. 2
Prelude Data.Array.Repa> size $ extent v22
4
Prelude Data.Array.Repa> let w22 = force $ Data.Array.Repa.map (\x  x * 2) v22
Prelude Data.Array.Repa> w22 -- Data.Array.Repa.force avalua els mapejos en paral·lel
Array (Z :. 2 :. 2) [2,4,6,8]
Vectors d'alt rendiment
modifica

Vectors amb índexs enters amb base 0.

Vector ε i MVector σ ε del paquet Vector[93] aporten vectors immutables i mudables, basats en famílies de tipus, que permeten implementacions més flexibles, i, a banda, funcions fusionables (stream fusion per la composició).[94][95]

  • cas d'allotjament directe dels elements: Per a vectors d'elements de tipus primitius el mòdul Data.Vector.Unboxed aporta vectors immutables i mudables d'elements no encapsulats.[96]
  • hi ha altres paquets relacionats amb funcionalitats diverses
    • vector-algorithms: algorismes sobre vectors mudables MVector
    • vector-binary-instances: serialització
    • vector-read-instances: instància de Read
    • vector-instances: instàncies de diverses classes
    • ...
biblioteca mono-traversable de Tipus Abstractes de Dades
modifica

Les classes de la biblio estàndard requereixen que les col·leccions estiguin parametritzades amb el tipus de l'element com a variable. Això fa que no es puguin aplicar a les col·leccions monomòrfiques on el tipus de l'element és fix (ex.: Word8 a Bytestring) perquè l'aritat del tipus de la col·lecció no concorda.

El paquet mono-traversable[97] aporta un conjunt de classes d'un sol índex, on els paràmetres de tipus dependents de les col·leccions hi son referits mitjançant tipus associats a la classe o bé famílies de tipus, aplicables tant a estructures paramètriques com monomòrfiques.

class SemiSequence t -- classe monoparàmetre, per a seqüències no buides (implementen Semigrup)
 type Index t -- tipus de l'índex de seqüències com a tipus associat (indexat al paràm. de la classe)

type family Element t -- definit globalment per evitar fer diferents versions de les interfícies.
class MonoFunctor t
 omap :: (Element t -> Element t) -> t -> t

-- Si Element t fos un tipus associat, caldria fer-ne versions diferents per a {seqüències, conjunts, diccionaris} i particularitzar {Functor, Foldable, Traversable, ..} per cada classe de contenidors

A més a més aporta instàncies dels TADs per a les estructures més utilitzades relacionant-hi les operacions.

  • MonoPointed:[98] operacions per elevar un element a la categoria de contenidor mono amb l'element d'entrada.
  • MonoFunctor:[99] Functor sobre contenidors mono.
  • MonoZip:[100] operacions de combinació per posició (zips) de contenidors mono. Requereix MonoFunctor
  • MonoFoldable:[101] Catamorfismes sobre contenidors mono.
  • MonoTraversable:[102] Recorregut (visita) dels elements dels contenidors mono que es poden travessar seqüencialment d'esquerra a dreta, aplicant-los una funció d'efectes o avaluant-los si ja ho són.
  • NonNull:[103] tipus per embolcallar estructures no buides i efectuar-hi amb seguretat operacions parcials que petarien en estructures buides.
Interfícies per l'abstracció de seqüències
  • SemiSequence:[104] Abstracció de les seqüències no-buides amb operacions basades en Semigrups. Aporta {cons, snoc, find, sortBy, reverse, intersperse}.
  • IsSequence:[105] Abstracció de les seqüències buidables, afegint l'element neutre a SemiSequence formant un Monoide. Requereix SemiSequence, Monoid, MonoPointed, MonoTraversable
  • Textual:[106] TAD per a seqüències de caràcters divisibles en paraules i línies. Aporta {words, unwords, lines, unlines, toUpper, toLower, ...}
  • Utf8:[107] TAD multiparàmetre per codificar seqüències textuals en UTF-8, parametritzat amb el tipus de l'estructura d'intercanvi no codificada.
Interfícies per l'abstracció de conjunts i diccionaris
  • SetContainer:[108] TAD amb les operacions comunes als contenidors d'elements distingits per una clau. (tipus de la clau, aporta consultes de la clau {member, notMember, keys}, ops. de combinació {union, unions, difference, intersection})
  • IsSet:[109] SetContainer quins elements són claus, (Element t) coincideix amb (ContainerKey t), amb operacions dels conjunts. Requereix SetContainer.
  • IsMap:[110] SetContainer travessable, amb les operacions dels diccionaris. Requereix SetContainer i MonoTraversable

Els noms de mono-traversable que defineixen funcions coincidents amb les del Prelude duen el prefix "o" (onull, olength, oelem, ...) per evitar haver de desfer ambiguïtats contínuament.

{-| prova.hs 
* Implementa la func. 'trossos' d'un màxim de n elements d'una seqüència retornant-ne la seqüència de seqüències.
* Abstracció dels tipus de la seq. d'origen i de la seq de seqs de sortida.
-}
{-# LANGUAGE PackageImports, TypeFamilies #-}
import "mono-traversable" Data.Sequences as S
import "mono-traversable" Data.MonoTraversable as M
import Data.Function ((&)) -- (&): aplicació cap enrere, (paquet base)
import Control.Monad (mfilter)

-- IsSequence implica MonoFoldable a la qual pertany 'onull', i també implica Monoid

trossos :: (IsSequence t, IsSequence t', Element t' ~ t) => Index t -> t -> t'
trossos n seq = desplega (parteixIValida n) seq

-- desplega equival a 'unfoldr' a les llistes
desplega :: (IsSequence t, a ~ Element t) => (b -> Maybe (a, b)) -> b -> t
desplega f estat = case f estat of
 Just (x, estat') -> S.cons x $ desplega f estat'
 Nothing -> mempty -- cas final, seqüència buida (neutre de concat) (IsSequence implica Monoid)

parteixIValida :: IsSequence t => Index t -> t -> Maybe (t, t)
parteixIValida n seq = 
 S.splitAt n seq
 & Just
 & mfilter (not. M.onull. fst) -- si el trosset era buit, estem al final (retorna Nothing)

provant-ho sobre instàncies de IsSequence com ara String o bé Vector de Data.Vector. Caldrà especificar els tipus de seqüències desitjats com a restricció de tipus:

$ ghci
GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help
Prelude> :load prova
[1 of 1] Compiling Main (prova.hs, interpreted)
Ok, modules loaded: Main.
* Main> trossos 2 "abcdef" :: [String]
["ab","cd","ef"]
* Main> import Data.Vector as Vec
* Main Vec> trossos 2 (Vec.fromList [1..4]) :: Vector (Vector Int)
[[1,2],[3,4]]
altres biblioteques rellevants
modifica
  • el paquet bytestring-trie[111] aporta una implementació de diccionaris com a arbres de prefix (Tries) per a claus de tipus ByteString
  • el paquet dlist[112] aporta llistes per diferència que redueixen el cost de concatenació en cas d'aniuament d'expressions Vegeu ref.[113]
  • el paquet heap[114] aporta cues amb prioritat per a seqüències d'ordenables ({Min|Max}Heap) o bé de parells (prioritat, valor) ({Min|Max}PrioHeap)
  • el paquet semigroups,[115] a partir de GHC 8.0 queda integrat al paquet base. Aporta la classe Semigroup[116] i Data.List.NonEmpty per a llistes no buides.[117]
module Data.List.NonEmpty
...
data NonEmpty a = a :| [a] -- infixr 5

-- afegeix pel davant
(<|), cons :: a -> NonEmpty a -> NonEmpty a
...

generació

modifica
  • Contenidors mono-valor d'opcionalitat o bé de dualitat resultat/error
mono-valor - generació
paquet tipus context sense valor
o bé error
amb valor de llista o funció generadora
base Maybe ε[118] Nothing Just x listToMaybe llista
Either tipError ε[119] Left error Right x
  • Seqüències d'accés lineal
seqüències - generació
paquet tipus context buit amb un elem. de llista
o altra estructura
anamorfismes
(A → A*)
base [ε][120] [ ] [x] ll. per comprensió
cycle llista
repeat x
replicate n x
iterate (f::ε→ε) x
unfoldr (f::acc→Maybe(ε,acc)) x
semigroups
/ base (des de GHC 8.0)
NonEmpty ε[117] x :| [] nonEmpty llista :: Maybe (NonEmpty ε)
cycle xs
repeat x
iterate (f::ε→ε) x
unfoldr (f::acc→(ε,Maybe acc)) x
unfold (f::acc→(ε,Maybe acc)) x
containers Seq ε[91] empty singleton x fromList llista
bytestring ByteString[121]
-- UArray Word8
empty singleton x pack llistaDeBytes
text
-- ∈ H. Platform[9]
Text[122]
-- ByteString codif. UTF16
indexat com [Word16][123]
empty singleton ch pack string
classes interfície
base Monoid[124] mempty

Data.List.cycle dispara error si la llista era buida.

  • Seqüències d'accés aleatori
vectors - generació
paquet tipus context buit amb un elem. de llista o funció generadora
-- associació = (índex, valor)
array Array ι ε[125]
-- immutable amb
allotj. indirecte dels elements
(Ix ι) listArray (iMin, iMax) llista
array (iMin, iMax) llistaD'Associacions
UArray ι ε[126]
-- immutable amb
allotj. directe
-- via interfície IArray
vector
(tipus Índex = Int)
Vector ε[127]
-- immutable amb
allotj. directe
empty singleton x fromList llista
replicate n x
generate llarg (f :: Índex → ε)
MVector σ ε[128]
-- mudable amb
allotj. directe
Vector.thaw ivector -- descongela immutable
replicate n x
replicateM n acció
clone mvector
repa multidim.
Array sh ε[92]
(Shape sh,
Elt ε)
singleton x
  :: Array DIM0 ε
fromList shape llista
classes interfícies
array IArray α ε[129]
-- immutables
  -- α ∈ {Array, UArray}
-- cal esmentar el tipus desitjat
listArray (iMin, iMax) llista :: α ι ε
array (iMin, iMax) llistaD'Associacions :: α ι ε
-- des de MArray
MArray.freeze mArray
MArray α ε m[130]
-- mudables
  -- mònada IO, α ∈ {IOArray, IOUArray}
  -- mònada ST, α ∈ {STArray, STUArray}
(Monad m) -- cal esmentar el tipus desitjat
newListArray (iMin, iMax) llista :: m (α ι ε)
newArray (iMin, iMax) x_inicial:: m (α ι ε)
-- des d'IArray
thaw iArray
  • Altres estructures
estructures - generació
paquet tipus context buit amb un elem. de llista o funció generadora
-- associació = (clau, valor)
de llista ordenada
-- més ràpid
containers IntSet[82] empty singleton x fromList llista fromAscList llista
IntMap ε[83] empty singleton clau x fromList llistaD'Associacions fromAscList llistaD'Associacions
containers Set ε[80] (Ord ε) empty singleton x fromList llista fromAscList llista
Map κ ε[81] (Ord κ) empty singleton clau x fromList llistaD'Associacions fromAscList llistaD'Associacions
unordered-containers HashSet ε[89] (Hashable ε, Eq ε) empty singleton x fromList llista
HashMap κ ε[90] (Hashable κ, Eq κ) empty singleton clau x fromList llistaD'Associacions
bytestring-trie Trie ε[111] empty singleton bytestringClau x fromList llistaD'Associacions
containers Tree ε[131] Node x [ ] unfoldTree f llavor
heap[114] {Min|Max}Heap ε
-- munt d'elems. ordenables
Ord ε empty singleton x fromList fromDescList
fromAscList
{Min|Max}PrioHeap prio val
-- munt de parells (prioritat, valor)
Ord prio empty singleton (prio,val) -- de llista de (prio,val)
fromList
-- de llista de (prio,val)
fromDescList
fromAscList
TADs de mono-traversable -- generació
paquet tipus/classe context amb un elem. de llista o funció generadora
mono-traversable[132]
ε ~ Element t
IsSequence t[105] singleton ε fromList
NonNull t[103] fromNullable --resultat opcional
MonoPointed t[98] opoint ε
IsSet t[109] Eq ε singletonSet ε setFromList llista
IsMap t[110] k ~ ContainerKey t, Eq k singletonMap k v mapFromList llistaDeParells

consulta

modifica
mono-valor - consulta
(en vermell si parcialment definides, disparen error)
tipus context és
buit?
mida pertinença obtenir elem components altres
Maybe ε[118] isNothing fromEnum . isJust (==). Just -- encaix
Nothing | Just x
maybeToList
fromJust
fromMaybe default
--retornant elements
catMaybes llistaDeMaybes
Either tipError ε[119] isLeft fromEnum . isRight (==). Right -- encaix
Left error | Right x
--retornant elements
lefts llistaDeEithers
rights llistaDeEithers
  • fromJust dispara error si la Maybe era buida ; fromMaybe n'és una versió segura (amb valor per defecte per als casos sense valor).
seqüències - consulta
(en vermell si parcialment definides, disparen error)
tipus context és
buit?
mida pertinença obtenir elem components altres
[ε][120] null length elem x find predicat:: Maybe ε
-- encaix
[ ] | (x : xs)
head
tail
subsequences
permutations
init
last
NonEmpty ε[117] length elem x uncons xs :: (ε, Maybe (NonEmpty ε))
-- encaix
(x :| llista)
head
tail
init
last
Seq ε[91] null length (=/ Nothing). (elemIndex{L|R} x) -- encaix de viewl seq
EmptyL | (x :< xseq)
-- encaix de viewr seq
EmptyR | (x :> xseq)
-- via Foldable
ByteString[121]
-- UArray Word8
null length elem x find predicat
-- encaix de uncons byteStr
Nothing | Just (x, xs)
:: Maybe (Word8, ByteString)
unpack
:: [Word8]
head
tail
init
last
Text[122]
-- ByteString codif. UTF16
indexat com [Word16][123]
null length elem ch find predicat
-- encaix de uncons text
Nothing | Just (ch, txt)
:: Maybe (Char, Text)
unpack
:: [Char]
head
tail
init
last
classes interfícies
Foldable contenidor[133] null length elem x find predicat toList
  • Al mòdul Data.List head, tail, init i last disparen error si la seqüència era buida. Al mòdul Data.List.NonEmpty no, perquè les seq. són no-buides: data NonEmpty a = a :| [a]

Els errors generats per funcions definides parcialment, com ara head en seqüències buidables, tenen una depuració complicada (la crida a error no informa de la situació de la crida que incompleix la pre-condició (excepte si es compila per l'ajustatge, ang:profiling i s'executa amb opcions addicionals per la depuració), ni normalment tampoc del valor causant (ex.: (dicc `Map.!` clau) pot donar l'error "Map.!: given key is not an element in the map" perquè la implementació de Show pels paràmetres no es requereix), només dispara una excepció genèrica ErrorCall amb el missatge).[134][135]

vectors - consulta
tipus context és
buit?
mida pertinença obtenir elem components altres
Array ι ε[125] (Ix ι) bounds—límits de l'índex
indices
elems
assocs—parells (índex, valor)
Vector ε[127] (Eq ε) null length elem x find predicat toList
head
tail
init
last
MVector σ ε[128] null length Vector.freeze --cap a Vector
multidim. repa
Array sh ε[92]
(Shape sh,
Elt ε)
size . extent toList
toScalar -- cas de shape DIM0
extent
  ::(Shape sh)
classes interfícies
IArray α ε[129]
-- immutables
bounds
indices
elems
assocs
MArray α ε m[130]
-- mudables
(Monad m) getBounds
getElems
getAssocs
estructures - consulta
tipus context és
buit?
mida pertinença obtenir elem components altres
Set ε[80]
IntSet[82]
null size elems
(Ord ε) member x -- cerca aproximació
lookup{LT,GT,LE,GE} x
toAscList
HashSet ε[89] (Hashable ε, Eq ε) null size member x toList
Map κ ε[81]
IntMap ε[83]
null size keys
keysSet
elems
assocs
(Ord κ) member clau (! clau)
lookup clau -- :: Maybe ε
-- cerca aproximació
lookup{LT,GT,LE,GE} clau
toAscList
HashMap κ ε[90] (Hashable κ, Eq κ) null size member clau (! clau)
lookup clau -- :: Maybe ε
lookupDefault default clau -- :: ε
keys
elems
toList
Trie ε[111] null size member bytestringClau lookup bstrClau keys
elems
toList
submap bstrClau
Tree ε[131] rootLabel
subForest
-- llista en pre-ordre
flatten
-- elems per nivells
levels
{Min|Max}Heap ε[114] Ord ε null size -- encaix de view heap
Nothing | Just (x, heap)
:: Maybe (ε, {Min|Max}Heap ε)
toList
toDescList
toAscList
{Min|Max}PrioHeap prio val[114] Ord prio null size -- encaix de view heap
Nothing | Just ((pri, v), heap)
:: Maybe ((prio, val), {Min|Max}PrioHeap prio val)
-- llista de (prio, val)
toList
toDescList
toAscList
TADs de mono-traversable -- consulta
tipus/classe context és
buit?
mida pertinença obtenir elem altres
MonoFoldable t[101] ε ~ Element t onull olength oelem ε headMay
lastMay
SemiSequence t[104] find predicat -- retorna :: Maybe (Element t)
NonNull t[103] MonoFoldable t head
last
IsSequence t tail
init
IsSet t[109] ε ~ Element t member ε
notMember ε
IsMap t[110] k ~ ContainerKey t member k
notMember k
lookup k -- resultat opcional
findWithDefault def k

actualització / transformació

modifica
mono-valor - actualitza/transforma
tipus aplicació (map)
als elems.
altres
Maybe ε[118] -- via Functor maybe default (f::ε→b)
Either tipError ε[119] -- via Functor either (f::tipError→b) (g::ε→b)
classes interfícies
Functor[136] fmap (f::a → b)
seqüències - actualitza/transforma
transforma
tipus context afegeix elimina aplicació (map)
als elems.
altres transforma'n
una llista
[ε][120] (x :) delete x map (f:: ε → b)
mapAccum{L|R} (f::acc→x→ (acc, y)) acumIni
-- amb imatge opcional
Maybe.mapMaybe (f::ε→Maybe b)
nub—elimina duplicats
reverse
intersperse x
transpose
intercalate llista
Ord ε insert x sort
Seq ε[91] (x <|) -- a l'esquerra
(|> x)—a la dreta
mapWithIndex (f:: Int → ε → b) reverse
ByteString[121] cons x -- al davant
snoc x -- al darrere
map (f:: Word8 → Word8) reverse
intersperse byte
transpose
intercalate bstr
Text[122] cons ch
snoc ch
map (f:: Char → Char) reverse
intersperse ch
-- específics
replace cerca subst
toUpper
toLower
toCaseFold
justifyLeft llarg ch
justifyRight llarg ch
center llarg ch
-- retalla espais
strip
stripLeft
stripRight
transpose
intercalate txt
classes interfícies
Functor[136] fmap (f::a → b)
  • Els tipus monomòrfics (ByteString, Text i també IntSet) no implementen Functor, que requereix tipus amb l'element com a paràmetre, altrament dit, kind * -> * (aritat del tipus == 1)
vectors - actualitza/transforma
transforma
tipus afegeix actualitza aplicació (map) altres
Array ι ε[125] (// llistaD'Associacions) -- via Functor
Vector ε[127] cons x -- pel davant
snoc x -- pel darrere
map (f:: a → b)
-- amb l'índex
imap (f:: Int → a → b)
reverse
MVector σ ε[128] (`set` x)
clear—desvincula refs.
-- copy dst src
(`copy` mvectorOrigen)
(`move` mvectorOrigen)
(`grow` n)
multidim. repa
Array sh ε[92]
map (f:: a → b) reshape shape
transpose
classes interfícies
IArray α ε[129] (// llistaD'Associacions) amap (f::a→b)
MArray α ε m[130] mapArray (f::a → b)
estructures - actualitza/transforma
transforma
tipus context afegeix elimina actualitza aplicació (map)
als elems.
Set ε[80]
IntSet[82]
(Ord ε) insert x delete x insert x -- subst. l'existent map (f:: a → b)
-- cas que f conservi l'ordre, més ràpid amb
mapMonotonic (f:: a → b)
HashSet ε[89] (Hashable ε, Eq ε) insert x delete x insert x -- subst. l'existent map (f:: a → b)
Map κ ε[81]
IntMap ε[83]
(Ord κ) insert clau x delete clau adjust (f::a→a) clau
update (f::a→Maybe a) clau
alter (f::Maybe a → Maybe a) clau
map (f:: a → b)
mapWithKey (f:: κ → a → b)
mapAccum (f:: acc → a → (acc, b)) acumIni
-- mapeig a les claus amb
mapKeys (f::k1 -> k2)
-- si f conserva l'ordre, més ràpid amb
mapKeysMonotònic (f::k1 -> k2)
-- en les col·lisions de f, combina valors
mapKeysWith (en_col·lisions::a->a->a) (f::k1 -> k2)
HashMap κ ε[90] (Hashable κ, Eq κ) insert clau x delete clau adjust (f::a→a) clau map (f:: a → b)
mapWithKey (f:: κ → a → b)
Trie ε[111] insert bytestringClau delete bstrClau adjust f bstrClau mapBy (f:: ByteString → a → Maybe b)
filterMap (f:: a → Maybe b)
Tree ε[131] -- via Functor
{Min|Max}Heap ε[114] (Ord ε) insert x
{Min|Max}PrioHeap prio val[114] (Ord prio) insert (pri, v) -- via Functor
TADs de mono-traversable -- actualitza/transforma
tipus/classe context afegeix elimina aplicació (map)
als elems.
altres
(ε = Element t)
SemiSequence t[104] cons -- pel davant
snoc -- pel darrere
intersperse
reverse
sortBy (f::ε->ε->Ordering)
IsSequence t[105] Eq (Element t) delete ε
Ord (Element t) sort
MonoFunctor t[99] omap (f::ε->ε)
IsSet t[109] (ε ~ ContainerKey t, Eq ε) insertSet ε deleteSet ε
IsMap t[110] (k ~ ContainerKey t, Eq k) insertMap k v deleteMap k adjustMap f k
updateMap f k
alterMap f k

Nota per les funcions d'actualització als diccionaris:

  • adjust (f::v→v) clau dicc : actualitza el valor, si i només si clau pertany al diccionari dicc.
  • update (f::v→Maybe v) clau dicc : actualitza o bé elimina, segons existeixi (isJust) la imatge de la funció f per al valor corresponent a la clau
  • alter (f::Maybe v → Maybe v) clau dicc : (actualitza o bé elimina o bé insereix) si clau no existeix, la insereix amb el valor (f Nothing); altrament com update

accés indexat

modifica
seqüències - accés indexat
en color si disparen error, en marró si és
previsible: domini acotat i continu: length O(1) i índex ∈ [0,length)
tipus context elem. a
l'índex
cerca l'índex
de l'elem.
:: Maybe Int
cerca índexs
::[Int]
elimina actualitza
[ε][120] (!! índx) elemIndex x -- per valor
findIndex predicat -- per predicat
elemIndices x
findIndices predicat
Seq ε[91] (`índex` índx)
-- amb result. opcional:
lookup indx
(`!?` indx)
elemIndexL x -- per l'esquerra
findIndexL predicat
elemIndexR x --per la dreta
findIndexR predicat
elemIndices{L|R} x
findIndices{L|R} predicat
adjust f índx
update índx x
ByteString[121] (`índex` índx) elemIndex x -- pel davant
elemIndexEnd x -- pel darrere
findIndex predicat
elemIndices x
findIndices predicat
Text[122] (`índex` índx) findIndex predicat
vectors - accés indexat
en color si disparen error, en marró si és
previsible: domini acotat i continu: length O(1) i índex ∈ [0,length)
tipus context elem. a
l'índex
cerca l'índex
de l'elem.
:: Maybe Int
cerca índexs actualitza
Array ι ε[125] (! índx)
Vector ε[127] (! índx)
(!? índx) -- :: Maybe ε
elemIndex x
findIndex predicat
-- retornen :: Vector Int
elemIndices x
findIndices predicat
MVector σ ε[128] (`read` indx) (`write` indx x)
(`swap` i j)
multidim. repa
Array sh ε[92]
(Shape sh,
Elt ε)
(! índxMultiDim)
(`índex` índxMultiDim)
(`safeIndex` índxMultiDim)
classes interfícies
IArray α ε[129] (! índx)
MArray α ε m[130] (Monad m) (`readArray` indx) (`writeArray` indx x)
estructures - accés indexat (índex basat en zero sobre seq. ordenada)
en color si disparen error, en marró si és
previsible: domini acotat i continu: size O(1) i índex ∈ [0,size)
tipus context elem. a
l'índex
cerca l'índex
de l'elem.
elimina actualitza
Set ε[80] (Ord ε) elemAt índx
retorna :: ε
findIndex valor -- :: Int
lookupIndex valor -- :: Maybe Int
deleteAt índx
Map κ ε[81] (Ord κ) elemAt índx
retorna :: (κ,ε)
findIndex clau -- :: Int
lookupIndex clau -- :: Maybe Int
deleteAt índx updateAt f índx
-- Del mòdul Data.Sequences del paquet ''mono-traversable''
-- les funcions parcials (poden disparar ''error'') duen el sufix Ex

indexEx :: IsSequence seq => seq -> Index seq -> Element seq -- funció parcial !! (dispara ''error'')

index :: IsSequence seq => seq -> Index seq -> Maybe (Element seq) -- resultat opcional

combinació

modifica
combina per op. associativa (semigrup) o per aparellament correlatiu (zips)
tipus context combinació
associativa
combina'n
una llista
combina per posició
(zips)
zip = zipWith (,)
desacobla llista de tuples
(unzips)
Maybe ε[118] -- via Monoid -- via Monoid
[ε][120] (++) concat (`zip` llista)
(`zip{3..7}` llista...)
zipWith (f::a→b→c) llista1 llista2
zipWith{3..7} f llista...
unzip
unzip{3..7}
Seq ε[91] (><) (`zip` seq)
(`zip{3..4}` seq...)
zipWith (f::a→b→c) seq1 seq2
zipWith{3..4} f seq...
ByteString[121] append concat (`zip` byteStr)
zipWith (f::Word8→Word8→a) byteStr1 byteStr2
unzip
Text[122] append concat (`zip` txt)
zipWith (f::Char→Char→Char) txt1 txt2
Set ε[80]
IntSet[82]
(Ord ε) union unions
HashSet ε[89] (Hashable ε, Eq ε) union unions
Map κ ε[81]
IntMap ε[83]
(Ord κ) union
-- amb func. combinació
unionWith (f::ε -> ε -> ε)
unionWithKey (f::κ -> ε -> ε -> ε)
unions
-- amb func. combinació
unionsWith (f::ε -> ε -> ε)
HashMap κ ε[90] (Hashable κ, Eq κ) union
-- amb func. combinació
unionWith (f::ε -> ε -> ε)
unions
Trie ε[111] union{L|R}
mergeBy (f::ε -> ε -> Maybe ε)
-- via Monoid
Tree ε[131]
{Min|Max}Heap ε[114] (Ord ε) union unions
{Min|Max}PrioHeap prio val[114] (Ord prio) union unions
Array ι ε[125]
Vector ε[127] (++) concat (`zip` vect)
(`zip{3..6}` vect)
zipWith (f::a→b→c) vect1 vect2
zipWith{3..6} f vect...
-- amb l'índex a la funció
izipWith (f::Int→a→b→c) vect1 vect2
izipWith{3..6} f vect...
unzip
unzip{3..6}
multidim. repa
Array sh ε[92]
(++)
append
classes interfícies
Monoid[124] mappend mconcat
IArray α ε[129]
MArray α ε m[130]
combina com a conjunt o bé multiconjunt
tipus context diferència unió uneix-ne
una llista
intersecció està contingut contingut
estrictament
obtenir
distints
[ε][120] (\\) -- multi union—vegeu ref.[137] intersect—multi (`isPrefixOf` llista)
(`isSuffixOf` llista)
(`isInfixOf` llista)
nub
Seq ε[91]
ByteString[121] (`isPrefixOf` byteStr)
(`isSuffixOf` byteStr)
(`isInfixOf` byteStr)
Text[122] (`isPrefixOf` txt)
(`isSuffixOf` txt)
(`isInfixOf` txt)
Set ε[80]
IntSet[82]
(Ord ε) (\\)
difference
union unions intersection isSubsetOf isProperSubsetOf
HashSet ε[89] (Hashable ε, Eq ε) difference union unions intersection
Map κ ε[81]
IntMap ε[83]
(Ord κ) (\\)
difference
union unions intersection isSubmapOf isProperSubmapOf
HashMap κ ε[81] (Hashable κ, Eq κ) difference union
unionWith (f::ε -> ε -> ε)
unions intersection
intersectionWith (f::ε -> ε -> ε)
Trie ε[111] unionL
unionR
{Min|Max}Heap ε[114] (Ord ε) union unions
{Min|Max}PrioHeap prio val[114] (Ord prio) union unions
{-| Composició a ''mono-traversable'' -}

-- els zips sobre llistes es resolen amb un (''newtype'' ZipList a) que implementa un ''functor aplicatiu''.
import Data.MonoTraversable (ZipList (ZipList, getZipList))
zipWithN combinador xs1 xs2 ... xsN = getZipList $ combinador <$> ZipList xs1 <*> ... <*> ZipList xsN

-- les seqüències són Monoides
import Data.Sequences
class (Monoid seq, ...) => IsSequence seq

-- consulta d'inclusió
is{Prefix|Suffix|Infix}Of :: (IsSequence seq, Eq (Element seq)) => seq -> seq -> Bool

-- composició de conjunts i diccionaris
import Data.Containers
class (Monoid set, ...) => SetContainer set
 union :: set -> set -> set
 unions :: (MonoFoldable t, Element t ~ set) => t -> set -- redueix una col·lecció de ''SetContainer'' a un de sol.
 -- com que (SetContainer set) requereix (Monoid set) llavors: unions = oconcat
 difference :: set -> set -> set
 intersection :: set -> set -> set
 ...
-- instàncies de contenidors ordenats (del mòdul ''containers'')
instance Ord element => SetContainer (Set.Set element) -- els conjunts d'ordenables es poden compondre
instance Ord k => SetContainer (Map.Map k v) -- els diccionaris amb clau ordenable també

-- instàncies de contenidors amb claus que implementen Hashable (funció resum) (del mòdul ''unordered-containers'')
instance (Eq element, Hashable element) => SetContainer (HashSet.HashSet element)
instance (Eq key, Hashable key) => SetContainer (HashMap.HashMap key value)

reducció

modifica
plegats
(en color si parcialment definides, disparen error)
tipus reducció successió de plegats parcials
per l'esquerra per la dreta per l'esquerra per la dreta
[ε][120] foldl (f::a→ε→a) inicial
foldl' f ini -- estricte
foldl1 (g::ε→ε→ε) -- sobre el primer elem.
foldr f ini
foldr1 g
scanl f ini
scanl1 g
scanr f ini
scanr1 g
NonEmpty ε[117] via Foldable via Foldable scanl f ini
scanl1 g
scanr f ini
scanr1 g
Seq ε[91] via Foldable via Foldable scanl f ini
scanl1 g
scanr f ini
scanr1 g
classes
Foldable t[133] foldl f ini
foldl' f ini -- estricte
foldl1 g
foldr f ini
foldr' f ini -- estricte
foldr1 g
  • foldl1, foldr1, scanl1, scanr1: disparen error si la col·lecció era buida.
llistes de mono-valor - plegats especials
tipus llista de
contenidors
retornant [a] retornant ([a], [b])
Maybe ε[118] catMaybes
Either tipError ε[119] lefts
rights
-- (lefts, rights):
partitionEithers
plegats especials
tipus sobre
booleans
s/. predicat s/. ordre numèrics amb funció de projecció
al tipus de l'estructura
contenidor
d'estructures
contenidor
de llistes
índex del
menor/major
ε ~ Bool (Ord ε / κ) (Num ε) (Ord ε)
[ε][120] and
or
all predicat
any predicat
minimum
maximum
sum
product
concatMap (f::a→[b]) concat
ByteString[121] all predicat
any predicat
minimum
maximum
concatMap (f::Word8→ByteString) concat
Text[122] all predicat
any predicat
minimum
maximum
concatMap (f::Char→Text) concat
Vector ε[127] and
or
all predicat
any predicat
minimum
maximum
sum
product
concatMap (f::a → Vector b) concat minIndex
maxIndex
Set ε[80] -- via Foldable -- via Foldable findMin
findMax
-- via Foldable -- via Foldable -- via Monoid
IntSet[82] findMin
findMax
-- via Monoid
HashSet ε[89] -- via Foldable -- via Foldable -- via Foldable -- via Foldable -- via Monoid
Map κ ε[81]
IntMap ε[83]
-- via Foldable -- via Foldable -- min/max de la clau
findMin
findMax
-- via Foldable -- via Foldable -- via Monoid
HashMap κ ε[90] -- via Foldable -- via Foldable -- via Foldable -- via Foldable -- via Monoid
{Min|Max}PrioHeap prio val[114] -- via Foldable -- via Foldable -- via Foldable -- via Foldable -- via Monoid
classes
(Monoid m)
Foldable t[133] and
or
all predicat
any predicat
minimum
maximum
sum
product
foldMap (f::a→m)
concatMap (f::a→[b])
concat
Monoid t[124] mconcat
  • minimum, maximum disparen error si la col·lecció era buida
  • minIndex, maxIndex disparen error si el vector era buit
  • findMin, findMax disparen error si el conjunt o diccionari eren buits
  • els tipus monomòrfics (ByteString, Text, IntSet) no implementen Foldable que requereix tipus amb l'element com a paràmetre, altrament dit Kind (* -> *) (aritat del tipus == 1)
TADs de mono-traversable -- plegats
les funcions parcials (disparen error) duen el sufix Ex
tipus/classes reducció
ε = Element t per l'esquerra per la dreta
MonoFoldable t[101] ofoldl' (f::a→ε→a) ini -- estricte
ofoldl1Ex' (g::ε→ε→ε) -- sobre el primer elem.
ofoldr f ini
ofoldr1Ex g -- sobre el primer elem.
NonNull t[103] ofoldl1' (g::ε→ε→ε) -- sobre el primer elem. ofoldr1 g
TADs de mono-traversable -- plegats especials
* sufix Ex: funcions parcials, disparen error
* sufix May: resultat opcional
tipus/classes sobre
booleans
s/. predicat s/. ordre numèrics elements monoides amb projecció a monoide
ε = Element t ε ~ Bool (Ord ε) (Num ε) (Monoid ε) (Monoid m)
MonoFoldable t[101] oand
oor
oall predicat
oany predicat
minimum{May|Ex}
maximum{May|Ex}
osum
oproduct
oconcat oconcatMap (f::ε -> m)
NonNull t[103] minimum
maximum

partició

modifica
mono-valor - filtratge
tipus context filtre
Maybe ε[118] via MonadPlus
classes
MonadPlus t mfilter[138]
seqüències - partició
tipus a l'índex s/.
predicat
en trams d'elements
consecutius equivalents
per ocurrències
d'un separador
en trams de
llargada fixa
:: (t,t) :: (t,t) group = groupBy (==)
[ε][120] take n
drop n
splitAt índx filter predicat
takeWhile predicat
dropWhile predicat
partition predicat
span predicat
break predicat
group
groupBy (equiv::a→a→Bool)
Seq ε[91] take n
drop n
splitAt índx filter predicat
takeWhile{L|R} predicat
dropWhile{L|R} predicat
partition predicat
span{l|r} predicat
break{l|r} predicat
ByteString[121] take n
drop n
splitAt índx filter predicat
takeWhile predicat
dropWhile predicat
partition predicat
span predicat
break predicat
group
groupBy (equiv::Word8→Word8→Bool)
split separador
splitWith predicatDelSeparador
Text[122] take n
drop n
-- pel final
takeEnd n
dropEnd n
splitAt índx filter predicat
takeWhile predicat
dropWhile predicat
-- pel final
takeWhileEnd predicat
dropWhileEnd predicat
-- ambdós extrems
dropAround predicat
partition predicat
span predicat
break predicat
group
groupBy (equiv::Char→Char→Bool)
splitOn textSeparador
split predicatDelSeparador
chunksOf n
  • (span{l|r} predicat) parteix com (takeWhile{L|R} predicat, dropWhile{L|R} predicat)
  • (break{l|r} predicat) equival a (span{l|r} (not. predicat))[139]
vectors - partició
tipus a l'índex s/.
predicat
en funció d'un rang
-- array de parells [(i, arr ! f i) | i <- [iMin..iMax] ]
(Ix i, Ix j) =>
Array ι ε[125] ixmap (iMin, iMax) (f::i → j)
Vector ε[127] take n
drop n
slice índx n
splitAt índx filter predicat
takeWhile predicat
dropWhile predicat
partition predicat
span predicat
break predicat
MVector σ ε[128] take n
drop n
slice índx n
splitAt índx
classes
IArray α ε[129] ixmap (iMin, iMax) (f::i → j)
MArray α ε m[130] mapIndices (iMin, iMax) (f::i → j)
estructures - partició
tipus context a l'índex en inferiors
i superiors
(estrictament)
s/.
predicat
pel prefix
Set ε[80]
IntSet[82]
(Ord ε) split pivot partition predicat
filter predicat
HashSet ε[89] (Hashable ε, Eq ε) filter predicat
Map κ ε[81]
IntMap ε[83]
(Ord κ) split clauPivot partition predicat
filter predicat
HashMap κ ε[90] (Hashable κ, Eq κ) filter predicat
filterWithKey (f::clau->predicat)
Trie ε[111] submap bytestrPrefixClau
Tree ε[131]
{Min|Max}Heap ε[114] (Ord ε) take n
drop n
splitAt n
filter predicat
partition predicat
takeWhile predicat
dropWhile predicat
span predicat
break predicat
{Min|Max}PrioHeap prio val[114] (Ord prio) take n
drop n
splitAt n
filter predicat
partition predicat
takeWhile predicat
dropWhile predicat
span predicat
break predicat
TADs de mono-traversable - partició
tipus context a l'índex s/.
predicat
en trams d'elements
consecutius equivalents
per ocurrències
d'un separador
índx ∈ Index t :: (t,t) :: (t,t) group = groupBy (==)
IsSequence t[105] take índx
drop índx
splitAt índx filter predicat
takeWhile predicat
dropWhile predicat
partition predicat
span predicat
break predicat
groupBy (equiv::a→a→Bool) splitWhen predicat -- retorna [t]
Eq (Element t) group splitElem x
splitSeq xs

implementació de classes

modifica

classes de la biblioteca GHC que les col·leccions implementen:

  • control seqüencial:
  • Monad: encadenament d'accions funció del resultat de la precedent. Seqüencia les accions.
  • Applicative: combinació de resultats d'accions: Seqüencia només les engegades. Aplicable en paral·lelisme.
  • composició no-seqüencial: Monoid
  • composició seqüencial (control seqüencial amb Monoide): Alternative, MonadPlus
  • mapejat no-seqüencial: Functor (Functor garanteix que l'aplicació de la composició de morfismes equival a la composició de les aplicacions dels morfismes individualment, estalviant la multiplicitat de travessaments)
  • visita seqüencial (cas d'elements amb efectes o bé les imatges d'un mapeig amb una funció d'efectes): Traversable
  • reducció: Foldable
  • comparació de les col·leccions: Eq, Ord
  • textualitzacio de les col·leccions: Show,[140] Read[141]
  • travessament genèric de l'estructura de les col·leccions: Data,[142] Generic[143]
  • avaluació en profunditat (a forma normal) de les col·leccions: NFData (contracció de Normal Form Data)[144]
  • serialització de les col·leccions: Binary[145]
excepcions i crítiques
modifica
  • els tipus monomòrfics (ByteString,[121] Text[122] i IntSet[82] no poden implementar les classes Functor i Foldable, Applicative, Monad, Alternative, MonadPlus, Traversable (quines funcions estan definides per a tipus amb un paràmetre). En intentar-ho, dona l'error: Kind mis-match (l'aritat del tipus no és l'esperada). Solució paquet mono-traversable (vegeu #alternatives)
  • no implementen Functor:
alternatives
Les funcions parcials tenen versions amb sufix Ex (poden disparar excepció cridant error), o bé sufix May (resultat opcional)
classe
(context de la classe)
context del mètode mètodes
MonoFunctor t omap (f :: Element t -> Element t)
MonoFoldable t Monoid m ofoldMap (f :: Element t -> m)
ofoldr (op :: Element t -> acc -> acc) accIni
ofoldl' (op :: acc -> Element t -> acc) accIni
onull
olength
otoList
{oall, oany} predicat
head{Ex|May}, last{Ex|May}
(MonoFoldable t,
Num (Element t))
osum, oproduct
(MonoFoldable t,
Element t ~ Bool)
oand, oor
MonoFoldableMonoid t
(MonoFoldable t,
Monoid t)
oconcatMap (f :: Element t -> t)
MonoFoldableEq t
(MonoFoldable t,
Eq (Element t))
oelem x
onotElem x
MonoFoldableOrd t
(MonoFoldable t,
Ord (Element t))
{minimum|maximum}{Ex|May}
{minimum|maximum}By{Ex|May} (f :: Element t -> Element t -> Ordering)
MonoTraversable t
(MonoFunctor t,
MonoFoldable t)
Applicative efecte otraverse (f :: Element t -> efecte (Element t))
Monad efecte omapM (f :: Element t -> efecte (Element t))
MonoPointed t opoint x
perquè els conjunts no implementen Functor
modifica

Perquè no mantenen la regla de la composició de morfismes dels Functors:   per a tot morfisme   i  . Vegeu Functor#Els conjunts no són Functor

implementacions
modifica
implementació de classes d'avaluació seqüencial dels elements
tipus Applicative
(combinador
de resultats d'accions)
Alternative
(applicative
amb monoide)
-- requereix Applicative
Monad
(encadenament
d'accions funció del
resultat de la precedent)
MonadPlus
(mònada
amb monoide)
--requereix Monad
Traversable[147]
(visita avaluant seqüencialment
els elements si són accions
o bé aplicant una funció d'efectes als elements)
-- requereix Functor, Foldable
Maybe ε[118]
Either tipError ε[119] No No des de ghc 7.8
[ε][120]
Seq ε[91] des de ghc 7.8
Array ι ε[125] No No No No No
Vector ε[127]
Set ε[80] No No No No No
HashSet ε[89] No No No No No
Map κ ε[81]
IntMap ε[83]
No No No No
HashMap κ ε[90] No No No No
Trie ε[111] No No
Tree ε[131] No No
  • Al GHC 7.10 està previst que Applicative esdevingui superclasse de Monad, i que Alternative ho sigui de MonadPlus.[148]
  • Alternative i MonadPlus inclouen implementacions que obeeixen regles diferents. Hi ha una proposta per separar cadascuna d'elles en dues classes.[149][150]
  • La regla de l'element absorbent per l'esquerra (ang:Left Catch): Maybe la compleix; la Llista no
 mplus (return x) k  return x -- element absorbent per l'esquerra
 (mplus a b) >>= k  mplus (a >>= k) (b >>= k) -- propietat distributiva per l'esquerra
implementació de classes
tipus Monoid[124]
(componible:
mempty, mappend)
Functor[136]
(mapejable:
fmap)
Foldable[133]
(reduïble:
foldl, foldr)
-- requereix Functor
Binary[151]
(serialitzable:
put, get)
NFData[152]
(avaluable a
Forma Normal:
rnf, deepseq)
Data[153]
(que se'n pot
recórrer l'estructura
genèricament:
gfoldl, gmapX)
Maybe ε[118] (Monoid ε)
compon els elements
(Binary ε) (NFData ε) (Data ε)
Either tipError ε[119] No des de ghc 7.8 (Binary tipError,
Binary ε)
(NFData tipError,
NFData ε)
(Data tipError,
Data ε)
[ε][120] mappend = (++) (Binary ε) (NFData ε) (Data ε)
Seq ε[91] mappend = (><) (Binary ε) (NFData ε) (Data ε)
ByteString[121] mappend = append N/A:
l'aritat del tipus
no concorda
(Kind mis-match)
N/A:
l'aritat del tipus
no concorda
Text[122] mappend = append N/A:
l'aritat del tipus
no concorda
(Kind mis-match)
N/A:
l'aritat del tipus
no concorda
(-- implementable
via "binary-generic")
Array ι ε[125] No No (Binary ι,
Binary ε)
(NFData ι,
NFData ε)
No
Vector ε[127] mappend = (++) (Binary ε)
-- instàncies al paquet
vector-binary-instances
(NFData ε) (Data ε)
Set ε[80] (Ord ε)
mappend = union
No #perquè els conjunts no implementen Functor (Binary ε) (NFData ε) (Data ε,
Ord ε)
HashSet ε[89]
mappend = union
No #perquè els conjunts no implementen Functor (-- implementable
via "binary-generic")
(NFData ε) (Data ε)
IntSet[82] mappend = union #perquè els conjunts no implementen Functor N/A:
l'aritat del tipus
no concorda
Map κ ε[81]
IntMap ε[83]
(Ord κ)
mappend = union
cas de claus coincidents
es pren el valor
del primer operand
(Binary ε) (NFData κ,
NFData ε)
(Data κ,
Data ε,
Ord κ)
HashMap κ ε[90]
mappend = union
(-- implementable
via "binary-generic")
(NFData κ,
NFData ε)
(Data κ,
Data ε)
Trie ε[111] (Monoid ε)
mappend = mergeBy...
cas de claus coincidents
en compon els valors
(Binary ε) No No
Tree ε[131] No (Binary ε) (NFData ε) (Data ε)
{Min|Max}Heap ε[114]
mappend = union
No No No No No
{Min|Max}PrioHeap prio val[114]
mappend = union
No No No
classes requerides pels TAD de mono-traversable
TAD Semigroup
(semigrup:
<>)
Monoid[124]
(componible:
mempty, mappend)
MonoFunctor[99]
(mapejable:
omap)
MonoFoldable[101]
(reduïble:
ofoldl, ofoldr)
MonoTraversable[102]
(travessable:
otraverse, omapM)
MonoPointed[98]
(generable
des d'un valor:

opoint
-- com pure de Applicative)
SemiSequence[104]
-- seqs. no-buides
No No No No
IsSequence[105]
-- seqs. buidables
SetContainer[108] No No No
IsSet[109] No No No
IsMap[110] No

Altres classes habituals dels contenidors:

  • Eq t: Compara contenidors igualables comparant els elements segons l'ordre seqüencial o bé mitjançant la conversió a llista.
  • Ord t: Compara contenidors ordenables comparant els elements segons l'ordre seqüencial o bé mitjançant la conversió a llista ascendent.
  • Show t: Contenidor textualitzable a String independent de la codificació de caràcters (els caràcters ASCII imprimibles (0x20 a 0x7F) tal com són, els metacaràcters (com ara '\t',..) amb el prefix '\', altres caràcters en representació numèrica, inclosos els no-anglosaxons). Per una sortida humanament llegible dels caràcters no anglosaxons, cal fer servir el paquet Text.
  • Read t: Omple contenidor llegint la sortida de (Show t).
implementa classes
tipus Eq[154]
(Igualable)
Ord[155]
(Ordenable)
Show[156]
(Textualitzable)
--només ASCII
altrament codis numèrics
Read[157]
(Llegible)
--només ASCII
altrament codis numèrics
--monomòrfics
ByteString[121]
Text[122]
IntSet[82]
--tipus d'aritat 1
Maybe ε[118]
[ε][120]
Seq ε[91]
Vector ε[127]
Set ε[80]
IntMap ε[83]
(Eq ε) (Ord ε) (Show ε) (Read ε)
--tipus d'aritat 2
Map κ ε[81]
Either κ ε[119]
(Eq κ, Eq ε) (Ord κ, Ord ε) (Show κ, Show ε) (Read κ, Read ε)
--tipus amb particularitats
(Ix ι) => Array ι ε[125] (Eq ε) (Ord ε) (Show ι, Show ε) No
Tree ε[131] (Eq ε) No (Show ε) (Read ε)
(Hashable ε, Eq ε) => HashSet ε[89] No (Show ε) (Read ε)
(Hashable κ, Eq κ) => HashMap κ ε[90] (Eq ε) No (Show κ, Show ε) (Read κ, Read ε)
(Ord ε) => {Min|Max}Heap ε[114]
(Ord prio) => {Min|Max}PrioHeap prio val[114]
Implementació de Binary (serialitzable) amb binary-generic
modifica

Cal que el tipus implementi la classe Data[153] del mòdul Data.Data que possibilita el recorregut genèric de l'estructura del tipus. Vegeu el paquet binary-generic[158]

{-# LANGUAGE PackageImports #-}
import Data.Data (Data) -- Data possibilita el travessament genèric de l'estructura
import Data.Binary (Binary(..)) -- la classe Binary i els seus mètodes (..)
import "binary-generic" Data.Binary.Generic (getGeneric, putGeneric)

-- Text de Data.Text ja implementa Data

import Data.Text (Text)
import Data.HashSet (HashSet)
import Data.Hashable (Hashable)

instance Binary Text where
 get = getGeneric
 put = putGeneric -- versió tàcita (sense paràm. formals reduïbles)

instance (Data a, Hashable a, Eq a) => Binary (HashSet a) where
 get = getGeneric
 put = putGeneric

prova:

cabal install binary-generic
ghc -c prova.hs
Sobrecàrrega de literals
modifica

La sobrecàrrega de literals permet associar-los a una classe de tipus, en comptes d'a un tipus fix, permetent-ne l'ús en posicions d'operands d'aquells tipus que les implementin.

literals i decodificació
literal (EBNF) classe funció decodificadora
o de conversió
extensió de llenguatge
9{9} -[\.E]
(* dígits sense parts decimal/exp. *)
Num
(anell)
fromInteger
9{9}[\.{9}] ['E'[±]9{9}]
(* notació científica *)
Fractional
(cos)
fromRational
9{9}[\.{9}] ['E'[±]9{9}]
(* n.c. avaluable a enter *)
Num
(anell)
fromInteger NumDecimals[159]
'"' caràcter {caràcter} '"'
(* seqs. de caràcters*)
IsString[160] fromString OverloadedStrings[161]
'\[' valor {',' valor} '\]'
(* seqs. de valors *)
IsList[162] fromList OverloadedLists[163]
Sobrecàrrega de literals String
modifica

Així com els literals numèrics obtenen el tipus de la signatura de les operacions en que intervenen, la classe IsString fa possible això mateix per als literals String admetent-ne l'ús en posicions d'altres tipus que n'implementin la conversió, en el mètode fromString de la classe IsString.[160] Cal especificar l'extensió de llenguatge OverloadedStrings.[161]

{-# LANGUAGE OverloadedStrings #-}
import Data.ByteString.Char8 as BS

-- "abc" convertit automàticament al tipus esperat
-- sempre que instancii IsString implementant ''fromString''
main = BS.putStrLn "abc"
implementa classes
tipus IsString[160]
[Char][120]
ByteString[164]
Text[122]

A partir de GHC 7.8, els literals String, si hi ha l'extensió OverloadedStrings, ja no tenen tipus String, sinó que el tipus es descriu de manera existencial com un tipus d'aquells que implementen IsString

(IsString t) => t

de manera similar als literals numèrics.

Prelude> import Data.ByteString as SBS -- Strict ByteStrings
Prelude SBS> :set -XOverloadedStrings
Prelude SBS> "abc" :: SBS.ByteString -- fromString, de la instància de IsString, fa la conversió
"abc"
Sobrecàrrega de literals Llista
modifica
  • Des de GHC 7.8. OverloadedLists: Els literals llista poden passar com a literals d'altres tipus que implementin (IsList t) aportant-hi la conversió.[163]
implementa classes
tipus IsList[162] Item (tipus de l'element del literal per a la instància)
[ε][120] ε
NonEmpty ε[117] ε
Text[122] Char
Seq ε[91] ε
IntSet[82] Int
Set ε[80] ε
IntMap ε[83] (Int, ε)
Map κ ε[81] (κ, ε)
HashSet ε[89] ε
HashMap κ ε[90] (κ, ε)
  • Exemple de generació d'instàncies d'IsList per a HashSet i HashMap, però sobre tipus derivats amb newtype altrament donaria error per duplicació. Vegeu instàncies òrfenes.[165]
 cabal install unordered-containers # conté Data.HashSet i Data.HashMap
-- Provat amb GHC 7.10.1
{-# LANGUAGE OverloadedLists, TypeFamilies, PackageImports, GeneralizedNewtypeDeriving #-}

import GHC.Exts (IsList(..))

import "hashable" Data.Hashable
import "unordered-containers" Data.HashSet as HS
import "unordered-containers" Data.HashMap.Lazy as HM
import Control.Category ((>>>)) -- f >>> g == g. f

newtype MyHashSet a = MyHashSet { getHashSet :: HashSet a} deriving (Eq, Show, Foldable)

instance (Hashable a, Eq a) => IsList (MyHashSet a) where

 -- Item col·lecció: tipus de l'element del literal llista relatiu al tipus de la instància
 type Item (MyHashSet a) = a
 fromList = HS.fromList >>> MyHashSet
 toList = getHashSet >>> HS.toList

provaConjunt = ["Joan", "Pere"] :: MyHashSet String

---------------
newtype MyHashMap k a = MyHashMap { getHashMap :: HashMap k a} deriving (Eq, Show, Functor, Foldable, Traversable)

instance (Hashable k, Eq k) => IsList (MyHashMap k a) where

 -- Item col·lecció: tipus de l'element del literal llista relatiu al tipus de la classe
 type Item (MyHashMap k a) = (k, a)
 fromList = HM.fromList >>> MyHashMap
 toList = getHashMap >>> HM.toList

provaDicc = [("Joan",2),("Pere",3)] :: MyHashMap String Int

Preprocessadors

modifica

Compilació condicional amb codi CPP

modifica

Inclusió de codi de preprocessador de C (CPP).[166] Compilació condicional.[167]

Cal esmentar el senyal de compilació -cpp o bé la pragma d'extensió de llenguatge {-# LANGUAGE CPP #-}.

El valor de la constant __GLASGOW_HASKELL__ definida pel compilador està relacionat amb els dos primers nombres de la versió de GHC que si és x.y.z llavors __GLASGOW_HASKELL__ == xyy, per ex. 708 per a GHC 7.8.*, 710 per a GHC 7.10.*.[168]

El gestor de projectes Cabal genera amb cabal build el fitxer de macros dist/build/autogen/cabal_macros.h definint per cada biblioteca l'identificador de versió i una macro.

// fitxer dist/build/autogen/cabal_macros.h -- generat automàticament en compilar el paquet "biblio-4.6.0.1"

/* package biblio-4.6.0.1 */
#define VERSION_biblio "4.6.0.1"

// MIN_VERSION macro que comprova si la versió actual (4.6.0) iguala o supera la versió dels paràmetres
#define MIN_VERSION_biblio(major1,major2,minor) (\ 
 (major1) < 4 || \
 (major1) == 4 && (major2) < 6 || \
 (major1) == 4 && (major2) == 6 && (minor) <= 0)

Això permet especificar compilació condicional segons la versió de biblioteca.

{-# LANGUAGE CPP, PackageImports #-}

#if defined(__GLASGOW_HASKELL__) /* cas de compilador GHC */

 -- pragmes condicionats a la versió de GHC

 #if __GLASGOW_HASKELL__ >= 707 && __GLASGOW_HASKELL__ < 710 /* GHC >= 7.7 && < 7.10 */
 {-# OPTIONS_GHC -fno-warn-amp #-}
 #endif

#elif defined(__UHC__) /* cas de compilador UHC */
 ...
#else
 #error "no ens fem responsables d'altres compiladors"
#endif

-- codi segons la versió de la biblioteca/paquet "biblio"

#if MIN_VERSION_biblio(4,0,0)
 ... codi que compila si la versió actual de "biblio" iguala o supera l'esmentada
#elif MIN_VERSION_biblio(3,0,0)
 ... codi que compila si la versió actual de "biblio" >= 3.0.0
#else
 #error "les versions de 'biblio' anteriors a la 3.0.0 no estan suportades"
#endif
  • Recomanat: Mòduls de compatibilitat per als casos de trencament de l'API[169]

altres preprocessadors

modifica

El gestor de projectes Cabal obté la llista de mòduls del fitxer de projecte (proj.cabal). Aquests mòduls poden correspondre a fitxers amb extensions diverses. Segons l'extensió, Cabal hi aplica el preprocessador corresponent, generant el fitxer de codi Haskell a compilar.

  • cas de document de programa, extensions {.hs, .lhs}, ja tenim el codi font.
preprocessadors gramaticals
extensió d'origen convertidor notes
.x, .lx alex[170] document d'analitzador de lèxic, de l'estil del programa en C lex
.y, .ly happy[171] document d'analitzador gramatical, de l'estil del Yacc

preprocessadors de generació de codi d'enllaç amb biblioteques d'altres llenguatges

modifica

Vegeu HaskellWiki - Foreign Function Interface

preprocessadors FFI
extensió d'origen convertidor genera notes
.hsc hsc2hs[172] .hs, mòdul_hsc.h, mòdul_hsc.c haskell amb incrustacions de C [173][174]
.gc greencard[175] .hs, .h, .c document haskell GreenCard[176]
.h c2hs[177] .hs generador de haskell partint de signatures en C

Forats en expressions per consultar-ne el tipus (ang: Type Holes)

modifica

És una nova característica de GHC 7.8 que permet consultar el tipus que pertoca a una posició de paràmetre actual en una expressió, designada amb el guió baix '_', abans d'escriure-hi una expressió.[178]

$> ghci
GHCi, version 7.7.20140111:...
Prelude> :set -XTypeHoles
Prelude> :{
Prelude| data Free f a
Prelude| = Pure a
Prelude| | Free (f (Free f a))
Prelude|
Prelude| instance Functor f => Monad (Free f) where
Prelude| return a = Pure a
Prelude| Pure a >>= f = f a
Prelude| Free f >>= g = Free _
Prelude| :}

<interactive>:32:23:
 Found hole _ with type: f (Free f b)
 Where: f is a rigid type variable bound by
 the instance declaration at <interactive>:29:10
 b is a rigid type variable bound by
 the type signature for
 (>>=) :: Free f a -> (a -> Free f b) -> Free f b
 at <interactive>:31:10
 Relevant bindings include
 g :: a -> Free f b (bound at <interactive>:32:14)
 f :: f (Free f a) (bound at <interactive>:32:8)
 (>>=) :: Free f a -> (a -> Free f b) -> Free f b
 (bound at <interactive>:31:3)
 In the first argument of Free, namely _
 In the expression: Free _
 In an equation for >>=: (Free f) >>= g = Free _
Prelude>

Depuració

modifica

Pistes per la depuració aquí.[179]

El problema de la manca d'informació de situació en les petades

modifica

Quan una funció definida parcialment, crida la funció error, el programa peta oferint, escassament, el missatge de la funció error en versions anteriors a GHC 8.0. A partir de GHC 8.0 el missatge afegeix la posició de la funció error excepte per al paquet base.

Per exemple, en cas de manca en la consulta en un diccionari Data.Map (dicc ! clau), dona l'error "Map.find: element not in the map". Cap pista (a GHC) de la situació de la crida a error, ni de la rutina de procedència, ni del valor causant de l'error. (no s'exigeix que les claus dels Map siguin textualitzables (instàncies de Show)).

A Haskell no hi ha la pila per a crides dels llenguatges estrictes sinó que la pila és només d'expressions pendents d'ésser avaluades per a diferents patrons.[180] Per tant tampoc tindrem el bolcat de crides per situar-se.

  • A partir de GHC 7.4.1, per quan es compila amb l'opció de l'ajustatge de rendiment (ang:profiling), (ghc -prof -fprof-auto), s'ha habilitat una reescriptura del codi intern per la simulació d'una pila de crides estricta, per obtenir-ne una traça de crides (sense posicions de les crides) en cas d'excepció.[181][182] Cal afegir +RTS -xc al llançament del programa.[183]
  • A partir de GHC 7.8.1 hi ha una versió d'error errorWithStackTrace del mòdul GHC.Stack, que per bolcar la pila de crides (amb les posicions) requereix haver compilat i relligat amb les opcions d'ajustatge (ang:profiling)(ghc -prof -fprof-auto). Les opcions d'execució de depuració (+RTS -xc) no són necessàries.[184]
  • GHC 8.0: errorWithStackTrace ha sigut devaluada (ang:deprecated). Ara error incorpora la funcionalitat de errorWithStackTrace. L'anterior error passa a denominar-se errorWithoutStackTrace. Les crides a error a la biblioteca base han estat reanomenades a errorWithoutStackTrace !!.
  • A partir de GHC 7.10.2 hi ha la possibilitat d'explicitar les crides a traçar amb paràmetres implícits (definits a l'àmbit que fa la crida) específics (?loc :: CallStack), i NO requereix l'ús del RunTimeSystem d'ajustatge, com s'explica a #Obtenció programàtica del punt de crida. Cal l'extensió ImplícitParams.
  • A partir de GHC 8.0 hi ha un truc per estalviar l'ús de l'extensió de sintaxi ImplicitPararms que és la definició de la restricció HasCallStack, fixant el nom de la variable de la pila de crides i fent que la crida a error la bolqui automàticament. (vegeu exemple)
  • Atenció: l'ús de CallStack/HasCallStack no funciona si no hi ha continuïtat en la propagació de l'àmbit de la variable implícita a les crides intermèdies, repetint la restricció a totes elles.
Per exemple a GHC 8.2, Data.Map.(!) del paquet containers no esmenta HasCallStack a la crida, per tant l'ús de HasCallStack en una rutina d'usuari que la invoqui NO afegirà el punt de crida a la pila de crides del missatge d'error perquè la primera crida no s'apila a la pila de la rutina que té la crida a error.
-- A GHC 8.0 !!
module GHC.Stack where ...
 type HasCallStack = (?callStack :: CallStack) :: Constraint

-------------
-- ús
import GHC.Stack (HasCallStack)

funcióParcial :: HasCallStack => a -> b
funcióParcial p
 | precondició p = resultat
 | otherwise = error $ "funcióParcial: la precondició falla\n" -- a GHC 8.0 el bolcat de "?callStack" és automàtic
 -- a GHC >= 7.10.2 i < 8.0 cal afegir el bolcat manualment amb 'showCallStack'
  • L'ús de l'ajustatge (ang:profiling) requereix relligar el programa amb la versió profiling del RunTimeSystem.[185] Per fer proves amb perfilat cal disposar d'una compilació amb -prof de totes les biblioteques que relliguem.[186]
Per evitar l'error de manca de biblioteca de perfilat en les dependències, cal generar versions amb perfilat de les biblioteques que s'instal·lin. Per això, per al desenvolupament en aïllament (sandbox), convé afegir l'opció de perfilat per defecte (library-profiling: True) a l'arxiu de configuració de cabal específic del projecte (cabal.config) a la carpeta del projecte.

Tanmateix si sabeu per on poden anar els trets, es pot anar acorralant amb paranys (catch) i afegir traces als llocs sospitosos.

Traça de crides a l'intèrpret GHCi

modifica

Des de GHC 8.0.1 (excepte per a Windows): Vegeu ref.[187]

ghci -fexternal-interpreter -prof # intèrpret que relliga amb la biblioteca RTS d'ajustatge (ang:profiling)

Exemple de bolcat de pila de crides per excepció amb prova de crida errònia d'una clau inexistent al diccionari:

CallStack (from HasCallStack):
La restricció HasCallStack aquí no funciona (no afegeix els punts de crida de l'exemple en cas d'excepció) perquè l'àmbit de la variable implícita ?callStack no es propaga amb continuïtat entre la crida a error i la funció amb HasCallStack de l'exemple (hi ha funcions interposades sense la restricció HasCallStack que, per tant, no connecten la variable implícita amb l'àmbit del punt de crida, per exemple Data.IntMap.(!)).
CallStack (from -prof):
La compilació d'ajustatge (ang:profiling), en cas d'excepció mostra els punts de crida corresponents als mòduls compilats amb l'opció -prof.
{-| file prova3.hs -}
{-# LANGUAGE OverloadedLists #-}
import Data.IntMap
import GHC.Stack (HasCallStack)
import Control.Exception (evaluate)

mostra = [(1,"a"),(2,"b")] :: IntMap String

mapGet :: HasCallStack => Int -> IntMap a -> a 
mapGet i m = m!i -- crida que dispara excepció si la clau 'i' no hi és

test1 :: IO ()
test1 = do
 evaluate $ mapGet 3 mostra -- error a posta (clau inexistent) per mostrar el bolcat de la pila de crides
 return ()
{- fi de fitxer -}

$ ghci -fexternal-interpreter -prof
GHCi, version 8.2.1: http://www.haskell.org/ghc/ :? for help
Prelude> :load prova3
[1 of 1] Compiling Main (prova3.hs, interpreted)
Ok, 1 module loaded.
* Main> test1
*** Exception: IntMap.!: key 3 is not an element of the map
CallStack (from HasCallStack):
 error, called at libraries/containers/Data/IntMap/Internal.hs:569:17 in containers-0.5.10.2:Data.IntMap.Internal
CallStack (from -prof):
 Main.mapGet (prova3.hs:10:14-16)
 Main.test1 (prova3.hs:14:22-36)
 Main.test1 (prova3.hs:14:11-36)
 Main.test1 (prova3.hs:(13,9)-(15,19))
* Main>

aproximació amb paranys (catch)

modifica

Desavantatge: Les clàusules catch no es poden utilitzar dins el codi funcional, sinó només en codi de la mònada IO.[188] El paquet exceptions[189] aporta una implementació vàlida en altres mònades.

El catch del Prelude està devaluat (eliminat a GHC v. 7.6.1) en front del recomanat de Control.Exception.

La funció catches evita la situació produïda en una seqüència d'aplicacions de catch (per gestionar diferents tipus d'excepcions), on una excepció produïda dins un handler pot ser interceptada pel catch següent.

{-# LANGUAGE ScopedTypeVariables #-}

import Control.Exception (catches, Handler(..), ErrorCall, SomeException, try)

default (Int, Double) -- seq. de desambiguació dels literals numèrics

cap (x:_xs) = x
cap [] = error "l'has espifiada, calça't per trobar qui m'ha cridat!!"

erroni n = cap $ drop n [1,2,3]

aproxAmbParanys n = (print $ erroni n)
 `catches` [ Handler (\(err :: ErrorCall)  putStrLn $ "caçada op. no definida per al valor: " ++ show err),
 Handler (\(err :: SomeException)  putStrLn $ "caçada: " ++ show err) ]

-- prova
main = aproxAmbParanys 5

Vegeu també #Punts de control al codi amb Loch-TH.

aproximació amb traces

modifica

Desavantatges:

  • en el codi funcional l'ordre d'impressió de múltiples traces és aleatori perquè l'ordre d'execució de subexpressions és indeterminat.
  • en el codi monàdic (seqüencial) a causa de l'avaluació tardana, s'imprimeix quan s'avalua i no pas en l'ordre especificat.
import Debug.Trace (trace, traceStack) -- traceStack (des de GHC v.7.4.1)

default (Int, Double) -- seq. de desambiguació dels literals numèrics

cap (x:_xs) = x
cap [] = error "l'has espifiada, calça't per trobar qui m'ha cridat!!"

erroni n = cap $ drop n [1,2,3]

-- aproximació amb traces:

-- trace imprimeix el missatge via unsafePerformIO
-- traceStack, a més, imprimeix una "pila de crides" artificial (no n'hi ha al Haskell)
-- sempre que es compili amb opcions de perfilat (ghc --make -prof -fprof-auto-calls prova.hs)

aproxAmbTraces n = resultat'
 where
 resultat = erroni n
 resultat' = trace msg resultat -- o bé amb ''traceStack'' si GHC >= 7.4
 msg = "el paràmetre era " ++ show n

main = aproxAmbTraces 5

-- des de GHC 7.8.1 hi ha 'errorWithStackTrace' (del mòdul GHC.Stack)
-- a GHC 8.0, la funció 'error' assumeix 'errorWithStackTrace' excepte al paquet base, reanomenada 'errorWithoutStackTrace'
$ ghc --make -prof -fprof-auto-calls prova.hs
...
$./prova
el paràmetre era 5
Stack trace:
 Main.exemple.resultat' (prova.hs:13:27-49)
 Main.main (prova.hs:16:17-25)
 Main.main (prova.hs:16:9-25)
 Main.main (prova.hs:(16,8)-(17,102))
 Main.main (prova.hs:(16,8)-(18,77))
 Main.CAF (<entire-module>)
prova: l'has espifiada, calça't per trobar qui m'ha cridat!!

aproximació amb depuració a l'intèrpret ghci

modifica

Amb GHCi debugger,[190] Vegeu l'article "No more exceptions: debugging Haskell code with GHCi".[191] establint punts d'aturada (break-points)

  • cal habilitar el senyal break-on-exception
  • l'excepció ErrorCall, generada per la crida a la funció error, provoca l'aturada
  • amb :back retrocedeix al punt d'aturada (ang:break-point) anterior
  • GHCi permet magatzemar en un fitxer "./.ghci"[192] les ordres a executar en arrencar ghci, facilitant desar una llista de ordres de càrrega de mòduls i declaració de break-points per a un ús reiteratiu. Requereix permís d'escriptura exclusiu del propietari al fitxer i al directori.
(a UNIX caldrà desactivar el permís d'escriptura de grup del fitxer i del directori: chmod g-w ".ghci" && chmod g-w $PWD)
default (Int, Double) -- seq. de desambiguació dels literals numèrics

cap (x:_xs) = x
cap [] = error "l'has espifiada, calça't per trobar qui m'ha cridat!!"

erroni n = cap $ drop n [1,2,3]

aproxAmbExecucióControlada n = erroni n

-- ordres GHCi:
-- :set -fbreak-on-exception
-- :break <ident-global|num-linia> -- crea un punt d'aturada a l'identificador o bé num. de línia
-- :back -- retrocedeix a break-point anterior
-- :continue -- endavant sense aturar-se
-- :step -- avança fins al proper punt d'aturada
-- :steplocal -- avança fins al proper punt d'aturada que pertanyi a la mateixa funció a nivell de declaracions
-- :stepmodule -- avança fins al proper punt d'aturada que pertanyi al mateix mòdul.
-- :show breaks -- llista punts d'aturada
-- :list -- llista el codi al voltant del punt d'aturada
--
-- consulta de variables:
-- :show bindings -- llista identificadors visibles (variables i retorn consultables)
-- :print <ident> -- consulta ident. avaluats, visibles en l'àmbit
-- :force <ident> -- cas de no estar avaluat, força'n l'avaluació i imprimeix-lo

main = aproxAmbExecucióControlada 5
ghci prova.hs
Ok, modules loaded: Main.
* Main> :set -fbreak-on-exception
* Main> :break aproxAmbExecucióControlada
Breakpoint 0 activated at prova.hs:(30,1)-(32,35)

* Main> aproxAmbExecucióControlada 5
Stopped at prova.hs:(30,1)-(32,35)
_result :: Int = _
[prova.hs:(30,1)-(32,35)] *Main> :continue
Stopped at <exception thrown>
_exception :: e = _
[<exception thrown>] *Main> :back
Logged breakpoint at prova.hs:(30,1)-(32,35)

Obtenció programàtica del punt de crida

modifica

Des de la versió 7.10.2[193] existeix un mecanisme per l'obtenció programàtica del punt de crida, per a ser inclòs en missatges d'error, com es descriu a l'exemple.[194]

A l'hora d'obtenir-ne el valor, es mostren només les crides corresponents al nom de variable implícita coincident.

-- A GHC >= 7.10.2
{-# LANGUAGE ImplicitParams #-}
import GHC.Stack (CallStack, showCallStack) -- a GHC 8.0 'showCallStack' serà 'prettyCallStack'
 -- GHC 8.0 estandarditza el nom ?callStack quin bolcat afegeix automàticament al msg d'error
f :: (?loc :: CallStack) => String
f = showCallStack ?loc -- retorna la pila de crides de nom "?loc": [@f, @g, @main]

g :: (?loc :: CallStack) => String -- apila les traces de crida segons el nom de la variable implícita
g = f

main = putStrLn g

Per a versions anteriors la biblioteca file-location permet incloure la posició actual en missatges d'error. Vegeu exemple a la secció següent.[195]

Traslladant la petada de les funcions parcials a la rutina que la crida amb paràmetres no vàlids

modifica

La crida a la funció error genera una excepció genèrica ErrorCall que cal evitar perquè proporciona molt poca info. de l'error.

Una solució: substituir la funció parcial per una de resultat opcional (Maybe) i avaluar-ne el resultat.

  • Si n'avaluem només el patró esperat, i es dona l'inesperat, la petada de l'encaix dona informació de la situació.[196]
  • Però és més correcte avaluar tots els patrons i assenyalar l'error amb la funció (err') del paquet file-location que, mitjançant una crida splice ($(x)) (extensió TemplateHaskell), avalua, en temps de compilació, la posició de la crida.
{-# LANGUAGE PackageImports, TemplateHaskell #-}
import "file-location" FileLocation (err') -- obsolet a GHC 8.0 ('error' ja incorpora la situació)

-- funció total headMay equivalent a la parcial ''head''
headMay :: [a] -> Maybe a
headMay (x : _) = Just x
headMay _ = Nothing

obtenirElCap :: [a] -> a
obtenirElCap llista =
 case headMay llista of
 Just cap -> cap
 Nothing -> $(err') "OH NO!" -- cas de GHC < 8.0, $(err') avalua en temps de compilació capturant la posició per al msg.
 -- per a GHC >= 8.0, ''error'' ja mostra la posició del punt de crida

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

Resultat:

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

La biblioteca Safe millora les funcions parcials del Prelude

modifica

La biblioteca Safe[197] ofereix diverses versions de les rutines predefinides al Prelude que poden petar.

  • sufix Def per a versions amb valor per defecte per al cas no definit.
  • sufix May per a versions amb resultat opcional (Maybe)
  • sufix Note per a versions amb afegitó per etiquetar el missatge d'error per distingir-ne l'origen,
  • sufix Safe: cas de no definició, retorna l'element zero del mateix tipus. (tailSafe [] = []; initSafe [] = [])

Exemple: at: funció substitutiva de la indexació de llistes (!!), definida parcialment per a valors no negatius de l'índex, menors que la llargada:

atDef :: a -> [a] -> Int -> a -- amb valor per defecte

atMay :: [a] -> Int -> Maybe a -- amb resultat opcional

atNote :: String -> [a] -> Int -> a -- amb nota diferenciadora per etiquetar el missatge d'error

Evitant les funcions parcials

modifica

El més convenient és evitar les funcions parcialment definides substituint-les per totals,

  • amb resultat opcional, analitzant el resultat per patrons
  • proporcionant un valor per defecte per al cas no definit
  • definint un tipus específic per al subdomini vàlid, amb generadors que en validin els valors.

Opcions de compilació per evitar fallades

modifica

Vegeu ref.[198]

  • -fwarn-incomplete-patterns : avisa quan l'encaix pot fallar
  • -fwarn-incomplete-uni-patterns : avisa quan l'encaix en una lambda és incomplet
  • -fwarn-incomplete-record-updates: avisa quan les actualitzacions de registres poden fallar

Un Prelude alternatiu sense funcions parcials

modifica

El paquet classy-prelude[199] ofereix diversos avantatges, i es pot utilitzar amagant el Prelude oficial amb la pragma {-# LANGUAGE NoImplicitPrelude #-}

Incorpora, a més a més, la biblio. mono-traversable[200] que proposa un tractament unificat de col·leccions, tant monomòrfiques com paramètriques, basat en famílies de tipus.

Evitant les petades amb Verificació Formal estàtica

modifica

Afegint anotacions de "tipus refinats amb predicats".

Vegeu #Verificació Formal estàtica amb la biblio LiquidHaskell

Punts de control al codi amb Loch-TH

modifica

El paquet loch-th[201] aporta una solució a les assercions fallides, generant automàticament en els punts de control $check <expr>, amb template haskell, gestors d'excepcions sobre l'expressió que prefixen la situació (fitxer : línia : columnes) del $check al missatge d'error. (el prefix '$' avalua en temps de compilació la funció o expressió (entre parèntesis) prefixades)

module Erroni where

import Control.Exception (assert)

erroni :: String
erroni = assert False "abc"
-- fitxer prova.hs
{-# LANGUAGE PackageImports, TemplateHaskell #-}
import "loch-th" Debug.Trace.LocationTH (check)
import Erroni

msg = $check erroni

main = print ($check msg)

la seva execució, en cas d'excepció, mostra les posicions dels $check travessats:

ghc --make prova.hs Erroni.hs -o prova
./prova

prova: prova.hs:8:14-19: prova.hs:6:6-11: Erroni.hs:6:9-14: Assertion failed

Loch-TH i els constructors

modifica

$check avalua amb evaluate que ho fa a WHNF per tant les expressions dels components dels contructors no són avaluades. Caldrà explicitar l'avaluació estricta per forçar-ne l'avaluació per aflorar les excepcions. Vegeu explicació a Haskell / estrictesa

force del paquet "deepseq"[202] avalua estrictament també els components (avaluació a Forma Normal), si el tipus implementa la classe NFData del mateix paquet. El paquet deepseq-th aporta la derivació genèrica d'instàncies NFData per a tipus de dades algebraics.

{-# LANGUAGE PackageImports, ScopedTypeVariables, TemplateHaskell, BangPatterns #-}
{-# LANGUAGE UnicodeSyntax #-} -- per les fletxes Unicode

import Prelude hiding (catch) -- El catch del Prelude no detecta excepcions en el codi funcional
import Control.Exception (catch, SomeException)
import Control.Monad

import "loch-th" Debug.Trace.LocationTH (check)
import "deepseq" Control.DeepSeq (force)

erroni1 = $check (head []) : "abc" -- sí que prefixa posició
erroni2 = $check (head [] : "abc") -- el constructor (:) atura l'avaluació de (head []) que no és avaluada (vegeu WHNF)
erroni3 = $check (head [] `consEstricteALElem` "abc") -- amb ! a l'elem a WHNF, sí que prefixa la posició
erroni4 = $check (force (head [] : "abc")) -- amb 'force' de deepseq a Forma Normal, sí que prefixa la posició

consEstricteALElem !x xs = x:xs -- ! al paràmetre, l'avalua a ''WHNF'' (vegeu ext. BangPatterns)

prova :: String -> String -> IO ()
prova títol cas = do
 putStr $ "cas " ++ títol ++ ": "
 putStrLn cas `catch` (\(excep :: SomeException)  print excep)

main = do
 forM_ [("erroni1", erroni1),("erroni2", erroni2),
 ("erroni3", erroni3),("erroni4", erroni4)]
 $ \(títol, cas) -> prova títol cas

dona, mostrant si s'avalua o no el codi erroni, assenyalant-ne la posició:

$ runhaskell prova
cas erroni1: prova.hs:11:11-16: Prelude.head: empty list
cas erroni2: Prelude.head: empty list
cas erroni3: prova.hs:13:11-16: Prelude.head: empty list
cas erroni4: prova.hs:14:11-16: Prelude.head: empty list

Traçabilitat

modifica

Vegeu ref.[203]

En el codi funcional

modifica

L'ordre d'impressió de diverses traces en una expressió serà aleatori!!, perquè l'ordre d'execució de subexpressions en el codi funcional és indeterminat.

  • trace: mostra un missatge mitjançant unsafePerformIO
  • traceShow: mostra el valor d'una variable textualitzable
  • traceStack (des de GHC 7.4): Cal compilar amb la biblio. de perfilat (ang:profiling) com trace afegint la pila de crides simulada de l'estudi de costos.

En context seqüencial

modifica
  • traceIO: mostra el missatge en el context de la mònada IO, retornant unit.
  • traceM: mostra el missatge en un context seqüencial (monàdic o bé aplicatiu), retornant unit.
  • traceShowM: mostra el valor d'una variable textualitzable en context seqüencial (monàdic o bé aplicatiu), retornant unit.

EventLog Tracing

modifica

És una traça per al registre d'esdeveniments, per a ser llegides per eines per l'ajustatge del rendiment (perfilat, ang:profiling).[204]

  • Cal compilar amb l'opció -eventLog.[205]
  • per a mostrar la traça textualment, cal compilar a més a més amb -debug i executar amb +RTS -v[flags].[206] Mostra les traces pel dispositiu de sortida stdout.
  • per a un ús de la traça per eines de perfilat, s'envien les traces al fitxer nom_programa.eventlog, amb codificació binària (més compacte), executant amb +RTS -l[flags].[206]

Exemple:

import Control.Monad
import Debug.Trace

main = forM_ [1..6] $ \cnt -> do
 traceEventIO $ "cnt: " ++ show cnt
$ ghc --make -eventlog -debug -rtsopts prova.hs
Linking prova...
$./prova +RTS -vu
...

Més problemàtiques

modifica

El problema de les configuracions

modifica

Se n'hauria de dir millor "el problema de les preferències canviants" o "de l'entorn global modificable".

L'accés, des del codi funcional, a variables globals carregades en temps d'execució es pot abordar des de diverses perspectives, cap d'elles plenament satisfactòria.[207]

  • Amb la mònada Reader[208] (proporciona accés a variables d'entorn d'un entorn canviant). Desavantage: no-accessible des del codi funcional, caldrà, llavors, passar-lo com a paràmetre.
    • Intent de solució mitjançant paràmetres implícits: Desavantatge: cal propagar el paràmetre implícit a totes les declaracions de tipus de les rutines intermèdies.
  • Accedint via unsafePerformIO, assegurant-se bé que l'escriptura de les variables ha estat avaluada ("the unsafePerformIO hack").[209] Desavantatge: hi ha el risc que les ops. no s'efectuïn en l'ordre correcte si no se'n controla bé l'avaluació.
  • Altres sistemes més sofisticats:
    • Segons Joachim-Breitner[210][211]
    • Segons Oleg Kiseliov i Chung-chieh Shan[212]

Una solució: El hack unsafePerformIO

modifica

unsafePerformIO[213] És una manera poc ortodoxa de definir variables globals mudables, com les ref del ML, modificar-les i accedir-hi des de codi funcional pur, sense necessitat de passar-les com a paràmetre

La variabilitat de les definicions globals trenca el principi de transparència referencial (constància del resultat d'una funció amb les mateixes entrades) de les funcions que hi accedeixen.

El resultat dependrà de l'ordre de les operacions mutadores i del moment que se n'avaluï el thunk corresponent. (Vegeu "IO inside: The dark side of the IO Monad".)[214]

hack, en informàtica, segons la viqui. anglesa vol dir "solució poc elegant però que permet sortir del pas",[215] aquí en diríem "un pedaç".

unsafePerformIO[213] és la porta del darrere de la mònada IO.[214]

unsafePerformIO :: IO a -> a

Per emprar amb seguretat unsafePerformIO en el codi funcional, cal que l'acció no-funcional mantingui la idempotència (no tingui efectes col·laterals i sigui independent de l'entorn).[213]

Aquesta solució és incompatible amb la certificació Safe Haskell.

El sistema més correcte és tractar les preferències com un entorn canviant, fent servir la mònada Reader o bé el transformador de mònades corresponent ReaderT com s'explica tot seguit.[216]

Tractament d'un entorn canviant, amb la mònada Reader

modifica

És la millor solució al problema de les preferències (variables globals) que tracta com un entorn variable mitjançant l'aplicació d'una funció (entorn -> entorn)

newtype Reader env a = Reader { runReader :: (env -> a) } -- el valor de la mònada Reader és una funció sobre l'entorn ('env' abbrev. de environment)

class MonadReader env m | m -> env where
 ask :: m env -- obté l'entorn
 local :: (env -> env) -> m a -> m a -- avalua localment una acció amb l'entorn modificat

asks :: MonadReader env m => (env -> a) -> m a -- obté el resultat d'una projecció sobre l'entorn

Exemple d'ús del transformador de mònades equivalent de la mònada Reader (ReaderT) en un procés interactiu:

{-# LANGUAGE PackageImports #-}
import "mtl" Control.Monad.Reader (ReaderT(runReaderT), MonadReader(ask, local)) 
import "mtl" Control.Monad.Trans (liftIO)
import Control.Monad (when)
import System.IO (hFlush, stdout)
import "safe" Safe (readMay)

type Env = Int -- entorn simplificat per a l'exemple
entorn_inicial = 0

bucle :: ReaderT Env IO ()
bucle = do
 env <- ask -- obté l'entorn en aquest punt
 str <- liftIO $ do -- cal elevar amb ''liftIO'' el tipus de les oper. de la mònada IO a la transformada
 putStrLn $ "l'entorn és: " ++ show env 
 putStrLn "entreu valor Int a afegir (cas de <= 0, acaba): "
 hFlush stdout
 getLine

 case (readMay str :: Maybe Int) of
 Nothing -> do
 liftIO $ putStrLn "entrada incorrecta!!"
 bucle
 Just v -> when (v > 0) $ local (v+) bucle -- si v > 0, repeteix amb l'entorn modificat per (v+)
 -- altrament, acaba
main :: IO ()
main = runReaderT bucle entorn_inicial

El problema del sobreiximent de la pila

modifica

L'ús del "plegat per l'esquerra estricte" foldl' no garanteix que es treballi a espai constant de la pila.

En cas de funcions amb recursivitat final,

  • les expressions (o variables definides localment) en la crida recursiva generen thunks pendents d'avaluar, que s'apilen mentre no s'arriba al cas no recursiu.
  • l'avaluació estricta (amb '!' BangPatterns (a WHNF)) en els paràmetres acumuladors tampoc ho garanteix, si l'avaluació es topa amb un constructor perquè per disseny els constructors aturen l'avaluació.

Vegeu "Stack overflow" al HaskellWiki[180] i la secció Haskell#Avaluació estricta explícita (treballar a espai constant de la pila). També "Retainer profiling" per investigar "Perquè l'avaluació d'un objecte és retinguda"[217]

La solució més fàcil és que el tipus de l'acumulador algebraic estigui definit estricte (prefix '!') en tots els seus components.

Altrament el paquet deepseq[218] aporta funcions d'avaluació en profunditat a Forma Normal. El paquet deepseq-th[219] facilita la derivació d'instàncies de la classe NFData (contracció de NormalFormData), només per als tipus algebraics. Altrament caldrà instanciar-la manualment, definint la funció rnf (acrònim de reduce to normal form) de manera que per cada constructor n'avaluï tots els components.

-- ''deepseq'': versió de ''seq'' que avalua també els components
-- dels tipus de dades, si i només si, aquests instancien NFData,
-- derivable, si el tipus és algebraic, amb el paquet deepseq-th.
deepseq :: NFData a => a -> b -> b

El problema de les fugues de memòria

modifica

Vegeu enllaços "Memory leaks","[220] Space leak zoo"[221]

Eines per l'ajustatge: "GHC: Perfilant (ajustant) l'ús de memòria".[222]

El maleït problema de les dependències de biblioteques (ang: cabal dependency hell)

modifica

El relligat requereix que hi hagi una sola versió de cadascuna de les biblioteques relligades.

El desenvolupament en aïllament (sandbox) es fa per evitar dependre de biblioteques instal·lades en l'entorn d'usuari, subjectes a alteracions per la instal·lació d'aplicacions i també per evitar que estiguin relligades amb biblioteques instal·lades relligades al seu torn amb versions diferents d'una mateixa biblioteca.

cabal sandbox init inicialitza un dipòsit de biblioteques exclusiu del projecte,[223] que impedeix tenir múltiples versions instal·lades d'una mateixa biblio.

Tanmateix la incorporació a posteriori de biblioteques en un projecte, pot portar a la situació que les dependències de la nova siguin incompatibles amb el conjunt de biblioteques instal·lades, i calgui eliminar el dipòsit i reintentar-ho de cap i de nou, havent de reinstal·lar-ho tot, amb la consegüent aturada d'hores que suposa la recompilació d'un programa complex per la quantitat de biblioteques com ara els servidors web yesod.

Stackage - rebost alternatiu amb dependències verificades

modifica

Stackage[224] vol ser una solució al problema de les dependències (biblioteques de les quals es depèn), oferint les biblioteques en conjunts de versions compatibles entre elles per una determinada versió de GHC garantint la correctesa de la compilació. L'evolució per modificació de les biblioteques resultarà en un seguit d'imatges instantànies (ang: snapshots) del conjunt de biblioteques, promovent que tots els autors s'actualitzin quan es produeix una modificació en una biblio. que trenqui la compatibilitat, quedant fora del conjunt les que no s'actualitzin.[225]

Les imatges etiquetades "Nightly" (compilació nocturna o en desenvolupament) tenen versions més recents de biblioteques, però poden ometre aquelles endarrerides respecte al suport de dependències que han modificat l'API i les que en depenguin, mentre que les etiquetades LTS Haskell (Long Term Support) són més completes però no tant recents.[226][227][228]

Cal especificar el rebost al fitxer cabal.config de la carpeta del projecte amb la propietat remote-repo. I després fer cabal update des del mateix directori.

Exemple per a l'edició lts-6.27:

# si utilitzem el gestor cabal, al fitxer cabal.config del projecte
remote-repo: stackage-lts-6.27:http://www.stackage.org/lts-6.27

# o bé si utilitzem el meta-gestor "stack"
stack --resolver=lts-6.27

Stackage, contracció de "Stable, Vetted Hackage" (cat: rebost Hackage estable i verificat), és una iniciativa[229] dels creadors de l'entorn de desenvolupament web Yesod, auspiciada per FPComplete.[230]

Programació de nivell baix

modifica

Aspectes d'optimització

modifica
  • "Més aviat: com fer que un programa compili més de pressa".[232]
  • "Més ràpid: com fer que un programa corri més".[233]
  • "Més petit: com fer que un programa ocupi menys".[234]
  • "Més auster: com fer que un programa engoleixi menys memòria dinàmica".[235]

Optimitzacions d'allotjament

modifica

Tipus primitius que es passen per valor (no encapsulats, ang:unboxed)

modifica

Boxing és el procés de convertir una dada que es passa per valor en un objecte que es passa per referència. Unboxing és el procés invers.

Els valors "no encapsulats" eviten l'allotjament en memòria dinàmica, però en ésser valors nus no disposen d'informació de tipus i les funcions polimòrfiques no s'hi podrán aplicar.[236]

Els operadors, tipus i literals de dades "unboxed" hauran de dur un indicador '#' al darrere, extensió de sintaxi que requereix la pragma {-# LANGUAGE MagicHash #-}:

  • tipus: Int#, Float#, Double#
  • operadors: (+#), (-#), (*#), (==#)
  • literals: 4# 3.2# 'c'#
De unboxed a boxed
  • el tipus de 4# és Int# ;
  • Amb el constructor I# de GHC.Exts, el tipus de (I# 4#) és Int
De boxed a unboxed per encaix
let !(I# unboxed_int) = 5::Int

Vegeu[237] i el farragós exemple següent:

{-# LANGUAGE MagicHash, BangPatterns #-}
import GHC.Exts (Int#, Int(I#)) -- importa el tipus Int# i el constructor I# del tipus Int
import GHC.Prim ((*#),(-#), (==#), (>#))

fact_rf :: Int#  Int#  Int#
fact_rf !acum !n -- avaluació estricta dels paràmetres
 | n ==# 0# = acum
 | n ># 0# = fact_rf (acum *# n) (n -# 1#)

imprimeix :: Int#  IO ()
imprimeix unboxed_int = do
 putStrLn $ "resultat " ++ show (I# unboxed_int) -- de unboxed a boxed aplicant el constructor

main = do
 let !(I# unboxed_int) = 5::Int -- de boxed a unboxed per encaix (requereix estrictesa(!))
 result = fact_rf 1# unboxed_int
 imprimeix result

Desencapsulament en un tipus - La pragma UNPACK

modifica

Desempaqueta un component d'un constructor de dades en el mateix constructor evitant un nivell d'indirecció.[238]

data T = T {-# UNPACK #-} !Float
 {-# UNPACK #-} !Float

La pragma {-# OPTIONS_GHC -funbox-strict-fields #-} ho fa automàtic.

Des de ghc 7.8, la optimització UNPACK ve per defecte, per als components estrictes de mida "petita" (igual o inferior a la de la paraula de la màquina, i també per als de coma flotant).[239]

Tuples no encapsulades per al retorn de múltiples valors

modifica

Les tuples unboxed[240]

(# a, b #)

són un capítol a part i s'utilitzen per retornar més d'un valor alhora, sense allotjar la tupla a la memòria dinàmica. Cal especificar l'extensió de lleng. UnboxedTuples i l'opció -fobject-code (generació de codi nadiu, perquè en codi intermedi (bytecode) no compila).

-- fitxer tuples-no-encapsulades.hs
{-# OPTIONS_GHC -fobject-code #-} -- força la generació de codi nadiu, descartant la sortida a bytecode
{-# LANGUAGE UnboxedTuples #-}

f x y = (# x+1, y-1 #) -- retorn amb tupla ''no encapsulada''

g x = case f x x of

 (# a, b #)  a + b -- obrim la tupla per encaix del patró ''(#... #)''

main = do
 let y = g 5
 putStrLn $ "y = " ++ show y

Compilació i exec.

ghc —make tuples-no-encapsulades.hs

./tuples-no-encapsulades

Especialització d'operacions sobrecarregades

modifica

L'especialització es fa servir per millorar el rendiment en les operacions sobrecarregades, quan es fan servir molt amb un tipus concret. Cal especificar la pragma SPECIALIZE.[241]

{-# LANGUAGE BangPatterns #-}

factorial :: (Num a, Ord a) => a  a
factorial 0 = 1
factorial n
 | n > 0 = factorial_rf 1 n
 | otherwise = error "n no pot ser negatiu"
 where
 factorial_rf !acum !n -- aval. estricta als paràmetres formals
 | n == 1 = acum
 | n > 1 = factorial_rf (acum * n) (n-1)

{-# SPECIALIZE factorial :: Int → Int #-}
{-# SPECIALIZE factorial :: Integer → Integer #-}

-- en detectar el compilador el tipus esmentat a la pragma, genera versions no sobrecarregades, més eficients, de la funció.

Safe Haskell

modifica

Vegeu ref.[242][243] És una extensió de llenguatge per restringir l'ús de diverses construccions, com ara unsafePerformIO[213] que permet trencar la transparència referencial de les expressions funcionals pures (constància del resultat en diferents invocacions amb les mateixes entrades).

Pretén assegurar les propietats següents:

  • seguretat dels tipus
  • transparència referencial
  • encapsulament estricte de mòduls
  • raonament modular
  • consistència semàntica.

Cal esmentar la pragma {-# LANGUAGE Safe #-}.

  • eta-lang.org és una modificació de GHC per generar codi JVM dels executables i biblioteques en fitxers ".jar" amb interoperabilitat amb classes Java, permetent l'ús de biblioteques haskell actuals en entorn Java, amb suport per la majoria d'extensions de GHC.
  • eta: és el nom que pren el GHC modificat, admetent sintaxi FFI (Foreign Function Interface) ampliada d'enllaç amb Java per a l'importació / exportació, (<:) per al requeriment d'implementació d'interfícies Java, ...
  • etlas: és una adaptació del programa cabal.
  • Frege[244] és una aproximació a Haskell sobre la JVM incompatible amb l'estàndard del llenguatge. (ex. a les def. de classes el nom de la classe precedeix el requeriment de context, al revés del Haskell, la precedència dels operadors atorga nivells de 0 a 16 quan al Haskell és de 0 a 9, ...)[245]

JavaScript com a rerefons de GHC (Haskell al navegador web)

modifica

JavaScript té el desavantatge de tenir un codi molt fràgil, però és una eina que s'hi pot comptar a tots els navegadors web.[246]

Haste[247][248][249][250] desenvolupat a la Universitat Chalmers de Göteborg i també GhcJs[251][252] són modificacions de GHC amb generació de codi JavaScript partint de la sortida STG del compilador.[253]

Permeten utilitzar primitives específiques juntament amb pràcticament qualsevol codi Haskell compilable amb GHC, i generar-ne codi JavaScript.

Exemple amb Haste, instal·lació (cal versió cabal-1.18+ per la compilació aïllada):

mkdir haste && cd haste # crea carpeta per la compilació aïllada

cabal sandbox init # inicialitza un dipòsit de biblioteques exclusiu del projecte
cabal update
cabal install haste-compiler
#
export PATH=$PWD/.cabal-sandbox/bin:$PATH # afegeix al PATH la carpeta d'executables generats

haste-boot # inicialitza i compila a Javascript les biblioteques bàsiques de GHC.

Programa d'exemple[247] que mostra en un element html amb id="id_sortida", el contingut de la casella de text (elem. input type="text") amb id="id_entrada_de_text"

import Haste

main = do
 Just inp <- elemById "id_entrada_de_text"
 Just outp <- elemById "id_sortida"

 onEvent inp OnKeyUp $ \_ -> do
 text <- getProp inp "value"
 setProp outp "innerHTML" text
# compilació, genera "prova.js"
hastec prova.hs

# caldrà incloure (<script src="prova.js" type="text/javascript"></script>)
# al codi de capçalera de la pàg. html
hlint
Aquí[254] analitzador de codi amb suggeriments de millora. Avisa de construccions redundants i proposa alternatives al codi.
cabalg
descarrega i instal·la des del GitHub. Obsolet (actualment les instal·lacions són via Stack)
cabalg https://github.com/usuari-gh/projecte-gh # descarrega ('git clone') a la carpeta 'projecte-gh' i instal·la
cabal-dev
(ang:"Sandboxed haskell build environments") Permet desenvolupar un projecte cabal amb un dipòsit de biblioteques específic del projecte (sub-carpeta cabal-dev/), per aïllar-se d'interaccions amb les biblioteques de l'entorn d'usuari/sistema corresponents a les aplicacions instal·lades. Caldrà afegir a la var. d'entorn PATH la carpeta cabal-dev/bin.[255]
  • Obsolet. La funcionalitat de cabal-dev ha estat incorporada al programa Cabal amb el verb sandbox i cabal-dev ja no s'actualitzarà. cabal sandbox init inicialitza el dipòsit de biblioteques del projecte en el subdirectori .cabal-sandbox deixant els executables a .cabal-sandbox/bin.
cabal-meta (cabal multi-projecte)
Permet especificar una llista de subprojectes locals o bé remots (git/hackage) per a un muntatge encadenat dels projectes dependents, en cas de modif. en un subprojecte. Obté la llista de subprojectes d'un fitxer sources.txt que pot referenciar altres directoris que també en continguin, per un tractament recursiu.[256]
.- instal·la (amb cabal-dev cas de --dev), en una sola ordre diversos paquets (del hackage o bé subprojectes de directoris locals o remots (git))
.- als fitxers sources.txt cada línia pot contenir:
  • cas de començar per '.' o bé '/': directori (seguit d'opcions) que pot contenir un altre fitxer sources.txt i/o un projecte cabal. (les opcions són flags per al cabal)
  • cas de prefix "git:", "https", "http": gitLocation gitBranca? opció*, és a dir, projecte recuperable amb git (sistema de control de versions distribuït)(les opcions són flags per al cabal)
  • altrament: paquet-del-hackage opció*
cabal-ghci
engega ghci preparant-lo per a proves del projecte, amb camins i extensions de llenguatge obtinguts del fitxer de projecte.cabal[257]
  • Obsolet !, Aquesta funcionalitat ha estat incorporada a cabal amb el verb repl (acrònim de read-eval-print loop) (cabal repl)
yackage
servidor de rebost "hackage" local per a desenvolupament.[258]
ThreadScope
Visualitzador gràfic de les traces d'esdeveniments corresponents a cada "capacitat" (fil d'exec. corresp a un processador elemental, sobre el qual s'executen els fils d'exec. lleugers).[259] Estudi "Ajustatge fi del paral·lelisme amb ThreadScope".[260]
hpc (haskell program coverage)
Anàlisi de cobertura del codi. Mostra, després de diverses execucions, amb marcatge de colors quines parts del codi no s'han executat mai i condicions sempre certes o sempre falses.[261]
  1. compilar amb l'opció -fhpc
  2. executar diverses vegades amb diferents entrades
  3. invocar: hpc markup nom_executable (genera hpc_index.html i altres html)
  4. obrir hpc_index.html amb un navegador
  5. per recomençar l'estudi, i també després de recompilar, cal eliminar el fitxer nom_executable.tix on guarda les traces d'execució.
hp2any (obtenció del perfil d'ús de memòria)
  • hp2any-graph mostra en temps-real un gràfic de l'evolució de l'ocup. de memòria.[262]
  • hp2any-manager mostra gràfics de fitxers de perfil (.hp) corresp. a execucions ja finalitzades.
més eines de desenvolupament al HaskellWiki

Vegeu ref.[263]

Verificació Formal estàtica amb la biblio LiquidHaskell

modifica

"LiQuiD Types" és un nom adaptat de la contracció de "Logically Qualified Data Types".[264]

La biblioteca LiquidHaskell[265][266] permet la verificació mitjançant anotacions de "tipus refinats amb predicats"[267][268] (actualment només de tipus enters) que cal acotar entre {-@ @-} com ara:

{-| comprova si es pot deduir de les crides, 
 que l'exponent no serà negatiu, estalviant tractar l'excepció que (^) llançaria.
 (^) és una funció parcial, definida per a valors de l'exponent enters no negatius
-}

{-@ -- anotacions per al programa 'liquid'
type Nat = {v:Int | v >= 0}

elevaAPotènciaNatural :: Num a => a -> Nat -> a 
@-}

elevaAPotènciaNatural :: Num a => a -> Int -> a
elevaAPotènciaNatural base exponent = base ^ exponent

i aporta un programa de verificació formal, anomenat liquid, que utilitza un solucionador de lògica de predicats SMT (Satisfiability Modulo Theory)[269] per comprovar que el codi compleix els predicats. A més incorpora comprovació de totalitat (contrari de parcialitat en funcions per garantir l'acabament dels programes).

Vegeu sintaxi, i l'apartat sobre mètrica dels tipus de dades: "2.6 Measures: From Integers to Data Types".[268] Explicació més planera aquí.[270] Vegeu bloc.[271]

És convenient separar cada instrucció LH en un bloc {-@ @-} a part, per entendre millor els errors de l'analitzador sintàctic del liquid.

Líquid permet l'ús en predicats de mètriques dels tipus de dades. Per fer servir una funció com a mètrica cal especificar-ho al LH amb el qualificador measure.

Darrerament també és possible especificar mètriques d'acotació de funcions recursives per garantir-ne l'acabament.

{-| per utilitzar una funció mètrica de tipus de dades, a les condicions dels predicats, 
 cal declarar-la amb el qualificador 'measure' -}

{-@ type Nat = {v:Int | v >= 0} @-}

-- | mètrica de llistes

llarg :: [a] -> Int
llarg [] = 0
llarg (x:xs) = 1 + llarg xs

{-@ measure llarg :: [a] -> Nat @-} -- declara 'llarg' com a mètrica per al seu ús en predicats de refinament

{-@ type LlistaNoNulaDeLlargadaSuperiorA MaxIdx a = {v:[a] | llarg v > 0 && llarg v > MaxIdx } @-}

-- | indexació verificant que la llargada serà superior a l'índex

indexa :: Int -> [a] -> a
{-@
indexa :: i:Nat -> LlistaNoNulaDeLlargadaSuperiorA i a -> a
@-}
indexa i (x:xs)
 | i == 0 = x
 | i > 0 = indexa (i-1) xs
 | otherwise = error "índex negatiu -- opció que no es donarà, si la verif. passa bé (i:Nat)"

indexa i [] = error "índex sobrepassa llargada -- opció que no es donarà si la verif. formal passa bé"

llista :: [Int]
{-@ llista :: LlistaNoNulaDeLlargadaSuperiorA 3 Int @-} -- MaxIdx == 3
llista = [1,2,3,4]

main = print $ indexa 2 llista

Comprovació amb el solucionador de lògica de predicats SMT MathSAT:.[272]

$ liquid --smtsolver=mathsat Main.hs
**** DONE: fixpoint ***********************************************************
**** DONE: solve **************************************************************
**** DONE: annotate ***********************************************************
**** SAFE **********************************************************************

Exemples

modifica

Degut a un robot viquipèdic que converteix les fletxes del codi -> en →, per poder compilar els exemples cal afegir l'extensió de llenguatge UnicodeSyntax, com ara {-# LANGUAGE UnicodeSyntax #-}.

Arrencada amb opcions

modifica

Exemple amb definició i anàlisi d'opcions d'arrencada per al nostre programa.

Per afegir opcions a l'intèrpret d'ordres vegeu l'enllaç.[273][274][275]

-- per una lectura correcta dels caràcters no anglosaxons a l'intèrpret de comandes a Linux (''shell'')
-- 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 System.Console.GetOpt

import Data.Maybe (fromMaybe)
import Text.Printf (printf)

-- un tipus per a les nostres opcions d'arrencada (en ang.: ''flags'')
data Flag = FVersio | FEntrada String | FSortida String | FArgLliure String {- els params. -}
 deriving (Eq, Show)

-- taula d'opcions amb nom curt, nom llarg, descriptor, missatge d'ajuda.
-- cadascuna pot venir amb paràmetres obligatoris (ReqArg), opcionals (OptArg), o sense (NoArg)
opcions :: [OptDescr Flag]
opcions = [
 Option ['V'] ["versió"] (NoArg FVersio) "mostra versió",
 Option ['i'] ["entrada"] (ReqArg FEntrada "FILE") "entrada requereix un nom de fitxer",
 Option ['o'] ["sortida"] (OptArg creaFlagSortidaAmbValorPerOmissió "FILE") "sortida"
 ]

creaFlagSortidaAmbValorPerOmissió :: Maybe String -> Flag
creaFlagSortidaAmbValorPerOmissió ms = FSortida (fromMaybe "sortida_per_defecte" ms)

-- embolcalla els arg. lliures com a ''flags'' per un tractament unificat
argQueNoEsOpció :: String -> Flag
argQueNoEsOpció arg = FArgLliure arg

msg_com = "Ús: nom_prog [-V] [--versió] [-i nom_fitxer] [--entrada nom_fitxer] [-o sortida] [--sortida sortida] paràmetres..."

processa flags = do
 print flags
 exitSuccess

error_als_args nom_prog msgs_error = do
 printf "%s error als paràmetres\n" nom_prog
 putStrLn $ concat msgs_error ++ usageInfo msg_com opcions
 exitWith $ ExitFailure 1 -- sortir amb el codi de finalització

main = do
 nom_prog <- getProgName
 args <- getArgs
 case getOpt (ReturnInOrder argQueNoEsOpció) opcions args of
 -- cas de manca d'errors (llista buida al 3r de la tupla)
 (flagsCorrectes, [], []) -> processa flagsCorrectes
 -- altrament
 (_, _, msgs_error) -> error_als_args nom_prog msgs_error

Excepcions de tipus definits per l'usuari

modifica

Haskell2010 millora les excepcions del Haskell98, permetent definir-ne tipus nous (amb els camps que convingui), que només han d'instanciar Show i la implementació per defecte de la classe Exception, prèvia derivació de Data.Typeable[276]

 -- per implementar Exception, cal que prèviament implementin les classes requerides pel context

 class (Typeable e, Show e) => Exception e

L'extensió DeriveDataTypeable facilita la derivació d'instàncies de Typeable pel compilador quan s'esmenta en una clàusula deriving.

{-# LANGUAGE DeriveDataTypeable #-}

import Control.Exception
import Data.Typeable

data TExcepcionsDeLAplicacio = EParametreIlegal String | EUnaAltraExcepcio String
 deriving (Show, Typeable) -- fem que el compilador derivi les instàncies requerides per la classe Exception

instance Exception TExcepcionsDeLAplicacio

Exemple:

{-# LANGUAGE DeriveDataTypeable, -- important !!
 ScopedTypeVariables, BangPatterns #-}

import Prelude hiding (catch) -- cal fer servir el ''catch'' de Control.Exception
{- Del Prelude
-- Non-I/O exceptions are not caught by this variant; to catch all
-- exceptions, use 'Control.Exception.catch' from "Control.Exception".
-- The 'catch' function is deprecated. Please use the new exceptions
-}
import Control.Exception
import Data.Typeable

data TExcepcionsDeLAplicacio = EParametreIlegal String | EUnaAltraExcepcio String
 deriving (Show, Typeable) -- important !!

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

factorial :: Int  Maybe Integer
factorial n | n == 0 = Just 1
 | n > 0 = Just (fac_rf n 1)
 | otherwise = Nothing
 where -- aval. estricta (!) als param. formals (extensió BangPatterns)
 fac_rf !m !acum | m > 0 = fac_rf (m-1) (acum * toInteger m)
 | m == 0 = acum

avalua_fac :: Int  IO ()
avalua_fac n = case factorial n of
 Just r  putStrLn ("resultat: " ++ show r)
 Nothing  throwIO (EParametreIlegal "avalua_fac: param. fora del domini")

-- els comentaris d'autodoc de Control.Exception "Catching exceptions" recomanen
-- caçar les excepcions síncrones amb "try"
-- caçar les asíncrones amb "catch" o bé "catches" o similars
-- (http://hackage.haskell.org/package/base/docs/Control-Exception.html#g:3)

prova_fac :: Int  IO ()
prova_fac n = case try (avalua_fac n) of
 Right v  putStrLn "no ha petat"
 Left (EParametreIlegal msg)  putStrLn $ "paràmetre ilegal! " ++ msg
 Left excep  putStrLn $ "excepció: " ++ show excep

main = prova_fac (-1)
--

Incorporant transformacions a les llistes per comprensió amb la clàusula then.[277] En cas d'emular una consulta SQL agrupada (Group by) cal prefixar els camps agrupats amb la clàusula the. Cal esmentar la pragma {-# LANGUAGE TransformListComp #-}

{-# LANGUAGE TransformListComp #-}
import GHC.Exts (the, groupWith, sortWith)
import Data.Function ((&)) -- (&) = flip ($) -- infixl 1 -- desde GHC 7.10.1

-- el prefix _ com a _nom és per evitar els ''Warnings'' en símbols no utilitzats

consulta = [ (the dept, avg salari)
 | (_nom, dept, salari) <- empleats,
 then group by dept using groupWith,
 then sortWith by (avg salari),
 then take 5 ]

empleats = [ ("Xavi", "Vendes", 80),
 ("Montse", "Vendes", 100),
 ("Vicenç", "Programació", 40),
 ("Biel", "Programació", 45),
 ("Anna", "Comptabilitat", 60)]

avg :: (Fractional a) => [a]  a
avg [] = 0
avg xs = sum xs / fromIntegral (xs & length)

main = print consulta

dona

 [("Programació",42.5),("Comptabilitat",60.0),("Vendes",90.0)]

vectors a la mònada ST

modifica

La mònada ST permet elaborar funcions per a ser cridades des de codi funcional, que encapsulen l'encadenament de canvis d'estat d'àmbit local, en aquest cas, de vectors.

1.- Amb vectors amb índexs de tipus divers en un rang, i elements d'allotjament directe (Unboxed), com a (UArray tipusIndex tipusElem).

Creació del vector com a immutable, descongela a mudable (thaw), modificació, recongelació(freeze) i llistat:

import Data.Array.IArray as IArray -- interfície vectors immutables
import Data.Array.MArray as MArray -- interfície vectors mudables

import Data.Array.Unboxed -- vector immutable UArray d'elements d'allotjament directe
import Data.Array.ST -- vector mudable STUArray d'elements d'allotjament directe a la mònada ST
import Control.Monad.ST -- encapsula canvis d'estat local dins de codi funcional

modificaArray :: Int  UArray Int Int  ST s (UArray Int Int)
modificaArray indx immVect = do
 mutVect <- thaw immVect :: ST s (STUArray s Int Int) -- descongela a mudable
 x <- readArray mutVect indx
 writeArray mutVect indx (x - 1)
 freeze mutVect -- congela a immutable

main = do
 let immVect = IArray.listArray (0,1) [0,1] :: UArray Int Int -- creació amb rang, elements i tipus
 immVect2 = runST $ modificaArray 0 immVect
 print $ elems immVect2

2.- Amb vectors "eficients" (índex Int basat en 0) i tipus del vector (Vector tipusElem) del paquet Vector.[96] Avantatge: implementa força més classes i disposa d'algoritmes específics al paquet vector-algorithms.[278]

{-# LANGUAGE UnicodeSyntax #-}

import Data.Vector.Unboxed (Vector, MVector)
import Data.Vector.Unboxed as V
import Data.Vector.Unboxed.Mutable as MV
import Control.Monad.ST

modificaArray :: Int  Vector Int -> ST s (Vector Int)
modificaArray indx immVect = do
 mutVect <- V.thaw immVect :: ST s (MVector s Int) -- descongela a mudable
 x <- MV.read mutVect indx
 MV.write mutVect indx (x - 1)
 V.freeze mutVect -- congela a immutable

main = do
 let immVect = V.fromList [0,1] :: Vector Int -- creació amb elements i tipus
 immVect2 = runST $ modificaArray 0 immVect
 print $ V.toList immVect2

diccionaris eficients amb HashMap

modifica

Hashable[87] aporta instàncies de funció resum per als tipus més freqüents.

HashMap i HashSet són més ràpids que les implementacions basades en arbres, especialment quan la comparació de claus és lenta, com és el cas del tipus String.[279]

L'anterior implementació de la biblioteca HashMap del paquet hashmap que feia servir un arbre balancejat per als cistells de claus coincidents requerint (Ord clau), ha quedat desaconsellada en favor de la biblioteca unordered-containers que fa servir llistes per als cistells requerint (Eq clau).

{-# LANGUAGE OverloadedStrings #-}

import Control.Exception (assert)
import Data.Text (Text)
import qualified Data.Text.IO as TextIO

import Data.HashMap.Lazy (HashMap, member, (!), lookup) -- aquí també s'importen les instàncies de Hashable
import qualified Data.HashMap.Lazy as Map

-- el tipus Text que implementa Hashable, serà la clau del diccionari eficient

dicc :: HashMap Text Text
dicc = Map.fromList [("primer", "el que va davant"),
 ("segon", "el que ve després")]

main = do
 assert (Map.member "primer" dicc) -- (!) és funció parcial, és per això que verifiquem la precondició
 $ TextIO.putStrLn $ dicc ! "primer" -- (!) és Map.!

 case (Map.lookup "segon" dicc) of -- amb Map.lookup (resultat opcional) no cal la comprovació precedent
 Just txt  TextIO.putStrLn txt
 Nothing  TextIO.putStrLn "el segon no hi era"
# el paquet "unordered-containers" que conté Data.HashMap.Lazy ve amb la plataforma Haskell
runhaskell prova.hs

Tots els paquets d'expressions regulars implementen (=~) que dona èxit i coincidències segons el tipus del resultat. (=~~) equival a (=~) en el context d'una mònada (text =~~ regex = return $ text =~ regex). Vegeu ref.[280]

  • L'operació (=~) està sobrecarregada, oferint diferents informacions segons el tipus del resultat, exigible amb una restricció de tipus:
  • Bool: indica si l'encaix té èxit
  • String: ofereix el text encaixat
  • MatchArray: informa de les posicions d'encaix dels grups, els elements són :: (posició, llargada)
  • (txt, MatchText txt, txt): informa (text_precedent, encaixos, text_posterior), l'elem. dels encaixos és :: (txt, (posició, llargada))
  • [MatchArray]: cerca múltiple oferint la llista [Array Int (posició, llargada)]
  • [MatchText txt]: cerca múltiple oferint la llista [Array Int (txt, (posició, llargada))]

En cas d'efecte lateral per l'ús d'una biblioteca externa, l'operador (=~~) ofereix la mateixa funcionalitat que (=~) però en el context seqüencial d'una mònada.

Prova: cabal install regex-pcre

$ ghci
Prelude> import Text.Regex.PCRE
> import Control.Monad (forM_, mapM_)
> import Data.Function ((&)) -- (&) ≡ flip ($)
> import Data.Foldable (toList)
> import Control.Category ((>>>)) -- f >>> g ≡ g. f

> let regex = "(\\d)(\\d)"

> "12" =~ regex :: Bool -- tenim coincidència ? 
True

> "12" =~ regex :: String -- text coincident ?
"12"

> ("12" =~ regex :: MatchArray) & toList -- llista grups :: [(posició, llargada)]
[(0,2),(0,1),(1,1)]

-- (MatchText txt) és un 'array' de grups :: (txt, (posició, llargada))

> "ab12cd" =~ regex :: (String, MatchText String, String) -- (text_precedent, grups, text_posterior)
("ab",array (0,2) [(0,("12",(2,2))),(1,("1",(2,1))),(2,("2",(3,1)))],"cd")

> :{ -- mode multilínia
| ("ab12cd" =~ regex :: (String, MatchText String, String)) 
| & \(pre, matchText, post) -> (pre, (matchText & fmap fst) & toList, post)
| :}
("ab",["12","1","2"],"cd")

-- Per cercar múltiples ocurrències cal especificar el tipus de sortida com una llista de resultats
-- per fer-ho en el context d'una mònada (IO) farem servir (=~~) en comptes de (=~)

> let {origen = "12 34"; regex = "(\\d)(\\d)"}

> (origen =~~ regex :: IO [MatchArray]) >>= (map toList >>> return)
[[(0,2),(0,1),(1,1)], [(3,2),(3,1),(4,1)]]

> :{ -- mode multilínia
| do
| ocurrències <- origen =~~ regex :: IO [MatchText String]
| forM_ ocurrències $ \ ocurrència ->
| (forM_ ocurrència $ \ grup -> (grup & fst & putStrLn))
| :} 
12
1
2
34
3
4

serialització en format JSON

modifica

Per als tipus que instancien la classe Generic (tipus algebraics i d'altres) se'n poden derivar les classes {ToJSON, FromJSON} automàticament.[281]

Amb l'ús de l'extensió DeriveAnyClass, per als tipus algebraics és tan fàcil com afegir les classes {Generic, ToJSON, FromJSON} a la clàusula deriving. Requereix el paquet aeson.

{-# LANGUAGE PackageImports, DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-} -- permet incloure a la clàusula 'deriving' classes que tinguin implem. per defecte

import "aeson" Data.Aeson (ToJSON, FromJSON, encode, decode)
import GHC.Generics (Generic)

data Persona = Persona {nom :: String, edat :: Int} deriving (Show, Generic, ToJSON, FromJSON)
data Llenguatge = Lleng_Fortran | Lleng_Haskell deriving (Show, Enum, Generic, ToJSON, FromJSON)
data Programador = Programador {persona :: Persona, llenguatges :: [Llenguatge]} deriving (Show, Generic, ToJSON, FromJSON)

joanPersona = Persona {nom="Joan", edat=10}
joanProgramador = Programador {persona=joanPersona, llenguatges=[Lleng_Fortran, Lleng_Haskell]}

serialitzat = encode joanProgramador

recuperat = decode serialitzat :: Maybe Programador

main = do
 putStrLn $ "serialitzat: " ++ show serialitzat
 putStrLn $ "recuperat: " ++ show recuperat

dona:

serialitzat: "{\"llenguatges\":[\"Lleng_Fortran\",\"Lleng_Haskell\"],\"persona\":{\"edat\":10,\"nom\":\"Joan\"}}"
recuperat: Just (Programador {persona = Persona {nom = "Joan", edat = 10}, llenguatges = [Lleng_Fortran,Lleng_Haskell]})

Vegeu ref.[282]

{-# LANGUAGE UnicodeSyntax #-}
import Data.Tree (Tree)
import qualified Data.Tree as Tree
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Tuple (swap)
import Data.Maybe
import Data.Function ((&)) -- (&) = flip ($) -- infixl 1 -- desde GHC 7.10.1

-- definició del tipus arbre a Data.Tree
-- data Tree a = Node {rootLabel :: a, subForest :: [Tree a]}

-- generador d'arbre
-- Tree.unfoldTree :: (b → (a, [b])) → b → Tree a

data Espècie = Animal | Vertebrat | Mamífer | Primat | Humà | Mico
 deriving (Eq, Ord, Enum, Show)

parentiu = [(Vertebrat, Animal), (Mamífer, Vertebrat), (Primat, Mamífer),
 (Humà, Primat), (Mico, Primat)]

subespècies :: Map Espècie [Espècie]
subespècies = Map.fromListWith (++) -- combina valors de col·lisions amb (++)
 $ fmap (elemALlista. capgira) parentiu
 where
 -- elemALlista = fmap (\x -> [x]) -- versió tàcita
 elemALlista (k, e) = (k, [e]) -- versió explícita
 capgira = swap

-- funció per generar l'arbre
jerarquitzador :: Espècie  (Espècie, [Espècie])
jerarquitzador x = (etiquetaNus, fills)
 where
 etiquetaNus = x
 fills = Map.lookup x subespècies
 & fromMaybe [] -- llista buida si no és al diccionari

elMeuArbre :: Tree Espècie
elMeuArbre = Tree.unfoldTree jerarquitzador Animal

imprimeixArbre :: (Show a) => Tree a  IO ()
imprimeixArbre arb = arb
 & fmap show -- l'arbre és instància de Functor
 & Tree.drawTree -- genera repr. String d'un arbre de Strings
 & putStrLn

main = imprimeixArbre elMeuArbre

dona el resultat:

Animal
|
`- Vertebrat
 |
 `- Mamífer
 |
 `- Primat
 |
 +- Mico
 |
 `- Humà

Combinació de filtres

modifica

La classe Applicative permet combinar, amb una funció o bé un constructor, els resultats d'una seqüència de computacions.

Prenem com a tipus que l'implementa, les funcions amb entrada del mateix tipus

((->) a) {- 'a' és el tipus del primer paràmetre de (->), o sigui el tipus de l'entrada -}
-- Del codi font del mòdul Control.Applicative

-- implementació de Applicative de les funcions amb entrada del mateix tipus ''a''
instance Applicative ((->) a) {- 'a' és el primer paràmetre d'una funció (a ->) -} where
 pure = const -- generador del tipus ((->) a), que ignora l'entrada
 (<*>) f g x = f x (g x) -- seqüència funcions aplicant-los-hi la mateixa entrada

Exemple de combinació.

import Control.Applicative

ésMúltipleDe :: Integral a => a -> a -> Bool
ésMúltipleDe x y = y `mod` x == 0

-- notació amb les operacions canòniques ''pure'' i (<*>)
ésMúltipleDe3oDe5 :: Integral a => a -> Bool
ésMúltipleDe3oDe5 = pure (||) <*> ésMúltipleDe 3 <*> ésMúltipleDe 5

-- notació amb liftAn definida a Control.Applicative
ésMúltipleDe3oDe5 :: Integral a => a -> Bool
ésMúltipleDe3oDe5 = liftA2 (||) (ésMúltipleDe 3) (ésMúltipleDe 5)

-- notació amb els operadors binaris de les classes Functor i Applicative típics (<$>) i (<*>)
ésMúltipleDe3oDe5 = (||) <$> ésMúltipleDe 3 <*> ésMúltipleDe 5

-- també podem definir:
(<||>) :: Applicative efecte => efecte Bool -> efecte Bool -> efecte Bool
(<||>) = liftA2 (||)

-- llavors
ésMúltipleDe3oDe5 = ésMúltipleDe 3 <||> ésMúltipleDe 5

QuickSort amb Data Parallel Haskell

modifica

Vegeu #Data Parallel Haskell i ref.[40]

1. Mòdul d'operacions vectoritzades

{-# LANGUAGE PackageImports, ParallelArrays #-}
{-# OPTIONS -fvectorise #-}
{-# OPTIONS -fno-spec-constr-count #-}
module QSortVect (quicksortPA) where
import "dph-lifted-vseg" Data.Array.Parallel
import "dph-lifted-vseg" Data.Array.Parallel.Prelude.Double as D
import qualified "dph-lifted-vseg" Data.Array.Parallel.Prelude.Int as I
import qualified Prelude
import Control.Category (>>>) -- f >>> g == g. f

{-# NOINLINE quicksortPA #-}
quicksortPA:: PArray Double  PArray Double
quicksortPA = fromPArrayP >>> qsortVect >>> toPArrayP

qsortVect:: [: Double :]  [: Double :]
qsortVect xs
 | lengthP xs I.<= 1 = xs
 | otherwise =
 let pivot = xs !: 0
 menors = [: x | x <- xs, x D.< pivot :]
 majors = [: x | x <- xs, x D.> pivot :]
 iguals = [: x | x <- xs, x D.== pivot :]

 -- mapeja en paral·lel qsortVect als subvectors (Nested Data Parallellism) !!
 vectors = mapP qsortVect [: menors, majors :]
 in
 (vectors !: 0) +:+ iguals +:+ (vectors !: 1)

2. Mòdul d'operacions NO-vectoritzades

{- Main.hs -}
{-# LANGUAGE PackageImports #-}
import System.Environment
import Text.Printf
import Data.Function ((&)) -- (&) = flip ($) -- infixl 1 -- desde GHC 7.10.1

import "dph-lifted-vseg" Data.Array.Parallel.PArray (fromList, toList)
import QSortVect (quicksortPA)

llistaDesordenada :: Int  [Double]
llistaDesordenada n = map toDouble $ [n,(n-1)..1]

toDouble n = realToFrac n :: Double

ordena :: Int -> [Double]
ordena mida = llistaDesordenada mida & fromList
 & quicksortPA
 & toList

main = do
 args <- getArgs
 nomProg <- getProgName
 case args of
 [arg]  do
 let num = read arg :: Int
 num & abs
 & ordena
 & take 5
 & print

 _  printf "afegiu nombre d'elements\nús: %s 999\n" nomProg
  • Compila i executa
prompt$ ghc --make -threaded -rtsopts -with-rtsopts=-N -Odph Main.hs QSortVect.hs -o main -package dph-lifted-vseg

prompt$ time./main 100000
[1.0,2.0,3.0,4.0,5.0]

real 0m4.065s
user 0m5.552s
sys 0m1.480s

Interfícies gràfiques

modifica

Hola món amb GTK

modifica

Web del projecte GTK per a Haskell aquí.[283]

Cal que el sistema tingui instal·lades versions de desenvolupament (acabades en -dev) de les biblioteques gràfiques

cabal install gtk2hs-buildtools gtk
import Graphics.UI.Gtk

gestorEnClicarBotó :: IO ()
gestorEnClicarBotó = putStrLn "Has clicat el botó Hola món"

main :: IO ()
main = do
 initGUI
 finestra <- windowNew
 botó <- buttonNew

 set finestra [ containerBorderWidth := 10,
 containerChild := botó ]

 set botó [ buttonLabel := "Hola món" ]

 onClicked botó gestorEnClicarBotó
 onDestroy finestra mainQuit

 widgetShowAll finestra
 mainGUI

Si apareix el missatge "Undefined symbol ___gxx_personality_v0 on link" cal afegir la biblioteca stdc++ al relligat.[284]

ghc --make prova.hs -lstdc++
./prova

Hola món amb WxHaskell

modifica

WxHaskell[285] és una implementació de l'entorn gràfic Wx[286] que és multi-plataforma.

Cal haver instal·lat prèviament les biblioteques wx de desenvolupament per al sistema subjacent.

# cas de cabal v1.18+, senyal -jN per a muntatge multi-procés

# per a wx 2.8
cabal install "wx < 0.90"

# per a wx 2.9
cabal install "wx == 0.90.*"

# per a wx 3.x
cabal install "wx >= 0.91"
module Main where
import Graphics.UI.WX

gestorEnClicarBotó :: IO ()
gestorEnClicarBotó = putStrLn "Has clicat el botó Hola món"

hello :: IO ()
hello = do
 frame1 <- frame [text := "Exemple"]
 botó <- button frame1 [text := "Hola món", on command := gestorEnClicarBotó]
 set frame1 [layout := widget botó]

main :: IO ()
main = start hello

Lents de Van Laarhoven -- Consulta i manipulació de parts d'estructures complexes

modifica

Una lent (referència funcional) és un mecanisme componible que permet enfocar un component d'una estructura per ésser manipulat en tant que integrant de la mateixa. Vegeu refs.[287][288][289][290] Exemple amb el paquet lens de Eduard Kmett.[291]

Vegeu també Haskell#Lents basades en profunctors

{-# LANGUAGE TemplateHaskell, PackageImports #-}

import "lens" Control.Lens (makeLenses, Lens', (^.), (^..), over, set)
import Data.Function ((&)) -- (&): aplic. cap enrere
import Data.Monoid ((<>)) -- mappend assoc per la dreta

-- per convenció, per crear les lents, cal prefixar els camps amb el caràcter de subratllat

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

-- ''makeLenses'' genera, per meta-programació, lents corresponents als accessors d'un registre, prefixats amb '_', assignant-los el mateix nom sense el prefix '_'
$(makeLenses ''Arc) -- $() avalua en temps de compilació
$(makeLenses ''Situació) -- el prefix doble apòstrof indica "tipus" en llenguatge Template Haskell

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

-- lents compostes
lentGrausDeLatitud, lentMinutsDeLatitud, lentSegonsDeLatitud :: Lens' Situació Int
lentGrausDeLatitud = latitud. graus -- composició de lents: (Lens' Situació Arc). (Lens' Arc Int)
lentMinutsDeLatitud = latitud. minuts
lentSegonsDeLatitud = latitud. segons

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

-- decimal a sexagesimal
arcDeGrausDec :: Double -> Arc
arcDeGrausDec v = Arc partSencera mins secs
 where
 -- amb 'properFraction' s'obté la "fracció pròpia" (negativa per als valors negatius de lat/lon)
 (partSencera, partFracció) = properFraction v 
 segons = truncate (partFracció * 3600)
 (mins, secs) = segons `quotRem` 60 -- partint cap a zero amb 'quotRem'

main = do
 -- llegeix els graus de la latitud
 let grausLat = sitBcn ^. lentGrausDeLatitud

 -- llegeix diversos a llista concatenant
 let enLlistaLat = sitBcn ^.. (lentGrausDeLatitud <> lentMinutsDeLatitud <> lentSegonsDeLatitud)

 -- modifica els graus de la latitud
 let sitDosGrausMésAlNordDeBcn = sitBcn & over lentGrausDeLatitud (+2)

 -- estableix els graus de la latitud
 let sitBcnAmbGrausLat45 = sitBcn & set lentGrausDeLatitud 45

 putStrLn $ "situació Bcn: " ++ show sitBcn
 putStrLn $ "\ngraus lat. Bcn: " ++ show grausLat
 putStrLn $ "\nlatitud en llista: " ++ show enLlistaLat -- dona: latitud en llista: [41,23,57]
 putStrLn $ "\ndos graus més al Nord de Bcn: " ++ show sitDosGrausMésAlNordDeBcn
 putStrLn $ "\nfixa graus lat. a 45 s/. situació de Bcn: " ++ show sitBcnAmbGrausLat45

Nota de l'exemple: la funció properFraction correspon a la definició de Fracció pròpia.

Extreure informació d'un document XML

modifica

Vegeu-ho a Fletxa (programació funcional)#Exemple - Extreure informació d'un document XML

Operacions als tipus - Tipus dependents de valors

modifica

Vegeu ref.[292][293]

Fins ara hi ha hagut algunes iniciatives partint dels nombres de Peano obtenint sèries curtes de tipus mono-valor anomenats en anglès singleton types[294] (cat: tipus solters, volent dir monovalor) o algunes definicions inductives més sofisticades.[295]

  • Remarcable: El paquet dimensional-tf incorpora als tipus les dimensions de les unitats de la física.[296]
{-# LANGUAGE PackageImports #-}

import Prelude hiding ((*),(^),(/),(+),(-))
import "dimensional-tf" Numeric.Units.Dimensional.TF
import "dimensional-tf" Numeric.Units.Dimensional.TF.SIUnits
import "dimensional-tf" Numeric.Units.Dimensional.TF.Quantities

-- Mul, Div, Pow son operacions definides com a famílies de tipus 
-- (vegeu https://hackage.haskell.org/package/numtype-tf/docs/Numeric-NumType-TF.html)
-- el tipus Pos2 té un únic valor ''pos2'' (+2)
-- negatius amb prefix ''Neg'' als tipus o bé ''neg'' per als valors

import "numtype-tf" Numeric.NumType.TF (pos2, pos3, neg2, Pos2, Pos3, Neg2)

-- les expressions són del tipus :: Quantity Dimensions a
-- on Dimensions és un vector de potències de cada unitat implementat com a tipus producte.
-- Dimensions es pot expressar en funció de vectors unitaris (DLength, DTime…)

-- type DLength = Dim Pos1 Zero Zero Zero Zero Zero Zero
-- type DArea = Dim Pos2 Zero Zero Zero Zero Zero Zero

-- i també de l'aplicació de les famílies de tipus {Mul, Div, Pow}
-- corresp. a producte, divisió i exponenciació, definides a Numeric.Units.Dimensional.TF.

-- type DArea = Pow DLength Pos2
-- type Area = Quantity DArea

llargada = 4 *~ kilo meter :: Quantity DLength Float -- sinònim :: Length Float
area = 10 *~ meter ^ pos2 :: Quantity (Pow DLength Pos2) Float -- sinònim :: Area Float

-- les anotacions de tipus permeten que el compilador validi les operacions

volum :: Quantity (Pow DLength Pos3) Float -- dimLlargada elevada a la potència Pos3
volum = llargada * area

temps = 10 *~ minute :: Quantity DTime Float -- sinònim :: Time Float

tempsAlQuadrat = 10 *~ second ^ pos2 :: Quantity (Pow DTime Pos2) Float

-- :: (Acceleration Float) també es pot expressar com segueix:

acceleració :: Quantity (Div DLength (Pow DTime Pos2)) Float

-- acceleració :: Quantity (Mul DLength (Pow DTime Neg2)) Float -- alternativa

acceleració = llargada / tempsAlQuadrat

main = do
 putStrLn $ "volum: " ++ show volum
 putStrLn $ "acceleració: " ++ show acceleració

passa la comprovació de tipus i dona:

volum: 40000.0 m^3
acceleració: 400.0 m s^-2 -- 4000 m / 10 s^2

Naturals als tipus

modifica

La proposta TypeNats[297] ("Type level natural numbers"), parcialment implementada a GHC 7.6.1, permet l'ús de naturals als tipus facilitant les proposicions inductives als tipus.

Cal fer servir l'endollable (ang:plugin) de GHC[298] type-nat-solver[299] de Iavor Diatchki (opció de compilació -fplugin=TypeNatSolver inclosa al codi, i afegir el paquet type-nat-solver a les dependències de l'aplicació.

Ampliant l'exemple.[300] Compilat amb GHC 7.10.1.

# instal·lar el paquet 'cabalg' que instal·la directament del GitHub (Doc a https://hackage.haskell.org/package/cabalg)
cabal install cabalg # 
cabalg https://github.com/yav/type-nat-solver # descarrega ('git clone') a la carpeta type-nat-solver i instal·la

Mòdul de nombres de Peano de tipus singulars (d'un sol valor) (ang: singleton types).

{-# OPTIONS_GHC -fplugin=TypeNatSolver #-}

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeOperators #-}
-- {-# LANGUAGE ExplicitNamespaces #-} -- per importar el constructor de tipus (+), implicat per l'ext. TypeOperators
{-# LANGUAGE GADTs #-}

module UNat (
 UNat(..),
 UNat.toInteger,
 plus,
) where

import Data.Proxy (Proxy(..))
import GHC.TypeLits (Nat, natVal, KnownNat, type (+)) -- prefix 'type' per explicitar l'espai de noms del "constructor de tipus" (+)

-- el constructor de tipus (+) definit a GHC.TypeLits: type family (m :: Nat) + (n :: Nat) :: Nat

import Control.Category (>>>) -- f >>> g == g. f

data UNat :: Nat -> * where
 Z :: UNat 0
 S :: UNat n -> UNat (n + 1)

toProxy :: UNat n -> Proxy n
toProxy _ = Proxy

toInteger :: (KnownNat n) => UNat n -> Integer
toInteger = toProxy >>> natVal

plus :: UNat n -> UNat m -> UNat (n + m) -- utilitza el constructor de tipus (+)
plus Z x = x
plus (S x) y = S (plus x y)

Mòdul de contenidor amb naturals al tipus.

{-# OPTIONS_GHC -fplugin=TypeNatSolver #-}

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}

module Vec (Vec,
 empty, null, length, index,
 cons, append, uncons, split,
 UNat(..) 
) where

import Prelude hiding (null, length)
import GHC.TypeLits
import Data.Proxy
import Numeric.Natural
import UNat
import qualified Data.List as L
import Control.Category (>>>) -- f >>> g == g. f

data Vec :: Nat -> * -> * where
 Nil :: Vec 0 a
 Cons :: a -> Vec n a -> Vec (n + 1) a

instance Show a => Show (Vec n a) where
 show Nil = "[]"
 show (Cons x xs) = show x ++ " : " ++ show xs

empty = Nil
cons = Cons

null :: Vec n a -> Bool
null Nil = True
null _ = False

singleton :: a -> Vec 1 a
singleton x = Cons x Nil

append :: Vec m a -> Vec n a -> Vec (m + n) a
append Nil ys = ys
append (Cons x xs) ys = Cons x (append xs ys)

uncons :: Vec (n+1) a -> (a, Vec n a)
uncons (Cons x xs) = (x, xs)

index :: (m <= n) => UNat m -> Vec (n+1) a -> a
index Z (Cons x xs) = x
index (S r) (Cons _ (Cons x xs)) = index r (Cons x xs)

split :: UNat m -> Vec (m + n) a -> (Vec m a, Vec n a)
split Z xs = (Nil, xs)
split (S n) (Cons x xs) = case split n xs of
 (as,bs) -> (Cons x as, bs)

--

vecProxy :: Vec (n::Nat) a -> Proxy n
vecProxy _ = Proxy

length :: KnownNat n => Vec (n::Nat) a -> Integer
length = vecProxy >>> natVal

Precisió dels tipus bàsics

modifica
import qualified Numeric
import qualified Char

precisióSencers = "Int.MAX: " ++ (map Char.toUpper hexIntMax)
 where
 hexIntMax = Numeric.showHex límitSuperiorSencers "\n"
 límitSuperiorSencers = maxBound::Int

precisióComaFlotant :: (RealFloat a) => a  String
precisióComaFlotant nombre = mostraPrecisióSignificand ++ "; " ++ mostraRangExponent
 where
 mostraPrecisióSignificand = "significand: " ++ show (floatDigits nombre) ++ " dígits"
 mostraRangExponent = "exponent (min, max): " ++ show (floatRange nombre)

main = do
 putStrLn precisióSencers
 putStrLn $ "Double: " ++ precisióComaFlotant (1.0::Double)
 putStrLn $ "Float: " ++ precisióComaFlotant (1.0::Float)

En un processador x86-64 amb GHC:

Int.MAX: 7FFFFFFFFFFFFFFF

Double: significand: 53 dígits; exponent (min, max): (-1021,1024)
Float: significand: 24 dígits; exponent (min, max): (-125,128)

Els sencers ofereixen, a GHC, el rang complet corresp. a la paraula de la màquina (malgrat que l'especificació de Haskell només exigeix un mínim [-2^29.. 2^29-1]),[301] i no fan com altres compiladors (OCaml#tipus bàsics, SML#Precisió dels tipus bàsics) que sacrifiquen un bit per optimitzar el tractament intern conjunt de sencers i de punters.

En coma flotant, sembla que els valors dels exponents oferts per floatRange[302] donen un valor esbiaixat respecte a la intuïció. L'intèrpret GHCI dona correctament

Prelude> 2^^128::Float
Infinity -- sobreiximent, sembla que 128 no hauria d'estar inclòs al rang

Prelude> isDenormalized (2^^(-126)::Float) -- tipus de representació segons IEEE 754
False -- sembla que (-126) hauria d'estar inclòs al rang

Però el truc és que l'exponent no correspon a la notació científica, sinó al component de la representació interna IEEE 754 segons l'especificació per a coma flotant del "Language Independent Arithmetic standard".

  • La notació estandarditzada per a coma flotant de 1 és (en base 2): 0.1base 2 * 2^1

A ghci:

Prelude> exponent (2^0::Float)
1 -- !! en base 2: 1 == 0.1 (pel bit amagat de la mantissa) * 2^1
-- significand: signe * (bit amagat precedint la repr. mantissa IEEE 754 / 2^nombre_de_bits)
Prelude> significand (2^0::Float)
0.5 -- signe * 1 / (2^1) -- en ésser potència de dos, la repr. de la mantissa són tot zeros
Prelude> let r = -1 :: Float
# es manté l'equació següent, fins i tot per a valors de r negatius
Prelude> r == (significand r) * (2 ^^ (exponent r))
True

Relacionat: Hi ha tres operadors d'exponenciació (^), (^^), (**) segons que el domini de l'exponent sigui natural, sencer, o real en coma flotant.

Compilació de mòduls amb referències mútues (cicles)

modifica

Encara que sovint és possible evitar aquesta situació quan es dissenya l'aplicació, si es tracta d'una modif. i el disseny no convé alterar-lo, caldrà trencar les importacions circulars, seguint les instruccions,[303] creant un subconjunt del mòdul com a Mòdul.hs-boot, i referir-s'hi des dels altres mòduls afegint la pragma SOURCE a la clàusula import.

No caldrà fer-ne esment en el fitxer de projecte.cabal. El gestor de projectes cabal, busca totes les extensions de fitxer per cada nom de mòdul, trobarà l'extensió ".hs-boot" i hi aplicarà el preprocés adequat.

Referències

modifica
  1. mmc - Compilador Mercury de Melbourne(anglès)
  2. web del Compilador Haskell de York(anglès)
  3. web del Compilador Haskell d'Utrecht Arxivat 2014-07-22 a Wayback Machine.(anglès)
  4. web del C--(anglès)
  5. Diagrama de blocs del ghc que incorpora C--(anglès)
  6. El llenguatge Cmm(anglès)
  7. Estàndard actual del llenguatge Haskell
  8. La "Plataforma Haskell"(anglès)
  9. 9,0 9,1 9,2 API de la Platforma Haskell Arxivat 2016-12-20 a Wayback Machine. Seleccioneu-ne la versió(anglès)
  10. Anunci de la GHC 6.12.1(anglès)
  11. How to play with Stack
  12. How to script with Stack
  13. How to build with Stack
  14. Biblioteques i mòduls estàndard actuals
  15. Versions de biblioteques de les edicions de GHC(anglès)
  16. Libraries Version-history(anglès)
  17. Proposal: Remove Control.OldException(anglès)
  18. Biblioteques del GHC 7.4.2(anglès) Control.OldException encara hi és
    Les excepcions del H98 estan suportades
  19. Biblioteques del GHC 7.6.1(anglès) Control.OldException ja no hi és
    Les excepcions del Haskell98 ja no estan suportades
  20. The Glasgow Haskell Compiler(anglès)
  21. Documentació del compilador GHC
  22. Opcions del RunTimeSystem per controlar la mem. dinàmica i el recollidor de memòria brossa Arxivat 2010-04-02 a Wayback Machine.(anglès)
  23. RTS - Envelliment(anglès)
  24. RTS - Allotjament en Blocs
  25. RTS - El planificador(anglès)
  26. El recollidor de brossa(anglès)
  27. RTS - El recollidor de brossa.
  28. mòdul System.Mem(anglès)
  29. Simon Peyton Jones, et. al. - Stretching the storage manager(anglès)
  30. mòdul System.Mem.Weak(anglès)
  31. Control.Concurrent.MVar.mkWeakMVar(anglès)
  32. Ús de biblioteques d'enllaç dinàmic (anglès)
  33. Suport d'enllaç dinàmic a les diferents plataformes (anglès)
  34. Building and using Win32 DLLs(anglès)
  35. Parallel Haskell Digest(anglès)
  36. El paquet meta-par(anglès)
  37. 37,0 37,1 HaskellWiki - A Repa Tutorial(anglès)
  38. REgular PArallel arrays(anglès)
  39. Haskell - Paral·lelisme de dades
  40. 40,0 40,1 Nested Data Parallelism - presentació (diapos)
  41. Paquet dph-examples
  42. Guy E. Blelloch - Programming Parallel Algorithms - Nested Data Parallellism
  43. GHC.Prim - SIMD Vectors(anglès)
  44. SIMD instructions in GHC (anglès)
  45. Paquet simd
  46. Paquet primitive-simd
  47. 47,0 47,1 UNSW.edu.au - An Embedded Language for Accelerated Array Computations(anglès)
  48. Biblioteca Accelerate per al paral·lelisme a les GPU(anglès)
  49. biblioteca accelerate-cuda(anglès)
  50. El paquet accelerate-opencl(anglès)
  51. El paquet accelerate-repa(anglès)
  52. paquet OpenCLWrappers(anglès)
  53. OpenCL from Haskell Arxivat 2012-01-21 a Wayback Machine.(anglès)
  54. How to write hybrid CPU/GPU programs with Haskell(anglès)
  55. Hackage - paquet accelerate(anglès)
  56. Hackage - paquet accelerate-cuda(anglès)
  57. CUDA developer zone(anglès)
  58. accelerate-opencl de HIPERFIT(anglès)
  59. NVIDIA OpenCL(anglès)
  60. AMD APP SDK – A Complete Development Platform Arxivat 2016-02-09 a Wayback Machine.(anglès)
  61. accelerate amb rerefons OpenCL-Cilk(anglès) Subcarpeta icc-opencl
  62. accelerate-repa(anglès)
  63. accelerate-llvm(anglès)
  64. Template Haskell(anglès)
  65. Haskellwiki - QuasiQuotation(anglès)
  66. 66,0 66,1 Yesod - plantilles(anglès)
  67. Sintaxi dels quasiQuoters Arxivat 2013-04-14 at Archive.is(anglès)
  68. «QuasiQuotation». Arxivat de l'original el 2013-04-14. [Consulta: 5 octubre 2011].
  69. Tipus algebraics del TemplateHaskell (anglès)
  70. Haskellwiki - Quasi-Quotation(anglès)
  71. Constructor QuasiQuoter al paquet template-haskell(anglès)
  72. Template Haskell - stringE(anglès)
  73. Fusionant successius Map: Fent que Haskell sigui un 225% més ràpid (anglès)
  74. From lists to streams to nothing at all(anglès)
  75. Loop fusion (anglès)
  76. Stream fusion for Haskell Arrays (anglès)
  77. 77,0 77,1 paquet Stream-fusion(anglès)
  78. API del GHC(anglès)
  79. API Contenidors(anglès)
  80. 80,00 80,01 80,02 80,03 80,04 80,05 80,06 80,07 80,08 80,09 80,10 80,11 80,12 Data.Set
  81. 81,00 81,01 81,02 81,03 81,04 81,05 81,06 81,07 81,08 81,09 81,10 81,11 81,12 81,13 Data.Map
  82. 82,00 82,01 82,02 82,03 82,04 82,05 82,06 82,07 82,08 82,09 82,10 82,11 Data.IntSet
  83. 83,00 83,01 83,02 83,03 83,04 83,05 83,06 83,07 83,08 83,09 83,10 83,11 Data.IntMap
  84. paquet hashmap (anglès)
  85. Data.HashSet del paquet hashmap(anglès)
  86. Data.HashMap del paquet hashmap(anglès)
  87. 87,0 87,1 paquet Hashable(anglès)
  88. El paquet unordered-containers (HashMap i HashSet que no requereixen claus ordenables)
  89. 89,00 89,01 89,02 89,03 89,04 89,05 89,06 89,07 89,08 89,09 89,10 89,11 Data.HashSet del paquet unordered-containers(anglès)
  90. 90,00 90,01 90,02 90,03 90,04 90,05 90,06 90,07 90,08 90,09 90,10 Data.HashMap.Lazy del paquet unordered-containers(anglès)
  91. 91,00 91,01 91,02 91,03 91,04 91,05 91,06 91,07 91,08 91,09 91,10 91,11 91,12 Data.Sequence(anglès)
  92. 92,0 92,1 92,2 92,3 92,4 92,5 Data.Array.Repa(anglès)
  93. paquet Vector(anglès)
  94. Haskell numèric: Guia sobre Vector(anglès)
  95. Famílies de tipus (anglès)
  96. 96,0 96,1 [enllaç sense format] http://hackage.haskell.org/package/vector/docs/Data-Vector-Unboxed.html
  97. El paquet mono-traversable(anglès)
  98. 98,0 98,1 98,2 MonoPointed(anglès)
  99. 99,0 99,1 99,2 MonoFunctor(anglès)
  100. MonoZip(anglès)
  101. 101,0 101,1 101,2 101,3 101,4 MonoFoldable
  102. 102,0 102,1 MonoTraversable(anglès)
  103. 103,0 103,1 103,2 103,3 103,4 tipus NonNull t(anglès)
  104. 104,0 104,1 104,2 104,3 classe SemiSequence(anglès)
  105. 105,0 105,1 105,2 105,3 105,4 classe IsSequence(anglès)
  106. Textual(anglès)
  107. Utf8(anglès)
  108. 108,0 108,1 SetContainer(anglès)
  109. 109,0 109,1 109,2 109,3 109,4 IsSet(anglès)
  110. 110,0 110,1 110,2 110,3 110,4 IsMap(anglès)
  111. 111,0 111,1 111,2 111,3 111,4 111,5 111,6 111,7 111,8 paquet bytestring-trie(anglès)
  112. Data.DList(anglès)
  113. Demistifying DList's(anglès)
  114. 114,00 114,01 114,02 114,03 114,04 114,05 114,06 114,07 114,08 114,09 114,10 114,11 114,12 114,13 114,14 114,15 114,16 Data.Heap del paquet heap(anglès)
  115. paquet semigroups
  116. mòdul Data.Semigroup(anglès)
  117. 117,0 117,1 117,2 117,3 117,4 Data.List.NonEmpty(anglès)
  118. 118,0 118,1 118,2 118,3 118,4 118,5 118,6 118,7 118,8 Data.Maybe
  119. 119,0 119,1 119,2 119,3 119,4 119,5 119,6 Data.Either
  120. 120,00 120,01 120,02 120,03 120,04 120,05 120,06 120,07 120,08 120,09 120,10 120,11 120,12 120,13 Data.List
  121. 121,00 121,01 121,02 121,03 121,04 121,05 121,06 121,07 121,08 121,09 121,10 Data.ByteString(anglès)
  122. 122,00 122,01 122,02 122,03 122,04 122,05 122,06 122,07 122,08 122,09 122,10 122,11 122,12 Data.Text(anglès)
  123. 123,0 123,1 Data.Text - Tipus intern del Arxivat 2013-06-15 a Wayback Machine.(anglès)
  124. 124,0 124,1 124,2 124,3 124,4 Data.Monoid(anglès)
  125. 125,0 125,1 125,2 125,3 125,4 125,5 125,6 125,7 125,8 Data.Array
  126. Data.Array.Unboxed
  127. 127,00 127,01 127,02 127,03 127,04 127,05 127,06 127,07 127,08 127,09 Data.Vector(anglès)
  128. 128,0 128,1 128,2 128,3 128,4 Data.Vector.Mutable(anglès)
  129. 129,0 129,1 129,2 129,3 129,4 129,5 Data.Array.IArray
  130. 130,0 130,1 130,2 130,3 130,4 130,5 Data.Array.MArray
  131. 131,0 131,1 131,2 131,3 131,4 131,5 131,6 131,7 Data.Tree
  132. 132,0 132,1 132,2 paquet mono-traversable
  133. 133,0 133,1 133,2 133,3 Data.Foldable
  134. Data.Map.(!)(anglès)
  135. HaskellWiki Debugging(anglès)
  136. 136,0 136,1 136,2 Data.Functor
  137. Data.List.union
  138. Control.Monad.mfilter
  139. Data.Sequence.breakl(anglès)
  140. La classe Show(anglès)
  141. La classe Read(anglès)
  142. La classe Data(anglès)
  143. La classe Generic(anglès)
  144. La classe NFData(anglès)
  145. La classe Binary(anglès)
  146. paquet mono-traversable(anglès)
  147. Data.Traversable
  148. Functor-Applicative-Monad Proposal(anglès)
  149. HaskellWiki - MonadPlus(anglès)
  150. HaskellWiki - MonadPlus reform proposal(anglès)
  151. Data.Binary(anglès) Serialització
  152. Control.DeepSeq(anglès)
  153. 153,0 153,1 Data.Data(anglès)
  154. Data.Eq(anglès)
  155. Data.Ord(anglès)
  156. Text.Show(anglès)
  157. Text.Read(anglès)
  158. Data.Binary.Generic(anglès)
  159. Extensió NumDecimals Arxivat 2018-08-21 a Wayback Machine.(anglès)
  160. 160,0 160,1 160,2 Data.String
  161. 161,0 161,1 Extensió OverloadedStrings[Enllaç no actiu](anglès)
  162. 162,0 162,1 La classe IsList(anglès)
  163. 163,0 163,1 Extensió OverloadedLists[Enllaç no actiu](anglès)
  164. El paquet bytestring(anglès)
  165. HaskellWiki - Orphan instance(anglès)
  166. Opcions i variables predefinides per al CPP Arxivat 2014-02-02 a Wayback Machine.(anglès)
  167. Compilació condicional Arxivat 2014-02-02 a Wayback Machine.(anglès)
  168. GHC version numbering policy Arxivat 2016-05-09 a Wayback Machine.(anglès) Política de numeració de versions de GHC
  169. HaskellWiki - Compatibility modules
  170. Alex: A lexical analyser generator for Haskell(anglès)
  171. Happy: The Parser Generator for Haskell(anglès)
  172. hsc2hs(anglès)
  173. Writing Haskell interfaces to C code: hsc2hs(anglès)
  174. Haskell FFI tutorial example(anglès)
  175. GreenCard - generador de codi d'enllaç amb biblioteques en C(anglès)
  176. GreenCard: a foreign language interface for Haskell
  177. [enllaç sense format] https://github.com/haskell/c2hs c2hs
  178. Type Holes(anglès)
  179. Depuració(anglès)
  180. 180,0 180,1 HaskellWiki - Stack overflow
  181. HIW 2012. Simon Marlow: Why can't I get a stack trace?(anglès)
  182. Finding the needle: Stack Traces for GHC - Tristan Allwood, Simon Peyton-Jones and Susan Eisenbach(anglès)
  183. HaskellWiki - Stack trace(anglès)
  184. errorWithStackTrace de GHC.Stack (anglès)
  185. GHC - RTS Configurations (anglès)
  186. GHC - Opcions de perfilat Arxivat 2014-07-14 a Wayback Machine. (anglès)
  187. Stack Traces in GHCi(anglès)
  188. Catching Exceptions(anglès)
  189. El paquet exceptions aporta excepcions no limitades a la mònada IO(anglès)
  190. «GHC users guide - The GHCi Debugger». Arxivat de l'original el 2011-03-03. [Consulta: 10 maig 2014].
  191. No more exceptions: debugging Haskell code with GHCi(anglès)
  192. El fitxer ".ghci" Arxivat 2014-07-25 a Wayback Machine.(anglès)
  193. Notes de la versió 7.10.2(anglès)
  194. Special implicit parameters Arxivat 2015-09-05 a Wayback Machine.(anglès) Pila de crides com a paràmetre implícit
  195. la biblio. file-location(anglès)
  196. HaskellWiki - Debugging - Locating a failure in a library function(anglès)
  197. La biblioteca Safe(anglès)
  198. GHC flag reference Arxivat 2014-09-18 a Wayback Machine.(anglès)
  199. La biblio classy-prelude(anglès)
  200. La biblio mono-traversable(anglès)
  201. El paquet loch-th aporta un mecanisme per informar de les crides a les assercions fallides(anglès)
  202. force del paquet deepseq permet avaluar els components d'una dada
  203. Mòdul Debug.Trace(anglès)
  204. EventLog Tracing(anglès)
  205. HaskellWiki EventLog(anglès)
  206. 206,0 206,1 GHC users guide - RTS EventLog Arxivat 2010-04-02 a Wayback Machine.(anglès)
  207. Variables globals(anglès)
  208. La mònada Reader
  209. HaskellWiki - Top level mutable state
  210. Una solució al problema de les configuracions.
  211. paquet Seal-module
  212. Implicit configurations
  213. 213,0 213,1 213,2 213,3 System.IO.Unsafe (anglès)
  214. 214,0 214,1 IO inside: The dark side of the IO Monad(anglès)
  215. Hack - In computer science(anglès)
  216. The Reader monad(anglès)
  217. Retainer profiling
  218. La biblioteca deepseq(anglès)
  219. El paquet deepseq-th(anglès)
  220. HaskellWiki - Memory leak(anglès)
  221. .Edward Z. Yang - Space leak zoo(anglès)
  222. Profiling (ajustatge)(anglès)
  223. An Introduction to Cabal sandboxes Arxivat 2014-09-25 a Wayback Machine.(anglès)
  224. HaskellWiki - Stackage(anglès)
  225. El servidor Stackage
  226. LTS Haskell: Versionar l'ecosistema(anglès)
  227. stackage.org - LTS actual(anglès)
  228. What's the point of Stackage LTS?(anglès)
  229. yesodweb.com - Stable, Vetted Hackage(anglès)
  230. FPComplete Haskell Center Arxivat 2014-07-16 a Wayback Machine.(anglès)
  231. haskellstack.org(anglès)
  232. Sooner: producing a program more quickly Arxivat 2017-08-22 a Wayback Machine.(anglès)
  233. Faster: producing a program that runs quicker Arxivat 2017-08-22 a Wayback Machine.(anglès)
  234. Smaller: producing a program that is smaller Arxivat 2017-08-22 a Wayback Machine.(anglès)
  235. Thriftier: producing a program that gobbles less heap space Arxivat 2017-08-22 a Wayback Machine.(anglès)
  236. «Tipus unboxed (allotjament directe) - Restriccions». Arxivat de l'original el 2013-08-09. [Consulta: 12 desembre 2013].
  237. Tipus amb allotjament directe (ang:unboxed) i primitius Arxivat 2013-08-09 a Wayback Machine. (anglès)
  238. «Pragma UNPACK». Arxivat de l'original el 2014-01-25. [Consulta: 12 desembre 2013].
  239. Notes de la versió 7.8.1[Enllaç no actiu]
  240. «Tuples unboxed.». Arxivat de l'original el 2013-08-09. [Consulta: 12 desembre 2013].
  241. Pragmes del Haskell98(anglès)
  242. haskellWiki - Safe Haskell(anglès)
  243. GHC users guide - Safe Haskell Arxivat 2016-03-14 a Wayback Machine.(anglès)
  244. Compilador Frege
  245. Referència del llenguatge Frege(anglès)
  246. HaskellWiki - The JavaScript Problem(anglès)
  247. 247,0 247,1 Haste: Running Haskell in the Browser
  248. Try Haste(anglès)
  249. Haste report - Towards a Declarative Web(anglès)
  250. GitHub - valderman/haste-compiler(anglès)
  251. GitHub - ghcjs(anglès)
  252. GHCJS, Concurrent Haskell in the Browser(anglès)
  253. GHC - Procés de generació de codi(anglès)
  254. Paquet hlint que genera el programa del mateix nom(anglès)
  255. Cabal-dev - Sandboxed development builds for Haskell Arxivat 2011-01-28 a Wayback Machine.(anglès)
  256. cabal-meta (anglès)
  257. cabal-ghci (anglès)
  258. yackage (anglès)
  259. Threadscope
  260. «Ajustatge fi del paral·lelisme amb ThreadScope». Arxivat de l'original el 2009-12-29. [Consulta: 7 setembre 2009].
  261. Haskell Program Coverage Arxivat 2009-10-25 a Wayback Machine.(anglès) eina que mostra quin codi no s'ha executat mai i condicions sempre certes o sempre falses
  262. Hp2any - Obtenció del perfil d'ús de memòria(anglès)
  263. Eines de desenvolupament de programes(anglès)
  264. Liquid Types(anglès)
  265. HaskellWiki - Liquid Haskell(anglès)
  266. LiquidHaskell README(anglès)
  267. Refinement types Arxivat 2014-12-05 a Wayback Machine.(anglès)
  268. 268,0 268,1 Refinement types for Haskell(anglès)
  269. L'estàndard SMT(anglès)
  270. Real world Liquid(anglès)
  271. LiquidHaskell Blog
  272. MathSAT(anglès)
  273. Guia d'Opcions de l'intèrpret d'ordres en Haskell Arxivat 2013-09-29 a Wayback Machine.(anglès)
  274. API System.Console.GetOpt amb exemples(anglès)
  275. Neil Mitchell - Intèrpret d'ordres, exemples d'arguments(anglès)
  276. The Exception type (anglès)
  277. GHC extra - Seleccions estil SQL a les llistes Arxivat 2005-11-29 a Wayback Machine.(anglès)
  278. paquet vector-algorithms(anglès)
  279. El paquet unordered-containers de la plataforma Haskell, aporta HashMap i HashSet(anglès)
  280. Haskell wiki - Regular expressions(anglès)
  281. paquet aeson(anglès)
  282. Data.Tree(anglès)
  283. Gtk2hs(anglès)
  284. Per què serveix el símbol ___gxx_personality_v0(anglès)
  285. WxHaskell(anglès)
  286. wxWidgets(anglès)
  287. Van Laarhoven - CPS Functional References(anglès)
  288. Eduard Kmett - Lens wiki - Overview(anglès)
  289. The lens library(anglès)
  290. HaskellWiki - Lens(anglès)
  291. El paquet lens(anglès)
  292. Haskell wiki - Dependent type (anglès)
  293. Haskell wiki - Type arithmetic(anglès)
  294. Paquet type-level-natural-number(anglès)
  295. Paquet numtype-tf(anglès)
  296. dimensional-tf(anglès)
  297. Haskell Trac - TypeNats(anglès)
  298. GHC Plugins(anglès)
  299. paquet type-nat-solver al GitHub(anglès)
  300. TypeNats example(anglès)
  301. Estàndard Haskell2010 - Signed integer types(anglès)
  302. GHC floatRange(anglès)
  303. 303,0 303,1 Mòduls mútuament recursius(anglès)

Vegeu també

modifica

Enllaços externs

modifica