
Il linguaggio JavaScript è sempre più utilizzato anche lato server grazie alle numerose librerie di sviluppo disponibili. Ciò comporta un considerevole aumento della complessità del codice che deve essere sempre più aperto alle estensioni e chiuso verso le modifiche.
Nel seguito dell’articolo verrà mostrato come evitare la duplicazione del codice sfruttando le high-order function in JavaScript.
Problemi di duplicazione del codice
Innanzitutto illustriamo in modo chiaro un problema tipico: viene fornito un un array di oggetti JavaScript che rappresentano utenti di una web app e che possiedono gli attributi nome, cognome, e post. Post è una lista di stringhe contenente il titolo di ciascun post pubblicato.
let p = {
"name": "Mary",
"surname": "Smith",
"posts": ["image.png", "codemotionRome.png", "article.txt"]
}
Code language: JavaScript (javascript)
Si vogliono implementare diversi algoritmi di ordinamento dell’array come ad esempio selectionSort e bubbleSort. Si vuole anche implementare la possibilità di ordinare l’array per nome, per cognome oppure per numero di post pubblicati.
Per implementare un algoritmo di ordinamento è necessario confrontare l’attributo di interesse secondo il quale si vuole ordinare. Tuttavia, il confronto fra due nomi avviene in maniera differente rispetto al confronto fra numeri di post pubblicati:
let people = [p0, p1, p2, ...]
// Confronto persone per nome
people[0]["name"] > people[1]["name"]
// Confronto persone per post pubblicati
people[0]["posts"].length > people[1]["posts"].length
Code language: JavaScript (javascript)
Si potrebbe quindi pensare di sviluppare le seguenti funzioni, modificando solamente l’implementazione del confronto fra oggetti:
selectionSortByName(){...};
bubbleSortByName(){...};
selectionSortByPosts(){...};
bubbleSortByPosts(){...};
...
La soluzione sopra proposta risulta essere veramente pessima poiché comporta un’ampia duplicazione del codice che esegue gli algoritmi di selectionSort e bubbleSort causando i seguenti problemi:
- Diventa necessario definire molte funzioni con implementazione quasi identica
- Se si verifica un errore nell’algoritmo di una funzione, tutte le altre funzioni della stessa famiglia devono essere modificate manualmente per risolvere lo stesso problema
First class object e high-order function
In JavaScript le funzioni sono oggetti di prima classe (first-class object), ciò significa che il loro codice (signature e implementazione) può essere assegnato a una variabile.
function foo(){
console.log("Executing foo...");
}
console.log(foo) //Stampa il codice della funzione
Code language: JavaScript (javascript)
Un funzione si dice di ordine superiore (high-order function) quando accetta come argomento un’altra funzione, oppure quando ritorna una funzione come risultato.
foo = function(){
console.log("foo")
}
function execute(fn){
fn()
}
execute(foo)
Code language: JavaScript (javascript)
Nel codice sopra proposto la funzione execute() è una high-order function.
Comparatori e algoritmi di ordinamento
Si può quindi pensare di sfruttare la logica delle high-order function per evitare la duplicazione di codice nell’esempio precedentemente riportato o in casi analoghi. Ecco come procedere:
- Definire le high-order function ossia le funzioni che implementano l’algoritmo di ordinamento e che accettano come parametri l’array da ordinare e una funzione che sarà invocata per eseguire il confronto fra i diversi elementi dell’array.
- Definire diverse funzioni per comparare due oggetti dell’array, nel nostro caso due persone, secondo parametri differenti. Queste funzioni comparator saranno passate come parametro alle high-order function.
Un aspetto fondamentale da non tralasciare è che le funzioni comparator dovranno agire in modo differente, ma avere stessa signature e stessa logica dei valori di ritorno.
Ogni funzione comparator accetta due elementi dell’array e ritorna i seguenti valori:
- -1 se il primo elemento è precedente al secondo
- 0 se i due elementi sono intercambiabili
- +1 se il primo elemento è successivo al secondo
Vediamo un esempio:
let compareByName = function(personA, personB){
if(personA.name < personB.name){ return -1}
else if(personA.name > personB.name){ return +1 }
else { return 0 }
}
let p1 = {"name": "John", "Surname": "Doe", "posts": []}
let p2 = {"name": "Mary", "Surname": "Smith", "posts": []}
compareByName(p1, p2) //Returns -1 in fact John < Mary
compareByName(p2, p1) //Returns +1 in fact Mary > John
Code language: JavaScript (javascript)
La stessa logica può essere implementata per eseguire il confronto per numero di post pubblicati:
let compareByPosts = function(personA, personB){
if(personA.posts.length < personB.posts.length){ return -1}
else if(personA.posts.length > personB.posts.length){ return +1 }
else { return 0 }
}
Code language: JavaScript (javascript)
Ora è sufficiente definire la high-order function e passarle il metodo di confronto desiderato:
function selectionSort(list, comparator){
for(let i = 0; i < list.length - 1; i++){
for(let j = i+1; j < list.length; j++){
if(comparator(list[j], list[i]) < 0){
tmp = list[i];
list[i] = list[j]
list[j] = tmp
}
}
}
}
// Sorting by name
selectionSort(people, compareByName)
// Sorting by posts number
selectionSort(people, compareByPosts)
Code language: PHP (php)
Siamo quindi riusciti a sfruttare due uniche high-order function che definiscono l’algoritmo di ordinamento dell’array e adottano metodi differenti per confrontare i diversi elementi. Il codice è poco duplicato e facilmente estendibile, infatti, se si volesse aggiungere un nuovo criterio di ordinamento, per esempio il cognome, sarà sufficiente implementare la funzione compareBySurname() e passarla come comparator ad una delle due high-order function.