lunes, 15 de abril de 2019

Cuando unos leguajes tratan los arrays por referencia y otros por copia

¿Qué ocurre cuando asignamos un array a otro? En la mayor parte de los lenguajes, lo habitual es que la nueva variable sea un puntero (referencia) al primer array. Por ejemplo, en Python:

array2 = array1 = {'uno': 1, 'dos': 2}

print('array1 antes:', array1)
print('array2 antes:', array2)

array2['dos'] = 22
array1['tres'] = 3

print('array1 después:', array1)
print('array2 después:', array2)


La salida de este script sería la siguiente:

~ $ python3 testarray.py 

array1 antes: {'uno': 1, 'dos': 2}
array2 antes: {'uno': 1, 'dos': 2}
array1 después: {'uno': 1, 'dos': 22, 'tres': 3}
array2 después: {'uno': 1, 'dos': 22, 'tres': 3}


Como era previsible, al cambiar algo en array1 o en array2, el otro array se modifica, porque las dos variables son una referencia al mismo array. (En Python a este tipo de datos se le llama diccionario, pero podemos asumir que es equivalente al array asociativo de PHP)

Este sencillo ejemplo es muy adecuado para explicar a alguien lo que son las referencias. Se ve claramente que cambiar algo en una variable tiene efectos colaterales.

Sin embargo, podemos encontrarnos sorpresas en otros lenguajes. PHP, por ejemplo, copia el array en la nueva variable y a partir de ese momento, son dos referencias independientes. Mucho cuidado si nuestro array contiene muchos datos, se duplica el consumo de memoria:

<?php 
$array1 = $array2 = array("uno" => 1, "dos" => 2);

echo "array1 antes:"; var_dump($array1); echo "\n";
echo "array2 antes:"; var_dump($array2); echo "\n";

$array1["tres"] = 3;
$array2["dos"] = 22;

echo "array1 después:"; var_dump($array1); echo "\n";
echo "array2 después:"; var_dump($array2); echo "\n";


La salida de este script es la siguiente:

~ $ php testarray.php 

array1 antes:array(2) {["uno"]=> int(1), ["dos"]=> int(2)}
array2 antes:array(2) {["uno"]=> int(1), ["dos"]=> int(2)}

array1 después:array(3) {["uno"]=> int(1), ["dos"]=> int(2), ["tres"]=> int(3)}
array2 después:array(2) {["uno"]=>, int(1), ["dos"]=> int(22)}

¿Qué ha pasado en este caso? Las dos variables son independientes, los cambios en una no afectan a la otra. Cada una apunta a una región de memoria diferente. En PHP se puede conseguir el mismo comportamiento que en otros lenguajes, esto es, que asignar una variable a otra por referencia:

<?php 
$array1 = array("uno" => 1, "dos" => 2);
$array2 = &$array1;

echo "array1 antes:"; var_dump($array1); echo "\n";
echo "array2 antes:"; var_dump($array2); echo "\n";

$array1["tres"] = 3;
$array2["dos"] = 22;

echo "array1 después:"; var_dump($array1); echo "\n";
echo "array2 después:"; var_dump($array2); echo "\n";

En este caso el comportamiento es el mismo que en el primer script en Python.
La expresión $array2 = &$array1asigna por referencia la variable 1 a la variable 2.

¿Qué comportamiento es deseable o más conveniente en un lenguaje de programación? La aproximación de PHP puede parecer más ineficiente, pero si reflexionamos un momento, tiene algo a su favor: es consistente.

<?php
$a = $b = 4;
$a = 5;
var_dump($a); var_dump($b);

La salida es:

int(5)
int(4)

Cada variable es una copia.

En Python, los tipos de datos simples sí que se asignan por copia:

Python 3.6.7 (default, Oct 22 2018, 11:32:17) 
>>> a = b = 4
>>> a = 5
>>> a
5
>>> b
4
>>>

También se ve cómo son variables independientes. 

Todos los tipos de datos, simples y compuestos, se comportan igual PHP, sin embargo, en Python los tipos de datos compuestos (diccionarios, listas,... se asignan por referencia y los tipos de datos simples (enteros,...), se asignan por copia.

Esto no es bueno ni malo... pero hay que estar atento.