You are currently browsing the monthly archive for marzo 2011.

El operador de igualdad se usa para dos conceptos diferentes en los lenguajes de programación: la identidad y la equivalencia. En este artículo quiero aclarar estos conceptos y dar ejemplos como algunos lenguajes de programación los implementan.

Los conceptos

Habitualmente se denomina igualdad al operador == (o = en algunos lenguajes como Pascal o Visual Basic). Pero hay dos conceptos de lo que puede ser igual: la entidad que contiene un valor (identidad) o el valor que contiene (equivalencia).

Decimos que las dos variables a = 5 y b = 5 son iguales en sentido matemático porque contienen el mismo valor 5. Sin embargo, a y b son dos variables distintas en sentido informático si se guardan en dos direcciones distintas en la memoria. En el último caso hablamos de identidad: las variables a y b son iguales cuando hacen referencia a la misma entidad. En el primer caso hablamos de equivalencia. Las variables pueden hacer referencia a una memoria diferente, pero contienen el mismo valor.

Es obvio que la identidad es un criterio más estricto. Dos objetos distintos pueden tener el mismo valor o no, pero el mismo objeto comparado a si mismo siempre es igual.

El uso en los lenguajes

Implementación de identidad y equivalencia
Significado de igualdad
Equivalencia
(mismo valor)
Identidad
(mismo objeto)
Lenguajes compiladas (“antiguas”) Operador == Operador == entre punteros
Lenguajes interpretadas (“modernas”) Método equals Operador ==

Los lenguajes de programación más antiguas como C o Fortran suelen interpretar la igualdad como equivalencia. Para esto puede haber varias razones. Primero la equivalencia es el concepto más matemático (Fortran viene de formula translation). Luego no existía el concepto de objeto cuando se crearon estos lenguajes. Los tipos de variables eran los básicos como caracteres, números enteros y números de coma flotante. Los compiladores convertieron las expresiones a == b a una comparación entre los valores de dos direcciones de memoria. Para comprobar identidad se podían comparar los punteros a estas memorias. No extraña que se sobrecarga el operador == en C++ con un significado de equivalencia hasta hoy en día.

El asunto se cambió con los lenguajes orientado a objetos en que no existen punteros como C#, Java o PHP. En estos lenguajes, no son las variables que guardan los objetos sino el intérprete. La variable de objeto guarda meramente un identificador. Por lo tanto, una comparación entre dos variables es una comparación entre identificadores y no valores. En consecuencia la igualdad tiene la conotación de identidad.

También cabe señalar que estos lenguajes no permiten sobrecarga el operador == como en C++. No es posible crear un operador == que compare el contenido. Tampoco conviene que el intérprete realice una comparación binaria entre dos objetos. La representación binaria depende de la plataforma y un lenguaje como Java se creó justamente para no depender de ella.

Clases inmutables

Ninguna regla sin excepción. En Java o C#, sí, se pueden comparar los valores de dos strings con el operador ==. Esto es posible ya que estas clases son inmutables (o constantes según la jerga de C++): Sus instancias ya no se pueden modificar una vez creadas. Los intérpretes – la máquina virtual de Java o el framework de .NET, respectivamente – aprovechan esta característica. Si initializo una instancia de string con un valor ya existente en otra instancia, entonces no se crea un nuevo valor, sino la nueva instancia apunta al valor existente. Por esto, la nueva variable contiene una referencia a la misma instancia que la variable antigua. Ambas apuntan al mismo objeto y, por lo tanto, son idénticos.

Las clases inmutables tienen más características interesantes. Por ejemplo, no hace falta copiar sus contenidos. En una copia profunda de una clase (deep copy en inglés) debo crear constructores de copia para cada subobjeto de una clase, pero no para un string. La copia causa que una variable de clase string apunta a otro objeto guardado en el intéprete, pero el valor del objeto en si no se modifica ya que es inmutable. Operaciones sobre cadenas de texto reasignan referencias, pero no tiene por qué crear nuevas cadenas.

