#howtodev - Funzioni in TypeScript

Scritto da il
typescript
funzioni
sviluppo

JavaScript #chaiproblemigrossi, quindi Microsoft ha ben pensato di strutturarci sopra un linguaggio che ne risolvesse i problemi principali, come la tipizzazione debole.

TypeScript becomes JavaScript via the delete key

~ typescriptlang.org

Questo è il secondo di una serie di articoli su TypeScript, parleremo di funzioni.

← Articolo precedente

Obiettivi

Lista degli obiettivi che a fine articolo il lettore consegue:

  • Scrivere una funzione
  • Distinguere tra vari tipi di argomenti
  • Passare parametri opzionali
  • Passare parametri REST
  • Assegnare una funzione ad una variabile
  • Scrivere funzioni anonime
  • Le funzioni come argomento di una funzione
  • Differenza tra arrow function e funzioni anonime

Prerequisiti

Lista dei prerequisiti e di conoscenze presenti in altri articoli che verranno date per scontato:

Funzioni

Creare una funzione con TypeScript è molto semplice:

function nomeFunzione(nomearg1:tipoarg1, nomearg2:tipoarg2):tiporitorno {
	
    // OPERAZIONI 
    
    return risultato;
}

Se il tipo di ritorno è void non si ha alcuna necessità di restituire un valore, anzi farlo è errato.

Normalmente le funzioni di TypeScript lavorano gli argomenti usando il loro valore e non il loro riferimento in memoria (passaggio per valore).

Ciò significa che funzioni del genere:

function cambioValore(variabile:number):void{
    variabile=13;
}

Cambieranno il valore della variabile solo localmente, al di fuori del metodo infatti, la variabile tornerà al suo valore originale.

var variabile:number= 10
cambioValore(variabile); 
console.log("valore variabile=",variabile);

output:

valore variabile= 10

Come passare un valore per riferimento quindi? Come in altri linguaggi ad alto livello, i valori degli oggetti coincidono con il loro riferimento in memoria. Ciò rende il passaggio del valore di un oggetto un passaggio per riferimento a tutti gli effetti.

Incapsuliamo la variabile di tipo primitivo in un oggetto quindi

function cambioValore(oggetto:any){
    oggetto.variabile=20
}

var oggetto:{ variabile:number}
oggetto={variabile:10}
cambioValore(oggetto)

console.log("valore variabile=",oggetto.variabile)

output:

valore variabile= 20

Altresì funziona se usiamo tuple o vettori

function cambioValore(tupla:[number]){
    tupla[0]=20
}

var tupla:[number] = [10]
cambioValore(tupla)

console.log("valore variabile=",tupla[0])

output:

valore variabile= 20

Gli arguments

In un metodo la parola chiave arguments è la lista di argomenti che viene passata in input. La si può interrogare come se fosse un json in cui ogni chiave è il numero posizionale del metodo:

function cambioValore(tupla:[number]){   
   console.log("Metodo cambioValore");
    console.log("lista argomenti=",arguments);  
    console.log("Numero argomenti=",arguments.length)
    console.log("primo argomento=",arguments[0])
    tupla[0]=20
}  
 
var tupla:[number] = [10]
cambioValore(tupla)

console.log("valore variabile=",tupla[0])

output:

lista argomenti= [Arguments] { '0': [ 20 ] }
Numero argomenti= 1
primo argomento= [ 20 ]
valore variabile= 20

Gli argomenti opzionali

Gli argomenti opzionali son particolari in quanto possono essere omessi quando si richiama la funzione, molto utile per evitare di creare overloading di metodi.

Un argomento è considerato opzionale se contiene il carattere ? dopo il nome:

function argomentiOpzionaliENon(a:number, b?:string){
        console.log("a=",a);
        console.log("b=",b);
        console.log("tutti gli argomenti=",arguments);
}
 
argomentiOpzionaliENon(1)

output:

Argomenti opzionali
a= 1
b= undefined
tutti gli argomenti= [Arguments] { '0': 1 }

È da notare come gli argomenti non passati risultino poi undefined, è quindi consigliato utilizzare strumenti come arguments, if o inizializzazioni ternarie per controllare il flusso delle operazioni

**ATTENZIONE:**

Tutti gli argomenti opzionali devono essere posizionati a fine intestazione, dopo quelli obbligatori!

Argomenti REST (o varargs)

