Javascript Coercion [3] Implicit Coercion

J
Questo articolo fa parte di una serie di articoli:

  1. Javascript Coercion [1] Abstract Value Operations
  2. Javascript Coercion [2] Explicit Coercion
  3. Javascript Coercion [3] Implicit Coercion
  4. Javascript Coercion [4] Loose Equality

 

IMPLICIT COERCION

Eccoci finalmente arrivati all’argomento più scottante dell’intera serie: la coercizione implicita. Questo tipo di coercizione, che alla fine dei conti è quella meno compresa e più odiata, si riferisce a tutte le conversioni di tipo che sono nascoste, non ovvie al programmatore. Spesso è stata presentata come il male assoluto, un pericolo da evitare sempre e comunque.

Ma le cose stanno effettivamente così? Innanzitutto, è realmente possibile evitarla del tutto? Ma, cosa ben più importante, non ha proprio nulla di buono da offrirci?

La risposta a tutte e tre le domande è la medesima: !true.
La coercizione implicita provvede un meccanismo che permette di eseguire alcune delle più importanti operazioni senza dover ricorrere a un codice inutilmente verboso. Essa segue delle regole ben precise che se esaminate, anziché semplicemente ignorate per poi lamentarsi quando non funziona un tubo, ci permettono di definire una zona sicura dove muoversi in totale tranquillità.

Uscire da questa zona equivarrebbe a tuffarsi in una piscina piena di piranha.

 

CONCATENAZIONE VS ADDIZIONE

Iniziamo col botto! Da peggiore argomento non potevamo cominciare, perciò se riusciamo a districarci quà possiamo sopravvivere ovunque.
Ah no, c’è la loose equality. Smorfia malvagia.

Allora, siamo certamente sicuri che:

var a = 42;
var b = 0;

a + b; // 42

e che:

var a = "42";
var b = "0";

a + b; // "420"

però quà:

var a = "42";
var b = 0;

a + b; "420"

inizia un leggero senso di smarrimento. La questione potrebbe tutto sommato definirsi conclusa (numero + stringa = stringa) se non fosse possibile sommare tra loro degli oggetti, oppure un oggetto con un valore primitivo, oppure fare dei veri e propri esperimenti:

function foo() {};
var bar = 42;
var obj = {};
var arr = [1];

var result = foo + bar + obj + arr; // codice perfettamente valido

Riuscireste al primo colpo a indovinare il valore contenuto nella variabile result? Ma certo! La soluzione è ovviamente: “function foo() {}42[object Object]1”.

Un momento, ma alla fine il risultato è pur sempre una stringa, e non è nemmeno così assurda come vi ho indotto a pensare.
In effetti non lo è: il JS invoca la ToPrimitive abstract operation su tutti gli operandi (più precisamente su due alla volta dato che ogni operatore + binario svolge una singola somma/concatenazione), indipendentemente se essi sono valori primitivi o meno.  È sufficiente che, come risultato di questa operazione astratta, uno solo dei due operandi sia diventato un valore di tipo stringa per trasformare l’operazione in una concatenazione, dopo aver coerciso adeguadamente l’altro operando (se necessario) invocando la ToString abstract operation su di esso.
Se invece entrambi gli operandi NON sono di tipo stringa, verrà eseguita un’addizione, dopo aver coerciso gli operandi in un valore di tipo numerico tramite la ToNumber abstract operation. Ovviamente il singolo operando non verrà coerciso se esso è già di di tipo numerico.

Vediamo qualche esempio:

var foo = 10;

foo + ""; // "10"
/*
 * La ToPrimitive abstract operation non trasforma gli operandi
 * poiché essi sono già di tipo primitivo. Dato che almeno uno dei due operandi
 * è una stringa viene eseguita la concatenazione, dopo un'opportuna coercizione
 * dell'altro operando tramite la ToString abstract operation
 *
 * È facile trovare codice che sfrutta questa istruzione (+ "") per
 * coercidere un valore numerico in un valore di tipo stringa
*/
var arr1 = [1];
var arr2 = [2, 3];

arr1 + arr2; // "12,3"
/*
 * La ToPrimitive abstract operation trasforma [1] in "1"
 * e [2, 3] in "2,3". 
 * Dato che almeno uno dei due operandi è una stringa viene
 * eseguita la concatenazione
*/
var foo = 1; 
var arr = [2]; 

foo + arr; // "12" 
/* 
 * La ToPrimitive abstract operation non modifica il valore 1 poiché esso è già primitivo,
 * ma trasforma [2] in "2"
 * Dato che almeno uno dei due operandi è una stringa viene 
 * eseguita la concatenazione dopo un'opportuna coercizione dell'altro operando
 * tramite la ToString abstract operation
*/
var foo = 1;
var obj = {
    valueOf() {
        return 42; 
    }
};

