Secondo esempio spiegato.

Per ragionare meglio sul secondo esempio, lo riscriviamo scomponendo l’espressione che determina il valore di tipo Picture.

import Graphics.Gloss
f y = Rotate (2*y) . Translate 0 y $ Circle y
ps = [f y | y <- [5,10 .. 100]]
main = displayInWindow "secondo esempio" (500,600) (0,0) white $ Pictures ps

Per caricare questa definizione in ghci, si deve salvare il codice in un file “esempio2.hs”, aprire ghci e usare il comando :load

Prelude> :load esempio2
[1 of 1] Compiling Main             ( esempio2.hs, interpreted )
Ok, modules loaded: Main.
*Main> 

Ora possiamo interrogare l’interprete per conoscere i tipi dei nomi caricati

*Main> :t f
f :: Float -> Picture
*Main> :t ps
ps :: [Picture]

Il tipo di f è lo stesso di Circle. Infatti il significato dell’espressione è: un cerchio di raggio y , spostato di y pixel in alto e ruotato intorno all’origine di 2*y gradi. Quindi una funzione che da qualsiasi y di tipo Float crea una Picture.
Andiamo piano. Prima di tutto eliminiamo il . e l’$ che sono combinatori e ci complicano la vita all’inizio. Utilizziamo le parentesi per isolare le sottoespressioni.

f y = Rotate (2*y) (Translate 0 y (Circle y))

L’espressione è equivalente ma meno idiomatica. Osseviamo i tipi dei 3 costruttori in gioco.

*Main> :t Circle
Circle :: Float -> Picture
*Main> :t Translate
Translate :: Float -> Float -> Picture -> Picture
*Main> :t Rotate
Rotate :: Float -> Picture -> Picture
*Main> 

C’è qualcosa che distingue nettamente Circle dagli altri. Infatti Circle è l’unico che pare costruisca una Picture, gli altri la trasformano. Ma in haskell le cose non si trasformano, le cose si creano e basta. Allora si capiscono meglio i tipi di Translate e Rotate. Translate vuole i due spiazzamenti orizzontale e verticale e una Picture, così facendo esso è un’altra Picture. Stesso vale per Rotate che vuole un angolo ed una Picture, risultando in una nuova Picture.

Questa corrispondenza tra la descrizione e il codice è la forza dei datatype ricorsivi: essi sono un buon modello per un linguaggio descrittivo.
I datatype ricorsivi contengono valori del tipo che stanno defininendo nella definizione dei propri costruttori.
Dobbiamo capire che essi sono semplicemente un valore, come 42 o “primo esempio” o 12.34, ma descrivono un valore che ha una struttura complessa.
Scriviamo alcuni valori di tipo Picture.

*Main> :t Translate 10 10 (Circle 10)
Translate 10 10 (Circle 10) :: Picture
*Main> :t Rotate 40 (Circle 20)
Rotate 40 (Circle 20) :: Picture
*Main> :t Translate 10 10 (Rotate 40 (Circle 20))
Translate 10 10 (Rotate 40 (Circle 20)) :: Picture
*Main> 

Il secondo ostacolo è il valore ps.

ps :: [Picture]

Il suo tipo si legge “Lista di valori di tipo Picture”. Purtroppo la lista è il primo tipo parametrico che incontriamo, ed ha una sintassi un po speciale che confonde molto le idee. Una lista è un tipo ricorsivo esattamente come Picture, ma è polimorfo, ovvero è di tipo diverso a seconda del tipo degli elementi che contiene.
Questa cosa riflette il suo significato, una lista di numeri è diversa da una lista di lettere, diversa da una lista di Picture e così via. Siccome la lista è omni presente nel codice haskell, una sintassi speciale è stata introdotta, in particolare il suo tipo si scrive [] e il suo parametro, il tipo degli elementi che contiene si scrive all’interno delle quadre. Anche il costruttore di valori lista si scrive [] e l’elenco dei valori contenuti si mette dentro separato da virgole.

*Main> :t ['a','b','c','d']
['a','b','c','d'] :: [Char]

Questa era una lista di caratteri. Il suo tipo [Char] si può scrivere [] Char. Il valore è stato costruito con parentesi quadre e elementi separati da virgole.
Nell’esempio ho usato una sintassi speciale per le liste (e non solo) che si chiama list comprehension e un operatore anch’esso speciale che genera sequenze dati gli estremi.
Vediamo prima l’operatore .