Gli argomenti variabili (varargs in alcuni linguaggi, REST in altri) son un numero indefinito di argomenti che vengono trasformati poi in un vettore, vanno inseriti a fine metodo e condividono tutti lo stesso tipo.

Per inserire un argomento variabile bisogna farne precedere il nome da tre caratteri . di seguito, il tipo scelto deve essere poi un array:

function funzioneConArgomentiVariabili(parametro1:number,parametro2:string,...argomentovarargs:string[]):void {
    console.log("varargs list:",argomentovarargs)
}

Possiamo richiamare la funzione concatenando un numero a scelta di parametri di quel tipo, ad esempio:

funzioneConArgomentiVariabili(1,"ciao","varargs1","varargs2","varargs3","varargs4")

L’output sarà:

varargs list: [ 'varargs1', 'varargs2', 'varargs3', 'varargs4' ]

Possiamo anche richiamare la funzione senza alcun varargs:

funzioneConArgomentiVariabili(1,"ciao")

output:

varargs list: []

NOTA BENE:

la variabile stringa prima del varargs non viene presa come parte del parametro variabile.

Gli argomenti variabili vengono poi trattati all’interno del metodo come fossero un array, con tutto quello che comporta, ad esempio è possibile richiamare la proprietà length oppure prelevare un particolare parametro:

function funzioneConArgomentiVariabili(...argomentovarargs:string[]):void {
    console.log("lunghezza", argomentovarargs.length)
    console.log("primo elemento", argomentovarargs[0])
}
funzioneConArgomentiVariabili("varargs1","varargs2","varargs3","varargs4")

output:

lunghezza 4
primo elemento varargs1

Per inserire un vettore di stringhe come parametro al posto del varargs si può usare usare questa sintassi:

let arrayVarargs: string[] = ["varargs1","varargs2","varargs3","varargs4"]

funzioneConArgomentiVariabili(...arrayVarargs)

Le variabili funzione e le funzioni anonime

In TypeScript le funzioni sono a tutti gli effetti delle variabili, questo consente di crearle e memorizzarle all’interno di una di esse.

Facciamo un esempio con una funzione che stampa true se la prima stringa ha un numero di caratteri superiore oppure è alfa-numericamente maggiore della seconda:

let funzioneConfrontoStringhe=(stringa1:string, stringa2:string) => {return stringa1.length==stringa2.length? stringa1>stringa2 : stringa1.length>stringa2.length;}

Se proviamo a stamparla:

console.log(funzioneConfrontoStringhe)

ne uscirà fuori questo:

[Function: funzioneConfrontoStringhe]

In particolare, le funzioni definite come:

(var1,var2):TipoRit => { corpo del metodo }

son dette “arrow function

Una variabile può anche essere definita con la parolina function ma senza nome:

let funzioneConfrontoNumeri : Function = function (int:number,int2:number): boolean{
	return Math.abs(int)>Math.abs(int2)
}

Possiamo poi chiamare la funzione all’interno della variabile come se la variabile stessa fosse il nome della funzione:

let stringa4caratteri:string="ciao"
let stringa9caratteri:string="caratteri"
console.log("stringa4caratteri \u00e8 pi\u00fa "+(funzioneConfrontoStringhe(stringa9caratteri,stringa4caratteri)?"piccola":"grande")+" di stringa9caratteri");

output:

stringa4caratteri è piú piccola di stringa9caratteri

Questo approccio consente di poter creare metodi che a loro volta prelevano dei metodi come parametri.

Supponiamo ad esempio una funzione che, dato un vettore di stringhe e una funzione che mette a confronto due stringhe, cambia il vettore rendendolo ordinato (secondo il metodo che gli passiamo) e lo stampa alla fine :

function ordinaStringheCustom (vettore:string[], funzioneOrdinamento: Function){
	
	let min:string =undefined;
	let imin:number=0; 
	for ( let index =0 ; index<vettore.length; index=index+1){
		for ( let index2= index; index2 <vettore.length; index2=index2+1){
			let stringa=vettore[index2]
			if( min && funzioneOrdinamento(stringa,min)) continue;
			
			min=stringa
			imin=index2	
		}	
		if(index===imin) {min=undefined; continue;}
		vettore[imin]=vettore[index]
		vettore[index]=min
		min=undefined; 
	}
	console.log(vettore)
}

Chiamiamolo:

ordinaStringheCustom(["abc","ad","bdd","cd"], funzioneConfrontoStringhe);

Quindi osserviamo il risultato:

[ 'ad', 'cd', 'abc', 'bdd' ]

Vincolare il tipo di funzione in ingresso

Si può vincolare il tipo di funzione in ingresso. Per chiarirci, allo stato attuale è possibile chiamare ordinaStringheCustom con una funzione in ingresso che non ordina le stringhe, ma incrementa un numero e lo restituisce:

ordinaStringheCustom(["abc","ad","bdd","cd"], (a:number):number=>{return 1+a;});

Il compilatore non ci ferma, il programma viene comunque eseguito ed il risultato è imprevedibile ( in questo specifico caso semplicemente, non viene ordinato il vettore )

Possiamo prevenire questo comportamento chiedendo a ordinaStringheCustom di prelevare solo funzioni che prelevano due stringhe in ingresso e restituiscono un boolean, ovvero cambiando l’ultimo parametro in ingresso con funzioneOrdinamento: (var1:string, var2:string)=> boolean :

function ordinaStringheCustom (vettore:string[], funzioneOrdinamento: (var1:string, var2:string)=> boolean){
	
	let min:string =undefined;
	let imin:number=0; 
	for ( let index =0 ; index<vettore.length; index=index+1){
		for ( let index2= index; index2 <vettore.length; index2=index2+1){
			let stringa=vettore[index2]
			if( min && funzioneOrdinamento(stringa,min)) continue;
			
			min=stringa
			imin=index2
		}	
		if(index===imin) {min=undefined; continue;}
		vettore[imin]=vettore[index]
		vettore[index]=min
		min=undefined; 
	}
	console.log(vettore)
}

Se adesso scriviamo:

ordinaStringheCustom(["abc","ad","bdd","cd"], (a:number):number=>{return 1+a;});

in output avremo:

Argument of type '() => string' is not assignable to parameter of type '(var1: string, var2: string) => boolean'.

Questo previene anche da un passaggio errato dei parametri nella funzione anonima. Supponiamo la funzione:

function funzioneOrdinamentoError (var1:string,var2:number,funzioneOrdinamento: Function){
	
	console.log ((funzioneOrdinamento(var1,var2) ? `${var1}` : `${var2}`) + " \u00e8 pi\u00f9 grande");
}

E chiamiamola con:

funzioneOrdinamentoError("ciao",1,funzioneConfrontoStringhe)

Cosa può accadere in questo caso? Una conversione automatica? Un errore di runtime?
Generalmente è una conversione automatica, ma noi non vogliamo proprio porci il problema, poiché deve essere impedito a livello di compilazione.

Se noi specifichiamo che tipo di funzione ci aspettiamo come sopra il risultato cambia. Quindi scrivendo:

function funzioneOrdinamentoCorretta (var1:string,var2:number,funzioneOrdinamento: (var1:string, var2:string)=> boolean){
	console.log ((funzioneOrdinamento(var1,var2) ? `${var1}` : `${var2}`) + " \u00e8 pi\u00f9 grande");
}

Otterremo un errore in output di compilazione:

Argument of type 'number' is not assignable to parameter of type 'string'.

Best practice: usare type e funzioni anonime insieme

Potrebbe essere un vantaggio quello di inizializzare con type un nuovo tipo che corrisponde ad un specifico tipo di funzione. Esempio:

type fConfrontoStringhe = (var1:string, var2:string)=> boolean; 

Quindi definire una funzione anonima ed assegnarla ad una variabile di questo tipo:

let funConfrontoStringhe:fConfrontoStringhe=(stringa1:string, stringa2:string) => {return stringa1.length==stringa2.length? stringa1>stringa2 : stringa1.length>stringa2.length;}

Infine, vincolarla in un metodo:

function fOrdinamentoCorretta (var1:string,var2:string,funzioneOrdinamento: fConfrontoStringhe){
	console.log ((funzioneOrdinamento(var1,var2) ? `${var1}` : `${var2}`) + " \u00e8 pi\u00f9 grande");
}

Differenza tra arrow function e non

Come abbiamo già spiegato la funzione anonima può essere creata in due modi, con questa forma, detta Arrow:

(arg1,arg2,...):tipoRitorno => {}

Ed in questa forma:

function (arg1,arg2...) : tipoRitorno {}

La differenza è che il primo metodo, seppur più elegante sintatticamente, ha delle limitazioni, non viene infatti creata una funzione come oggetto ma più come oggetto primitivo.

history_edu Revisioni