foo + obj; // 43

/*
 * La ToPrimitive abstract operation non modifica il valore 1 poiché esso è già primitivo,
 * ma trasforma l'oggetto nel valore numerico 42.
 * Dato che nessuno dei due operandi è una stringa viene eseguita la addizione
*/
var foo = true;
var bar = true;

foo + bar; // 2
/*
 * La ToPrimitive abstract operation non modifica i valori dei due operandi poiché
.* essi sono già di tipo primitivo.
 * Dato che nessuno dei due operandi è una stringa viene eseguita la addizione,
 * dopo aver coerciso gli operandi da tipo booleano a tipo numerico
 * tramite la ToNumber abstract operation
*/

Se avete difficoltà a relazionarvi con le operazioni astratte citate, date un’occhiata al primo articolo di questa serie.

Vi sono eccezioni a questo comportamento? Certo che si! Altrimenti che Javascript sarebbe?
Avete presente che [] + {}; // "[object Object]" ma {} + []; // 0? Non ho la più pallida idea del perché il vostro codice debba contenere uno statement simile, ma in caso sappiate che avete appena immerso un piede nella succitata piscina e zac!, un piranha sta assaggiando il vostro alluce.

Ad ogni modo il fulcro sta nella grammatica del linguaggio. Senza entrare troppo nel dettaglio, nel primo caso {} viene interpretato come un oggetto vuoto e quindi si applicano le regole appena esposte: l’array vuoto viene trasformato in una stringa vuota, mentre l’oggetto vuoto viene trasformato in“[object Object]”; la concatenazione è inevitabile. Nel secondo caso {} viene interpretato come un blocco completamente scollegato dall’operazione, perciò quella che noi crediamo essere una somma/concatenazione è in realtà +[], ovvero una banale coercizione esplicita dell’array vuoto in un valore di tipo numerico, 0 in questo caso. Rimando al secondo articolo di questa serie per maggiori dettagli sulla coercizione esplicita.

Altre operazioni

Le altre operazioni matematiche (-, *, /, %, **) non lasciano spazio ad ambiguità dato che sono valide solo per valori di tipo numerico. Questo non significa che non sarà possibile utilizzare stringhe, i valori booleani oppure oggetti nell’operazione, ma significa che entrambi gli operandi verranno prima trattati dalla ToNumber abstract operation, la quale provvederà a invocare la ToPrimitive abstract operation se necessario. Dopodiché l’operazione verrà eseguita correttamente.

 

To Boolean implicit coercion

Quali operazioni implicano una coercizione impicita in un valore di tipo booleano?

  1. L’espressione di test un uno statement if
  2. L’espressione di test nell’header di un ciclo for
  3. L’espressione di test nel ciclo while e nel ciclo do-while
  4. L’espressione di test nell’operazione ternaria ? :
  5. L’espressione di test nell’operazione logica || e nell’operazione logica &&

In tutti questi casi verrà invocata la ToBoolean abstract operation sul valore in questione.

Vediamo qualche esempio:

if( {foo:"bar"} ) {
    console.log("it runs");
}
// gli oggetti sono valori truthy

for(;"condition";) {
    console.log("never ends");
}
// le stringhe non vuote sono valori truthy
while("") {
    console.log("never runs"); 
}
// le stringhe vuote sono valori falsy
undefined ? console.log("it doesn't run") : console.log("it runs");
// undefined è un valore falsy
[] && console.log("it runs");
// un array, anche se vuoto, è un'oggetto, percio è un valore truthy

null || console.log("it runs");
// null è un valore falsy

 

Conclusione

Come avevo promesso, le regole che governano la coercizione implicita non sono affatto assurde ne incomprensibili, anzi usano intensivamente le operazioni astratte che abbiamo già esaminato. Perciò è sufficiente comprendere l’ordine di invocazione delle stesse per non essere mai in difficoltà quando questo tipo di coercizione viene chiamata in causa.

Un operazione strettamente legata alla coercizione implicita è la famigerata loose equality, argomento del prossimo articolo!

A proposito di me

Andrea Simone Costa

Classe 1997, Toscano DOCP, Asimov oriented.
Si è diplomato come perito elettronico, ma ha ben presto tradito le origini per immergersi nell'universo JavaScript. Ama condividere ciò che ha imparato in modo semplice e pragmatico, senza mai ricadere nel banale, coltivando segretamente il sogno di insegnare.

Gli articoli più letti

Articoli recenti

Commenti recenti