Prelude> :t ['a' .. 'z']
['a' .. 'z'] :: [Char]
Prelude> [1 .. 10]
[1,2,3,4,5,6,7,8,9,10]
Prelude> [1,3 .. 10]
[1,3,5,7,9]
Prelude> [10,8 .. -10]
[10,8,6,4,2,0,-2,-4,-6,-8,-10]
Prelude> 

E ora la list comprehension.

Prelude> [x * 2 | x <- [1..10]]
[2,4,6,8,10,12,14,16,18,20]

L’operazione costruisce una lista moltiplicando per 2 tutti gli elementi di [1 .. 10]. Prima della barra verticale scriviamo l’espressione che descrive un elemento della lista, dopo la barra descriviamo l’assegnazione degli elementi ai nomi che appaiono nell’espressione.
Nell’esempio sopra x * 2 impone che un elemento è il doppio del valore del nome x. Mentre x <- [1 .. 10] impone che il nome x assuma i valori da 1 a 10 nell’espressione dell’elemento.


Prelude> [(x,y) | x <- ['a' .. 'c'], y <- [1 .. 3]]
[('a',1),('a',2),('a',3),('b',1),('b',2),('b',3),('c',1),('c',2),('c',3)]

Qui creiamo delle coppie dette tuple. Da notare che gli assegnamenti alla ‘x’ e alla ‘y’ non avvengono in parallelo , ma ogni elemento della ‘x’ viene accoppiato con ogni elemento ‘y’.

Tornando a ps, ps è una lista di elementi creati applicando la funzione ‘f’ ad ogni ‘y’ dove ‘y’ varia tra 5 e 100 con passo 5. Ma la funzione ‘f’ genera una Picture da un Float, quindi i valori assegnati a ‘y’ sono Float e ps è una lista di Picture.

Possiamo vedere i primi valori di ps che sono i cerchi che compongono la nostra figura.

*Main> take 3 ps
[Rotate 10.0 (Translate 0.0 5.0 (Circle 5.0)),Rotate 20.0 (Translate 0.0 10.0 (Circle 10.0)),Rotate 30.0 (Translate 0.0 15.0 (Circle 15.0))]

Per finire esaminiamo il costruttore Pictures, infatti l’ultimo argomento di displayInWindow deve essere di tipo Picture, mentre ps è di tipo [Picture].

*Main> :t Pictures
Pictures :: [Picture] -> Picture
*Main> :t ps
ps :: [Picture]
*Main> :t Pictures ps
Pictures ps :: Picture

Esattamente il necessario per accontentare displayInWindow.

Primi passi spiegati

Nell’articolo precedente abbiamo introdotto la libreria gloss per accompagnare il percorso sulla programmazione in haskell con qualcosa di concreto.
Ora, esaminando i tre stralci di codice , cerchiamo di capire le basi.
Tutti i programmi di un certo livello si appoggiano ad altri di livello inferiore. Tutti i file contenenti codice si dicono moduli. Nelle prime righe di ogni modulo sono inserite le dipendenze dagli altri.
La parola speciale è import seguita dal nome del modulo che contiene le definizioni che intendiamo utilizzare.
Nei nostri esempi abbiamo importato il modulo Graphics.Gloss. Questo modulo esporta varie definizioni che ci servono a disegnare.
Negli esempi abbiamo una sola definizione a livello di file, quella del nome main.
Ad esso viene associata la funzione displayInWindow con i suoi parametri.
In haskell tutti i nomi che iniziano con lettera minuscola hanno un tipo associato, che determina la correttezza nell’utilizzo.
Dalla documentazione sulla funzione displayInWindow, essa riceve 5 argomenti: il nome della finestra, la grandezza della finestra, le coordinate dell’angolo alto sinistro, il colore di fondo e infine un valore di tipo Picture che definisce il disegno.
Nel primo esempio il valore di tipo Picture è Circle 100. Circle è una funzione costruttore di valori.
Per costruire un valore di tipo Picture possiamo usare uno qualsiasi dei costruttori del suo datatype, quindi Blank, Circle, Polygon e gli altri, ognuno corredato dai suoi argomenti di tipo corretto.
Per esaminare meglio usiamo ghci, l’interprete di ghc.
L’interprete non è potente come il compilatore, ma è molto flessibile e istruttivo.
Si lancia da riga di comando.