Conclusión

Hemos visto que existen dos conceptos de igualdad: la identidad y la equivalencia. La equivalencia es el concepto implementado en los lenguajes más tradicionales, mientras la identidad se ha impuesto en los lenguajes más recientes – entre otros motivos por razones técnicas. Los lenguajes que implementan el operador == como equivalencia suelen comparar identidades mediante punteros. Los lenguajes que implementan la identidad para el operador == suelen propicionar un método equals para la equivalencia.

Enlaces de interés

En este artículo quiero proponer una manera eficaz y general de suprimir una advertencia de “parámetro sin referencia” en los programas de C++. Tras explicar varias soluciones estándar, propongo la solución de la función fantasma.

Introducción

Si uno quiere programar con buen estilo, entonces suele activar el máximo nivel de advertencia del compilador. Esto, por cierto, también es una ayuda para entender bien el concepto de lenguaje. Dejarse advertir sobre todo puede resultar en que no todas las advertencias aciertan. Sin embargo, desactivarlas a coste de no ser advertido por errores significativos tampoco conviene.

Por ejemplo, si compilamos con Microsoft Visual Studio el constructor de copia

Object(const Object& source) {}

entonces se obtiene la siguiente advertencia: warning C4100: ‘source’ : parámetro formal sin referencia. Esto sería un caso típico de un constructor de copia que realmente no copia nada. Debemos mantener la interfaz para que no deje de ser un constructor de copia, pero actualmente no usamos el parámetro source.

Eliminar el parámetro

El artículo Visual C++: Warning C4100 discuta varias opciones que mencionamos aquí

  • Hacer caso a la advertencia y eliminar el parámetro no usado.
  • Si necesitamos el parámetro pero estamos seguros que no lo usaremos, entonces podemos quitar el nombre. Un constructor de copiar se quedaría sin “source”

    Object(const Object&) {}
    
  • Si queremos mantener el nombre aunque actualmente no lo usamos, podemos comentarlo.

    Object(const Object& /* source */) {}
    

    Debemos tener en cuenta que el comando @param de un comentario de documentación como lo usuaría Doxygen se quedría obsoleto. Deberíamos comentar el comantario. Además, los comentarios /* */ no suelen poder anidarse – un problema a la hora de deshabilitar bloques de código.

  • Deshabilitar la advertencia. En Microsoft Visual Studio esto se puede conseguir de la siguiente manera:

    #pragma warning( push )
    #pragma warning( disable: 4100 )
    Object(const Object& source) {}
    #pragma warning( pop )
    

    El problema de este enfoque es que se necesita una directiva #pragma diferente para cada compilador y ni siquiera está garantizado que haya. Así no conviene si el programa debe compilar en varias plataformas.

Una macro de “parámetro no referenciado”

A parte de estas propuestas suele haber otro apaño de usar el parámetro. Por ejemplo

Object(const Object& source)
{
    source = source;
}

Por ser una construcción no especialmente obvia, se puede crear una macro UNREFERENCED_PARAMETER.

#define UNREFERENCED_PARAMETER(param) param = param
Object(const Object& source)
{
    UNREFERENCED_PARAMETER(source);
}

La macro trae la ventaja que se puede definir de forma diferente según el efecto deseado. Una versión de release podría expandir la macro a nada:

#ifdef _DEBUG
#define UNREFERENCED_PARAMETER(param) param = param
#else
#define UNREFERENCED_PARAMETER(param)
#endif

Lógicamente volvería salir la advertencia cuando la macro suprime el uso del parámetro.

La desventaja de la asignación es tiempo de cálculo que requiere y que no está disponible en todas las clases. Estas clases son qquellas que tienen el operador = sobrecargado, pero ninguna sobrecarga admite una instancia de la clase misma como parámetro (o fuente) de la asignación.

Función fantasma

Mi forma preferida es una mezcla de varias propuestas y usa una función fantásma.

