F#

Variabili e numeri        

Iniziamo col nostro immancabile saluto che ci farà da apripista per introdurre l'argomento di questo paragrafo:

  Esempio 1.1
1
2
let x = "Hello, World";
System.Console.WriteLine(x);

Molto semplice vero? Ma ricco di significato.... La riga 1 definisce una variabile x che viene riconosciuta di tipo string via inferenza. La riga 2 provvede a stamparla usando il metodo WriteLine (che stampa a va a capo) appartenente alla classe Console del namespace System definito nell'ambito del framework .Net. Alla riga 1 troviamo, come avrete senza'altro notato, quella keyword let che, di fatto, è la parola più importante di questo linguaggio dato che, tramite essa, vengono definite variabili (anche se in un linguaggio funzionale potrebbe avere meno senso parlare di variabili...), funzioni ecc... E' una keyword molto potente che troveremo in azione in varie forme nel prosieguo dello studio del linguaggio. Le variabili definite tramite let devono essere inizializzate tramite l'operatore =
Una prima interessante caratteristiche di F# è legata proprio alla parola let e alla "variabile" che dovrebbe introdurre. In realtà essa non fa altro che collegare un valore ad un nome e non viceversa. Come vedremo a meno di non utilizzare la parola
mutable, x non può avere altri valori mentre non può mai essere ricreata. Questo è un primo punto da tenere molto bene a mente perchè è un distinguo abbastanza profondo per chi viene da altri linguaggi. Tanto è che in F# come per altri linguaggi funzionali si parla spesso di valori piuttosto che variabili. Approfondiremo comunque il discorso.
Un altro modo, più "funzionale" per salutare il mondo è quello che trovate alla riga 1 dell'esempio seguente, nella riga successiva la versione "semplificata":

  Esempio 1.2
1
2
let x = printf("Hello, World")
printf("Hello, World")

Fin troppo facile vero?

L'operazione di assegnazione ad una variabile mutable avviene attraverso il simbolo <- (e non il classico = che si usa solo in fase di inizializzazione) quindi:

let mutable x = 5
x <- x + 2

Gli identificatori in ambito .Net non devono superare i 512 caratteri di lunghezza (qui, lerrore specifico del compilatore) e le regole convenzionali, aventi una valenza più ampia, sono piuttosto elaborate, ne trovate un buon sunto in questa pagina.
Una piccola nota la meritano i commenti che hanno 3 forme divers
e:

//
(* .. *)
///


il primo tipo permette commenti su linea singola.
il secondo permette commenti su più righe
il terzo invece è un commento XMLDoc che può essere arricchito con ulteriori tags XML ed è analizzabile con tools per la gestione di informazioni formattate tramite XML.

Le righe 1 delle'esempio 1.1 e la 1 e 2 del 1.2 ci presentano una prima tipologia di dati, ovvero le stringhe che vengono individuate tramite una coppia di doppi apici. Ne parleremo nel capitolo successivo. In questo paragrafo vedremo invece i tipi base numerici con i quali potremo operare.
In questo linguaggio utilizzeremo i seguenti tipi di numero:

tipo suffisso tipo in .Net range
byte uy System.Byte 0-255
sbyte y System.Sbyte -128 +127
int16 s System.Int16 -32768 +32767
uint16 us System.Uint16 0 65535
int (int32)   System.Int32 da -2^31 a 2^31 - 1
uint32 u System.Uint32 da 0 2^32 - 1
int64 L System.Int64 da -2^63 a 2^63 - 1
unit64 UL Sytem.Uniit64 da 0 a 2^64-1
float   System.Double Floating point doppia precisione
float32 f System.Single Floating point singola precisione
decimal M System.Decimal Flotaing point precisione 28 cifre

F# permette anche l'uso di numeri in formato esadecimle, ottale e binario, rispettivamente introdotti dai prefissi
0x, 0o e 0b.
Le operazioni di base non differiscono da quanto si conosce in altri linguaggi:

  Esempio 1.3
1
2
3
4
5
6
7
8
let x = 7;
let y = 3;
System.Console.WriteLine(x + y);
System.Console.WriteLine(x - y);
System.Console.WriteLine(x * y);
System.Console.WriteLine(x / y);
System.Console.WriteLine(x % y);
System.Console.WriteLine(7.0 ** 3.0);

NB: in questi esempi iniziali usiamo System.Console.WriteLine perchè più duttile, a mio avviso, rispetto alla nativa printf.
Gli operatori sono quelli noti, alla riga 6 abbiamo la divisione, che restituisce, ahimè, il risultato nella sua parte intera, il famoso quoto o quoziente insomma e non quello "vero" (siamo nel 2013 mentre scrivo e ancora 7 / 3 non può fare direttamente 2.333.... altri linguaggi anche in ambito .Net riescono a dare il risultato giusto... Cobra, ad esempio) mentre l'output risultante alla riga 7 è il resto della divisione. La riga 8 ci presenta l'elevamento a potenza che funziona solo sui float. Per eseguire la stessa operazione partendo da due interi si deve procedere come segue:

System.Console.WriteLine(pown 7 3);

utilizzando quella funzione
pown che effettuerà autonomamente una conversione interna.
Per risolvere invece il problema del risultato della divisione una soluzione può essere la seguente modifica alla riga 6 che riscriviamo così:

System.Console.WriteLine((float32(7)) / float32(3));

In pratica usiamo una funzione di conversione... contenti loro di costringere i programmatori a queste cose....da notare che non basta fare il cast, ovvero una conversione, di un solo numero, bisogna farlo per tutti e due altrimenti il compilatore si farà sentire. Comunque float32 non è l'unica routine di conversione disponibile in F#. La tabella seguente le riporta tutte, banalmente:

sbyte      - converte a sbyte
byte       - converte a byte
int16      - converte a int16
uint16     - converte a uint16
int32, int - converte a intero su 32 bit
uint32     - converte a unit32
int64      - converte a int64
uint64     - converte a uint64
float      - converte a float64 (default)
float32    - converte a float32
decimal    - converte a decimal

bisogna fare un po' di attenzione perchè certe conversioni posso lasciarsi indietro qualche cosa:

int(2.5) dà come risultato 2 e vi siete persi la parte decimale. Attenzione poi ad andare fuori range per il tipo nel quale volete convertire il vostro dato. Ad esempio:

System.Console.WriteLine(byte(314));

scrive come risultato 58 (= 314 - 256) e non 314. Pensate un attimo al perchè, se non siete tanto skillati sulla cosa, che di per se è banale, basta pensare alla rappresentazione binaria dei numeri indicati.... Tutte queste operazioni di conversione fanno uso, sotto sotto, di
System.Convert, libreria del framework .Net. In merito alla scrittura di prima il vero problema è che il sistema non vi avverte di nulla... qualcosa di simile è coinvolto nel famoso disastro del razzo Ariane. Fate i vostri conti (bene...).
Altro concetto da tenere in mente è che F# non effettua conversioni implicite. Se volete passare da un tipo all'altro per timore di overflow

Se il fatto di poter avere un overflow è un problema, potreste considerare l'adozione del tipo
bigint. Esso permette di rappresentare numeri di grandezza arbitraria e limitata solo dalle risorse hardware a disposizione. Ovviamente si tratta di un tipo "pesante" quindi prestazionalmente pagherete un prezzo per il suo uso, nonostante alcune ottimizzazioni previste specificamente. Per avere accesso ai bigint dovrete aprire il namespace Numerics (tramite l'istruzione open System.Numerics). Va peraltro ricordato che in ambito .Net la parola "bigint" è in realtà un alias per la struttura BigInteger, appartenente come detto a System.Numerics (che al suo interno contiene anche la struttura Complex per la gestione dei numeri complessi). I bigint sono caratterizzati dal suffisso I (maiuscolo). Vediamo un esempio:

  Esempio 1.4
1
2
3
4
5
open System.Numerics
let x : bigint = 70I
let y = 70
let z : bigint = BigInteger.Pow(x, y)
System.Console.WriteLine(z)

Esiste peraltro una notazione di dichiarazione alternativa forse di comprensione più immediata che può sostituire la riga 2:

let x = bigint 70

e anche la 4 poteva esser scritta:

let z = BigInteger.Pow(x, y)

Pow è uno dei tanti metodi a disposizione di BigInteger. Essi, insieme alle sue proprietà sono ampiamente esposti nella sezione dedicata sul sito di MSDN.

I numeri in virgola sono gestiti tramite i 3 tipi indicati nella tabella poco sopra: il tipo standard (
float ovvero sarebbe un float64) prevede una precisione su 15-16 cifre. Il tipo float32 ha una precisione di 7 cifre mentre con decimal siamo su 28-29 cifre di precisione. Mentre il tipo di default non ha suffissi di sorta float32 deve essere seguito da f o F mentre decimal da m o M. Personalmente preferisco la lettera maiuscola, che a mio avviso è più evidente all'occhio, ma è una questione di gusti. Il differente comportamente potete osservarlo attraverso questo semplice esempio:

  Esempio 1.5
1
2
3
4
5
6
7
8
9
10
let x = 17.3M
let y = 3.0M
let t = 17.3
let z = 3.0
let k = 17.3F
let w = 3.0F
System.Console.WriteLine(x / y)
System.Console.WriteLine(t / z)
System.Console.WriteLine(k / w)
let out = System.Console.ReadLine()

con il seguente output:

5,7666666666666666666666666667
5,76666666666667
5,766666

Sono evidenti i diversi gradi di precisione. I decimal sono evidentemente migliori per calcoli molto accurati ma sono anche più pesanti e lenti, come succede sempre in questi casi. Al contrario di float32 sono più leggeri ma meno accurati. Valutate sempre bene cosa vi conviene usare.