paolino@paolino-desktop:~$ ghci
GHCi, version 7.2.1: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Loading package ffi-1.0 ... linking ... done.
Prelude> import Graphics.Gloss
Prelude Graphics.Gloss> :type Circle
Circle :: Float -> Picture
Prelude Graphics.Gloss> :info Circle
data Picture = ... | Circle Float | ...
  	-- Defined in Graphics.Gloss.Data.Picture

Qui sopra ho riportato una sessione ghci dove ho importato Gloss, ho chiesto il tipo e le informazioni riguardanti Circle.
Come possiamo notare il tipo di Circle è Float -> Picture, che si legge “dato un valore di tipo Float, produce un valore di tipo Picture”. Dalla documentazione apprendiamo che il valore di tipo Float corrisponde al raggio del cerchio. Il simbolo :: si legge “è di tipo”.
Mentre al simbolo = corrisponde una definizione, il simbolo :: corrisponde ad una firma.

Prelude Graphics.Gloss> :t displayInWindow 
displayInWindow
  :: String -> (Int, Int) -> (Int, Int) -> Color -> Picture -> IO ()

Continuando la nostra ispezione notiamo i tipi degli argomenti di displayInWindow che corrispondono al significato dato prima. Controlliamo la coerenza del primo esempio.

Prelude Graphics.Gloss> :t "primo esempio"
"primo esempio" :: [Char]
Prelude Graphics.Gloss> :i String
type String = [Char] 	-- Defined in GHC.Base
Prelude Graphics.Gloss> :t (100,100)
(100,100) :: (Num t, Num t1) => (t, t1)
Prelude Graphics.Gloss> :t (0,0)
(0,0) :: (Num t, Num t1) => (t, t1)
Prelude Graphics.Gloss> :t white
white :: Color
Prelude Graphics.Gloss> :t Circle 100
Circle 100 :: Picture
Prelude Graphics.Gloss> 

Non è andata benissimo, ma su qualcosa ci siamo da subito, white è di tipo Color , e Circle 100 è di tipo Picture. Gli ultimi due argomenti di displayInWindows sono corretti.

Per il primo ho dovuto chiedere informazioni rispetto a String, e , per fortuna il mistero è risolto String è definito come [Char]. Qui si introduce il concetto di sinonimo di tipo. La parola magica è type.

type String = [Char]

Quindi String e [Char] sono lo stesso tipo.
Se continuiamo l’indagine le cose si complicano e si introduce la omni presente struttura dei programmi funzionali, la lista. Infatti [Char] si legge “lista di caratteri”, ma rimandiamo la cosa, per ora, visto che non ci mettiamo a smontare il valore “primo esempio”.

Per gli argomenti dimensione e piazzamento della finestra, i risultati dell’interrogazione sono imprevisti.

Prelude Graphics.Gloss> :t (300,300)
(300,300) :: (Num t, Num t1) => (t, t1)

Ci aspettavamo

(300,300) :: (Int, Int)

, invece l’interprete ci ha restituito

(t,t1)

lasciandoci capire che il tipo di 300 non è fissato. In compenso ha aggiunto una parte nuova nella risposta

(Num t, Num t1) => 

che si legge “se t è istanza della classe Num e t1 è istanza della classe Num, allora …”.
Senza entrare nelle classi di tipi, cerchiamo di capire il problema.
L’argomento di Circle nel primo esempio è 100, ma Circle è di tipo Float -> Picture, quindi 100 è di tipo Float quando lo mettiamo dopo Circle, invece se lo mettiamo come argomento di displayInWindow diventa di tipo Int. Qualcosa non va.

Prelude Graphics.Gloss> :t 100
100 :: Num a => a

Si può leggere 100 è di un tipo qualsiasi a patto che il tipo sia un numero. Ora suona meglio.
La domanda sorge spontanea, quanti tipi che sono numeri esistono ? Qualsiasi tipo che implementi un certo insieme di funzionalità e di comportamenti è un numero.
Non a caso abbiamo i numeri interi, i razionali e i reali anche nella nostra matematica.
La classe Num contiene le funzionalità da implementare per i tipi che ne vogliono essere istanze.
Tanto per curiosare, interroghiamo l’interprete.