// La función fantásma (dummy function)
inline void reference_parameter(const void*) { }

// La macro
#define UNREFERENCED_PARAMETER(param) \
    reference_parameter(static_cast<const void*>((&param)));

// El uso
Object(const Object& source)
{
    UNREFERENCED_PARAMETER(source);
}

Esta propuesta reune todas las ventajas.

  • Gracias a referenciar el parámetro no referenciado por una macro, podemos eliminar y cambiar esta definición sin tocar el resto del código.
  • Si dejamos la macro como aquí propuesta, entonces se reemplaza por una llamada a una función con un puntero al parámetro no usado. Esta operación siempre es posible con cualquier objeto.
  • La función reference_parameter no pone nombre al parámetro, por lo cual no aparece una advertencia de “parámetro sin referencia”.
  • Finalmente es una operación rápida. Copiar un puntero es rápido y como la función no hace nada, desaparecerá con la expansión inline.

Referencias

Enlaces externos
En este sitio

Lectura adicional


Durante la modificación de código conviene a veces desactivar partes del código. La forma habitual es usar comentarios de C /* */. Esto puede traer problemas, porque estos comentarios no se puede anidar.

Una buena manera para evitar este problema es no utilizar los marcadores de comentarios /* */. Utiliza comentarios de línea simple //. La herramienta de documentación automatizada Doxygen permite marcar comentarios de documentación con una triple barra ///.

La forma más segura de desactivar código es mediante macros de compilación. Cualquier cosa que se encuentre entre #if 0 y #endif no se compila. Los entornos de desarrollo integrados modernos son capaces de marcar código excluído de la compilación con un color de fuente diferente para distinguirlo.

En lugar de #if 0 puede escribir también #if NOMBRE_DEFINIDO. Para desactivaciones no tan temporales queda más claro que un simple 0, ya que el NOMBRE_DEFINIDO puede conllevar la razón por qué se desactivó el código. Además, se puede definir este símbolo mediante una opción en la línea de comando del compilador (normalmente la opción /d) sin modificar el código.

