11

En este script de Perl, tengo dos variables, $x y $y, que parecen ser iguales.

#!/usr/bin/perl
use strict;
use warnings;

my $x = '';
my $y = !"true";

print "Son iguales\n" if $x eq $y;        
printf("A: '%s'\n", $x);
printf("B: %i\n", $x+0);
printf("C: '%s'\n", $y);
printf("D: %i\n", $y+0);

Pero $x+0 emite un error (como esperaba), pero $y+0 no. ¿Por qué?

Aquí es la salida del script:

Son iguales
A: ''
Argument "" isn't numeric in addition (+) at ./x line 11.
B: 0
C: ''
D: 0
Paul Vargas
  • 181
  • 1
  • 20
  • 39
Flimzy
  • 1,924
  • 10
  • 32

2 Answers2

9

Valores booleanos canónicos

Lo que te está pasando aquí es que el valor interno que usa Perl para representar un falso canónico es un valor que ya tiene sentido numérico, mientras la cadena de cero caracteres no lo tiene.

He aquí la diferencia entre los dos valores que usaste en cuanto a sus representaciones internas:

tchrist% perl -MDevel::Peek -e 'Dump("")'
SV = PV(0x7ff4aa804080) at 0x7ff4aa8292d0
  REFCNT = 1
  FLAGS = (POK,IsCOW,READONLY,PROTECT,pPOK)
  PV = 0x7ff4aa405230 ""\0
  CUR = 0
  LEN = 10
  COW_REFCNT = 0

tchrist% perl -MDevel::Peek -e 'Dump(! "true")'
SV = PVNV(0x7fa48c0021f0) at 0x104ba29e8
  REFCNT = 2147483647
  FLAGS = (IOK,NOK,POK,READONLY,PROTECT,pIOK,pNOK,pPOK)
  IV = 0
  NV = 0
  PV = 0x104b887f5 ""
  CUR = 0
  LEN = 0

Cuando haces $x + 0, la operación aritmética de + intenta extraer un valor numérico (NV, o en algunos casos IV) del valor escalar (SV). Como ves en las pruebas que hice arriba, la variable $x es una cadena que no tiene ni NV ni IV. El algoritmo que usa Perl para inferir un número de una cadena (PV, valor puntero) emite la queja que viste sobre un argumento no-numérico para la adición.

Por otro lado, cuando hiciste lo mismo con $y + 0, la forma interna de la variable $y no era un PV (cadena) sino un PVNV, o sea un PV y un NV a la vez. Porque este PVNV sí tiene valor numérico NV = 0, no sale ninguna advertencia cuando intentas hacer una operación numérica con él.

Si te fijas en los detalles de Devel::Peek, quizás verás alguna cosa más. Las direcciones de los PV no se parecen en absoluto, siendo el uno 0x7ff4aa405230 (grande) y el otro 0x104b887f5 (chico). Ocurre que Perl almacenan esos dos punteros en dos espacios completamente distintos porque el uno es un valor fundamental del compilador mismo y el otro ni es más que uno que calculaste tú.

Además el REFCNT (el número de referencias) del primero es 1 solo, pero el REFCNT del segundo es un número enorme, de hecho 0x7FFF_FFFF.

Falso mágico

Esta curiosidad puede explicarse por la existencia de un valor especial que Perl mantiene para representar un falso canónico. En la documentación perlapi para programadores C, explica lo siguiente:

  • PL_sv_no

This is the false SV. See PL_sv_yes. Always refer to this as &PL_sv_no.

  SV  PL_sv_no

Traduciendo libremente,

  • PL_sv_no

Este es el SV falso. Véase PL_sv_yes. Siempre se refiere a este valor como &PL_sv_no.

 SV  PL_sv_no

A veces se llama este falso canónico falso mágico (magic false en inglés) por el hecho de que funcione como undef pero sin las advertencias que provocarías si usases undef para eso.

Se puede obtener otra muestra de este falso mágico por decompilar un programa mínimo:

tchrist% perl -MO=Concise,-exec -e 'print 0 > 1'
1  <0> enter 
2  <;> nextstate(main 1 -e:1) v:{
3  <0> pushmark s
4  <$> const(SPECIAL sv_no) s/FOLD
5  <@> print vK
6  <@> leave[1 ref] vKP/REFC
-e syntax OK

Este falso canónico se produce en varios escenarios:

  • Los operadores booleanos come ! 1, ! "true".
  • Los operadores relacionales como <, >, &c.
  • Una sustitución que no substituye nada "foo" =~ s/x/y/gr

Cómo usarlo

Al fin y al cabo, lo importante que quiero que entiendas es que tú puedes aprovechar del falso mágico si quisieras tener un valor en tu programa que evaluase a 0 como número y a "" como cadena, pero no quieres las advertencias que siempre salen al usar undef así. Por ejemplo:

 use utf8;
 my $falso_mágico = (0 > 1);

O igualmente:

 use utf8;
 my $falso_mágico = !0;

O aun:

 use utf8;
 my $falso_mágico = ! "true";

Hazme el favor de nunca escribir esto:

 use utf8;
 my $falso_mágico = ! "false"; # ¡qué malo soy!

Sí, es verdad que esa versión produce el mismo valor, pero ese código va a confundir a todo el mundo. Por favor no lo hagas. :)


Pregunta relacionada: ¿Cómo puedo probar si un valor booleano es falso en Perl?

tchrist
  • 503
  • 6
  • 19
4

La variable $y no es una cadena de texto, sino el resultado de la negación, que es compatible con el operador de adición (+).

Considera el siguiente ejemplo para ver las diferencias más claramente:

#!/usr/bin/perl
use strict;
use warnings;

my $x = '';
my $y = !"true";
my $z = !""

print "Son iguales\n" if $x eq $y;        
printf("A: '%s'\n", $x);
printf("B: %i\n", $x+0);

printf("C: '%s'\n", $y);
printf("D: %i\n", $y+0);

printf("E: '%s'\n", $z);
printf("F: '%i'\n", $z);

La salida es:

Son iguales
A: ''
Argument "" isn't numeric in addition (+) at ./x line 11.
B: 0
C: ''
D: 0
E: '1'
F: '1'

Podemos ver que el resultado de negar una cadena de texto depende de si hay o no contenido en ella. Como dato adicional, "true" y "false" no significan nada, son cadenas y se evaluan como verdaderas. Sólo las cadenas "0" y "" son falsas.

Para aclarar más las cosas, Perl imprime una cadena vacía cuando se maneja un valor falso como cadena de texto (caso C). Por ejemplo, imprimir printf("'%s'\n", 1==2); imprime una cadena vacía.

ArthurChamz
  • 500
  • 2
  • 16