Prelude Graphics.Gloss> :i Num
class (Eq a, Show a) => Num a where
  (+) :: a -> a -> a
  (*) :: a -> a -> a
  (-) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
  	-- Defined in GHC.Num
instance Num Point -- Defined in Graphics.Gloss.Data.Point
instance Num Integer -- Defined in GHC.Num
instance Num Int -- Defined in GHC.Num
instance Num Float -- Defined in GHC.Float
instance Num Double -- Defined in GHC.Float

Alcune cose erano prevedibili, un numero deve potersi sommare ad un altro, moltiplicare e sottrarre , negare …
Altre sono più tecniche e ci interessano meno.
La lista delle istanze che segue la classe, invece, contiene un particolare interessante.

instance Num Point -- Defined in Graphics.Gloss.Data.Point

La libreria gloss, nel suo modulo Graphics.Gloss.Data.Point ha creato un istanza di Num nuova: l’istanza di Point.
Ora siamo più curiosi.

Prelude Graphics.Gloss> :i Point
type Point = (Float, Float)
  	-- Defined in Graphics.Gloss.Data.Point

Un altro sinonimo di tipo, una coppia di tipi Float. Non ci resta che testarne l’appartenenza alla classe Num

Prelude Graphics.Gloss> (4,5) + (7,1) :: Point
(11.0,6.0)

Ottimo, a parte che ho dovuto annotare l’espressione con :: Point , ma questi sono prezzi da pagare quando si gioca con certe istanze di cui un giorno forse parleremo.

Primi passi con gloss (haskell tutorial in italiano)

Con questo articolo e molti dei prossimi intendo fornire del materiale introduttivo per aspiranti programmatori in haskell.

Per lavorare con gloss dobbiamo installare i bindings a opengl, che sono compresi nella haskell platform, ma devono essere scaricati in caso diverso.

Nel secondo caso, ci procuriamo alcune librerie necessarie

sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev freeglut3-dev

e installiamo quindi gloss che trascinerà tutte le dipendenze necessarie

cabal install gloss

Purtroppo l’esperienza con ghci non è delle migliori e quindi ci si deve accontentare del lavoro sui files.

Per un utilizzo di base l’interfaccia in Graphics.Gloss è sufficiente. La funzione displayInWindows apre una finestra e disegna quanto richiesto nel suo quarto argomento.

Quindi nel nostro primo file mettiamo

import Graphics.Gloss
main = displayInWindow "primo esempio" (300,300) (0,0) 
                white (Circle 100)

esempio 1
Salvato il file in esempio.hs lo eseguiamo con

runhaskell -lglut esempio.hs

o meglio

ghc --make esempio.hs && ./esempio

Dalla documentazione di gloss (docs) esaminiamo la definizione del datatype Picture


data Picture
	= Blank
	| Polygon 	Path
	| Line		Path
	| Circle	Float
	| ThickCircle	Float		Float
	| Text		String
	| Bitmap	Int	Int 	ByteString
	| Color		Color  		Picture
	| Translate	Float Float	Picture
	| Rotate	Float		Picture
	| Scale		Float	Float	Picture
	| Pictures	[Picture]

Con la solita naturalezza haskell definisce Picture ricorsivamente per permettere le trasformazioni affini e per aggiungere il colore.
L’ultimo costruttore ci permette di vedere un insieme di Pictures come una sola.
Rimanendo sulla figura di base del cerchio ci possiamo sbizzarrire attraverso le trasformazioni affini.
Ecco un esempio di Picture costruita attraverso una list comprehension.

import Graphics.Gloss
main = displayInWindow "secondo esempio" (600,500) (0,0) white $
         Pictures 
         [Rotate (2*y) . Translate 0 y $ Circle y | y <- [5,10..100]]

esempio 2
E una variazione dove moduliamo anche il colore

import Graphics.Gloss

main = displayInWindow "terzo esempio" (600,500) (0,0) white .
   Pictures . map f $ [100,99.9 .. 5]
   where f y    = Rotate (10*y) 
                . Translate 0 y 
                . Color (makeColor 0 (y/100) (1 - y/100) (y/100)) 
                $ ThickCircle y y

esempio 3