La ventaja de las directivas respecto a los comentarios /* */ es su capacidad de anidamiento. Podemos desactivar código aunque ya contiene un trozo desactivado. (No olvides marcar con un comentarios el #endif correspondiente a cada #if)

Referencias

Introducción

“Expresiones regulares”, muchas veces abreviados por RE de regular expressions en inglés, sirven para buscar un texto dentro de otro de una forma sofisticada. Pueden servir, además, para dividir un texto complejo en componentes simples.

Un programador que domina expresiones regulares puede conseguir a dividir el código de un programa entero en símbolos con una sola llamada. No obstante, para el novato el escenario se presenta más bien al revés: puede gastarse el tiempo de escribir un programa entero para hacer bien una sola llamada con expresiones regulares.

Expresiones regulares son fascinantes y frustrantes a la vez. Este artículo trata de como podemos acercarnos al probablemente más ilegible lenguaje de programación con utilidad.

El próposito de expresiones regulares

En la programación se da muchas veces el caso de buscar una cadena de texto en otra. Por ejemplo, podemos buscar el texto “rata” en la cadena “errata”. “rata” se denomina el “patrón de búsqueda” (search pattern en inglés) o “subcadena” y “errata” el destino. (Para el destino hay más variedad de nombres.)

Si el patrón de búsqueda se encuentra dentro del texto a rastrear, es decir, si hay un match en inglés, entonces una función de búsqueda típicamente devuelve el índice de la primera ocurrencia de la subcadena (como la función strpos en PHP, o un puntero a la subcadena (como la función strstr en C).

Las funciones de búsqueda sin expresiones regulares existen también sin distinguir entre mayúsculas y minúsculas (case-insensitive en inglés) y comenzando la búsqueda desde el final – como stripos y strrpos en PHP, respectivamente.

Estas funciones son fáciles de entender, pero no ofrecen mucha flexibilidad. Por ejemplo, ¿qué hago para buscar la palabra “rata” y no simplemente la secuencia de cadena r-a-t-a dentro de cualquier palabra? Quiero una función que encuentre “rata” en “El gato caza la rata” pero no en “errata”. Podría apañarme buscando algo como espacio más “rata” más otro espacio, pero esto no funcionará si rata es la última o primera palabra. Es decir, lo que necesito es una función que encuentre mi patrón de búsqueda sólo cuando es una palabra entera y no parte de una palabra.

Pues, esta funcionalidad se obtiene con las expresiones regulares: una búsqueda con alternativas y cadenas variables en longitud y contenido.

Un ejemplo de carácter especial

Las expresiones regulares se basan en usar unos caracteres como especiales. Por ejemplo, el carácter “+” significa “el símbolo delante una o más veces. La expresión regular “ra+to” correspondería a “rato”, “raato” o “raaaaato”, pero no a “rto” (cero veces la letra a).

Hay muchos símbolo especiales que suelen estar explicados en la referencia de las funciones que usan expresiones regulares. Hay que tener en cuenta que existen pequeñas pero a veces frustrantes diferencias entre diferentes lenguajes de programación y proveedores de compiladores respecto a las expresiones regulares. Un dialecto de expresiones regulares que está muy difundido es el de perl – un lenguaje que hace mucho uso de expresiones regulares.

Componer expresiones regulares

En este artículo no quiero enumerar los códigos especiales – esto ya se hace bastante exhaustivamente en muchos manuales de referencia – sino quiero dar unas pistas de como un humano puede componer y entender un código como este:

\[\s*\w+(\s+\w+)*\s*\]

Mi forma preferida de crear una expresión regular es componerla de objetos cada vez más complejos. El código anterior corresponde en lenguaje humano a encontar “una o varias palabras entre corchetes separados o no por espacios”. Por ejemplo, algo como “[sección]”, “[índice de array]” o “[ corchetes con espacios ]”. Como una expresión regular es una cadena de texto, podemos codificarla como string.

Analizemos la definición “una o varias palabras entre corchetes separados o no por espacios”. Una palabra está compuestos por letras. El código de una letra cualquiera en una expresión regular es \w. “Uno o más” se códifica con +. Es decir, podemos decir que

const string PALABRA = "\\w+";

Aquí la barra inversa \ está duplicada porque se encuentra dentro de comillas. La mayoría de los lenguajes de programación usan la letra \ como letra de escape dentro de una cadena de texto que altera el significado de la letra siguiente. Por ejemplo, \t no es “barra más t” sino un carácter tabulador. Pues, si se quiere escribir una barra inversa, entonces la combinación es \\.

Una letra de espacio se codifica con \s. La letra * significa “cero o más”. Por lo tanto, \s* representa uno, varios o ningún espacio, es decir “separados o no por espacios”.

const string ESPACIOS_OPCIONALES = "\\s*";

Nótese que hemos no hemos denominado la constante “cero o más espacios” sino “espacios opcionales”. Es importante escoger nombres significativos pero los más cortos posibles para mejorar la calidad del código. Así conviene tener en cuenta palabras abstractas y colectivas.

De forma similar podemos codificar “uno o más espacios” con +.

const string AL_MENOS_UN_ESPACIO = "\\s+";

Como vemos, la ídea es reemplazar los códigos crípticos de las expresiones regulares por identificadores con una semántica más clara. Se pueden construir expresiones más complejas concatenando cadenas de texto, sin embargo esto no es siempre lo más práctico. Por ejemplo, es más intuitivo implementar una subexpresión regular (algo entre paréntesis) con una función.

string subexpr(string contenido)
{
    return "(" + contenido + ")";
}

“Ninguno o más” se puede implementar por

string ninguno_y_mas(string contenido)
{
    return subexpr(contenido) + "*";
}

Por mantener un mismo estilo conviene “transcribir” también construcciones que se usan menos a menudo como “en corchetes”.

string en_corchetes(string contenido)
{
    return "\\[" + contenido + "\\]";
}

Con las definiciones anteriores podemos crear expresiones más complejas como “entre espacios opcionales”

string entre_espacios_opcionales(string contenido)
{
    return ESPACIOS_OPCIONALES + contenido + ESPACIOS_OPCIONALES;
}

o varias “palabras” en lugar de una “palabra” simple

const string PALABRAS =
    PALABRA + ninguno_y_mas(AL_MENOS_UN_ESPACIO + PALABRA);

Con todo esto conseguimos que finalmente podemos construir una expresión para “Una o varias palabras entre corchetes separados o no por espacios”.

// Una o varias palabras entre corchetes separados o no por espacios
const string PALABRAS_ENTRE_CORCHETES =
	en_corchetes(entre_espacios_opcionales(PALABRAS));

Ahora la pregunta: ¿Qué es más intelligible? ¿PALABRAS_ENTRE_CORCHETES o el formato original \[\s*\w+(\s+\w+)*\s*\]?

Crítica de la composición

Una de las desventajas de componener expresiones regulares es que se puede perder la vista sobre las subexpressiones anidados. Cada función puede añadir paréntesis o no y subir el nivel de anidamiento. Normalmente esto no es un problema. Sin embargo, unas bibliotecas devuelven también resultados que se basan en estas subexpresiones – normalmente de una forma ordenada en un array. Cambiar la estructura de subexpresiones cambia el orden y la cantidad de estos subresultados. No será demasiado difícil adaptar el código si siempre se analiza la misma expresión regular, pero puede complicarse cuando la expresión es variable.

Otra desventaja es que la composición requiere un tiempo de cálculo durante la ejecución del programa. Sin embargo, esto es despreciable en la mayoría de las veces y suele ser suficiente componer una expresión regular una vez y luego guardarla en una variable estática.

La composición debe tener en cuenta los diferentes dialectos de expresiones regulares si quieren ser generales. También puede ser necesario añadir caracteres adicionales. Algunas funciones como preg_match requieren limitar una expresión regular por dos símbolos iguales como “/” + expresión + “/”. Tras el último limitador puede haber modificadores como “i” como case-insensitive.

Analizar expresiones regulares

Los ejemplos para buscar un determinado tipo de texto suelen dar las expresiones regulares tan feas como inevitablemente son. Para dar con este chorro de caracteres usamos sustituciones similares como en el apartado de composición aunque esta vez sin la necesidad de obtener un programa compilable.

Como ejemplo analizamos el ya conocido código de \[\s*\w+(\s+\w+)*\s*\] y pretendemos que aún no sabemos lo que es. Podríamos reemplazar los \s por <espacio> y los \w por <carácter>. Sin embargo es más directo si tenemos en cuenta los cuantificadores + (uno o más) y * (cero o más veces). De hecho, con un poco de práctica podemos interpretar el código de “un carácter o más” \w+ como “palabra”.

// El código original
\[\s*\w+(\s+\w+)*\s*\]

// se puede quedar en algo como
\[
<espacios opcionales>
<palabra>(<uno o más espacios><palabra>)*
<espacios opcionales>
\]

Resolvemos los paréntesis en la línea central de nuestro pseudo-código.

// La subexpresión ()* se repite entre cero y n veces
<palabra>(<uno o más espacios><palabra>)*

// se puede quedar en algo como
<palabra> cero_o_mas(<espacios><palabra>)

Una palabra más cero o más “espacios + palabra” son simplemente “palabras separadas por espacios” – popularmente conocido como “texto”. Los códigos \[ y \] son simplemente corchetes. Se deben escapear estos símbolos con \ ya que los corchetes tienen un significado especial en una expresión regular.

Con todo esto, la expresión regular dice algo como

[
<espacios opcionales>
<texto>
<espacios opcionales>
]

A esta altura ya podemos ver que la expresión \[\s*\w+(\s+\w+)*\s*\] representa a un texto entre corchetes que no necesitan estar pegados a la primera y última letra del texto.

Observaciones sobre el análisis

Como hemos visto, el análisis puede acabar en líneas largas. Si tenemos en cuenta que no necesitamos seguir ninguna sintaxis particular para el análisis de una expresión regular, entonces podemos añadir espacios o salto de líneas a nuestro gusto. Podemos copiar la expresión regular en un procesador de texto como Microsoft Word y cambiar el color de texto o la fuente si nos ayuda. El análisis es para que nosotros entendamos – no el ordenador.

Hemos hecho este análisis escribiendo, pero con un poco de práctica se puede hacer en la mente. No hay un camino de oro de como empezar. Puede convenir a reemplazar en primer lugar trozos pequeños de la expresión regular por identificadores singificativos o puede ser mejor empezar por la sustitución de subexpresiones entre paréntesis. Lo más importante es que no dejemos de ver el bosque por tantos árboles y escogemos los identificadores adecuados. El código \w+ no es “uno o más caracteres” sino una “palabra”.

Conclusión

Como expresiones regulares no permiten espacios y saltos de línea como elemento de estructuración, se hacen difícil a leer y entender. He propuesto una manera de componer y analizar expresiones regulares que transforma símbolos crípticos a conceptos humanos.

Hay muchos artículos sobre qué es una expresión regular pero hasta ahora no he visto ningún artículo similar para no perderse en ellas. Un poco me sorprende porque mi enfoque me ha ayudado bastante y creo todo el mundo tiene problemas similares a la hora de iniciarse en las expresiones regulares.

De hecho podría ser un avance de crear un lenguaje de expresiones regulares estructuradas que permite escribir patrones de búsqueda en un formato más legible para los humanos y que se dejaría transformar fácilmente en las expresiones regulares habituales.

Referencias

Páginas externos
En este mismo sitio

En España siempre eché de menos buenos libros técnicos y científicos. Es decir, libros con índice de palabras al final para no necesitar leer el libro entero para encontrar (o no) un determinado asunto. Muchos libros españoles no están publicados para más que los alumnos del profesor que lo escribió. No extraña que los libros de referencia en español son muchas veces traducciones de libros norteamericanos.

Creo, antes de quejarse, uno debería hacerlo mejor uno mismo. Sin embargo, hasta ahora escribir un libro entero propio me ha parecido un proyecto demasiado ambicioso (tampoco sé como publicarlo), así surgió la idea de iniciar un blog con artículos sobre diferentes aspectos de la informática y cuyo nivel se sitúa entre un libro y las páginas de web habituales de “como-resuelvo-este-problema-en-concreto”. Como ya hay muchas buenas páginas en inglés, me incliné escribir los artículos en español aunque no es mi lengua materna (bueno, el inglés tampoco).

Tras casi un año, este blog sobre trucos informáticos recibe alrededor de 60 a 70 visitas en un día laboral habitual – en enero más que 90 por primera vez. En febrero 2011 se han acumulado 10000 visitas totales a mi blog. Esto es ciertamente conseguir un hito. Estoy contento que mis artículos han ayudado a alguien de alguna forma – ahora ya más que diez mil veces. Esto da la sensación de haber contribuido algo positivo al mundo y “pagar” así a la comunidad estos proyectos de código abierto de que he disfrutado tantas veces gratis.

Lo que más ha faltado hasta ahora es la interacción con los lectores como tú. Aunque todavía me quedan bastante ideas sobre qué escribir, podría ser más eficaz si conozco vuestra opinión. Así os invito a enviarme un mensaje o dejar un comentario en alguno de los artículos.

Referencias

Escribe tu dirección de correo electrónico para suscribirte a este blog, y recibir notificaciones de nuevos mensajes por correo.

Únete a otros 56 seguidores

Archivos

marzo 2011
L M X J V S D
« Ene   Abr »
 123456
78910111213
14151617181920
21222324252627
28293031