Operadores Personalizados

Tutorial Swift – Operadores Personalizados

In Swift Avanzado, Tutoriales by Josué V. Herrera0 CommentsLast Updated: 05 Ago 2017

En este Tutorial Swift continuaremos aprendiendo acerca de la sobrecarga de operadores, pero esta vez enfocados en aquellos operadores personalizados que podemos definir, veremos también como cambiar la precedencia, todo esto en pos de lograr comportamientos aún más específicos.

Para una mejor comprensión de este tutorial recomiendo la lectura de nuestro anterior artículo donde hablamos sobre la sobrecarga de operadores en Swift. Sin más, comencemos hablando de la precedencia y la asociatividad.

Precedencia y Asociatividad

La precedencia de operadores es una funcionalidad o una característica que existe en la mayoría de los lenguajes de programación, donde se le da a un operador una prioridad más alta que a otros en pos de poder evaluar (en una primera instancia) expresiones lógicas y matemáticas correctamente.

Ahora, la asociatividad de operadores define como los operadores de misma precedencia son agrupados, ya sea en asociación con la expresión de la izquierda o desde la derecha.

Dicho esto es evidente la importancia de considerar la precedencia y la asociatividad de cada operador cuando estamos creando una expresión donde confluyan varios de ellos. Por ejemplo la precedencia de operadores explica por que la siguiente expresión es igual a 17.

Si analizamos la expresión estrictamente desde la izquierda hacia la derecha, podemos esperar que sea calculada de la siguiente manera:

  • 2 más 3 es igual a 5
  • 5 resto de 4 es igual a 1
  • 1 multiplicado por 5 es igual a 5

No obstante el resultado es 17 y no 5 ya que los operadores con una precedencia alta son evaluados antes de aquellos de precedencia menor. En el ejemplo el operador de resto (%) y el de multiplicación cuentan con un nivel de precedencia más alto que el operador de suma (+), lo que trae como resultado que en el análisis sintáctico / léxico estos sean evaluados antes que el operador de suma.

En el caso anterior es muy importante tener en cuenta la precedencia y asociatividad de cada operador en pos de conocer el orden exacto en el que la expresión será evaluada. Ahora, teniendo en cuenta de que los operadores de resto y multiplicación cuentan con precedencias iguales, y que mientras mayor sea la precedencia de un operador antes será evaluado, nos hacemos la siguiente pregunta:

¿Qué sucede cuando ambos operadores cuentan con la misma precedencia?

Pues en estos casos (como el actual) las fuerzas están igualadas y entra en juego la asociatividad la cual también en el caso de ambos operadores está enfocada en su expresión de la izquierda. Esto último sería similar a decir que ante dos operadores de similar precedencia y asociatividad, pues la asociatividad será el aspecto que determinará el flujo de la operación. En el caso de que la asociatividad de los operadores sea hacia la expresión a su izquierda pues esta será la primera en evaluarse. Veamos todo esto de una mejor manera añadiendo paréntesis al ejemplo anterior:

Aquí podemos analizar de una manera más gráfica como el operador resto es el primero en evaluar ya que se encuentra a la izquierda del operador de multiplicación, luego le sigue este último y finalmente el operador de suma que es el de menor precedencia.

Implementando un Operador Prefix o Postfix

Imagino que todos conocemos lo que es el factorial de un número. En caso contrario demos una introducción básica citando a Wikipedia:

El factorial se define en principio como el producto de todos los números enteros positivos desde 1 (es decir, los números naturales) hasta n.

En pos de una explicación más clara y sobre todo práctica digamos que el factorial es muy útil en las permutaciones. Por ejemplo si tenemos dos casillas y dos fichas y nos preguntan por cuantas combinaciones podemos lograr pues la respuesta sería que 2 ya que:

CombinacionesCasilla 1Casilla 2
1 Ficha 1Ficha 2
2Ficha 2Ficha 1

…sencillo verdad? Pues esto se complica en la medida que el número de casillas y sus fichas correspondientes son mayores, por ejemplo en el caso de 5 las combinaciones posibles aumentan a 120 pero ya en el caso de 7 ascienden a 5040. Ya en este punto es necesaria una ayuda y es cuando el cálculo factorial nos tira una mano brindándonos la respuestas a estas incógnitas. Sí pues resulta que el factorial de 2 es igual a 2, el de 3 es igual a 6, el de 5 es igual a 120 y el de 7 es igual a 5040. Espero haya quedado claro este ejemplo de uso, continuemos.

Hallar el factorial de un número es de esos primeros problemas y algoritmos con los que nos enfrentamos cuando estamos aprendiendo a programar y especialmente cuando llegamos a las funciones recursivas, por esta razón lo he escogido para la demostración de esta sección.

La regla o función para hallar el factorial de un número es la siguiente:

n! = n * (n – 1)!

…que sería lo mismo a decir: el factorial de n es igual a n multiplicado por el factorial de n menos uno.

Volviendo a lo que nos atañe, mi intención es la de crear un operador personalizado que me permita obtener el factorial de un número. Como podemos ver arriba el símbolo matemático de factorial es el signo de exclamación (!)  como sufijo de la variable o número al cual deseamos hallar su factorial. El problema que tenemos aquí es que el signo de exclamación ya tiene un significado dentro del lenguaje Swift, ya existe una sobrecarga por defecto que lo convierte en un operador unario postfix y que le asigna una funcionalidad: el operador de negación lógica.

Esto nos trae un problema ya que no podemos sobreescribir la funcionalidad del operador de negación lógica y mantenerlo como un operador postfix, tenemos que llevarlo a prefix para lograr un código compilable, es decir que cuando usemos el signo de interrogación al final fungirá como el operador de negación lógica y cuando lo usemos al inicio como factorial de ese número o variable.

Lo que intentaremos lograr es que la expresión de la línea 3 del siguiente código sea válida:

Como podemos observar nuestro operador personalizado factorial es de tipo prefix por lo antes explicado. Veamos su implementación:

La diferencia con lo que hemos visto hasta ahora (tutorial acerca de la sobrecarga de operadores en el lenguaje de programación Swift) se encuentra en la línea 1 y en la 3. En la primera declaramos al compilador nuestra intención de definir un nuevo operador. Esto lo hacemos especificando su tipo (prefix) seguido por la palabra clave operator y finalizando con el caracter/es (!) que darán vida a nuestro nuevo operador personalizado. La otra línea a destacar es la 13 donde multiplicamos el número por el factorial de él mismo menos uno, haciendo uso de nuestro propio operador factorial y generando así una llamada recursiva.

En el caso de que se pregunten como sería la definición de un operador postfix pues similar, solamente tendríamos que cambiar la palabra clave prefix por postfix y listo. Pero si hacemos esto en el anterior ejemplo el compilador nos mostrará un mensaje de error ya que esta definición ya existe: la del operador de negación lógica.

Implementando un Operador Infix

La implementación de los operadores Infix es igual de sencilla. Recordemos que estos operadores a diferencia de los anteriores vistos (unarios) son aquellos (binarios) donde interactuan dos operandos, como los operadores de suma (+), resta (-), multiplicación (*), etc.

Para la demostración siguiente veremos un ejemplo donde creamos dos operadores personalizados. El primero (¿%) nos permitirá obtener el x por ciento de cierta cantidad y el segundo (%?) pues la operación inversa: dado un segmento de un total obtendremos el por ciento correspondiente. Pasemos al código:

…la salida en pantalla sería:

En este código hemos adoptado un enfoque genérico que nos permite usar nuestros dos operadores con los tipos numéricos Int, Double y Float. Hemos comenzado por crear un protocolo donde especificamos estos operadores en ciertas combinaciones que necesitaremos usar. Seguido a esto extendemos los tipos antes mencionados para que adopten nuestro protocolo. En el caso de Double y Float hemos tenido que especificar como se debe manejar la sobrecarga de los operadores de multiplicación y división para estos casos específicos. Luego de la línea 7 a la 20 es donde definimos nuestros dos operadores nuevos. Dentro de este segmento tenemos las línea 7 y 8 las cuales mantienen la declaración de intención donde comunicamos la posterior definición de un nuevo operador personalizado. Las diferencias con el ejemplo anterior (apartando el enfoque genérico) reside en que los operadores infix no necesitan especificar antes de la palabra clave func el tipo de operador y al mismo tiempo tenemos que especificar dentro de los paréntesis el otro operando con el cual estamos trabajando, el parámetro de la izquierda representa al operando izquierdo y de manera similar el parámetro de la derecha representa al operando derecho. La puesta en práctica de estos nuevos operadores se encuentran en las líneas 37 y 41.

Asignando Precedencia a un Operador Personalizado

A partir de Swift 3 han ocurrido varios cambios en la declaración de operadores y en el sistema de precedencia. Actualmente contamos con grupos de precedencia ya establecidos por defecto y mediante los cuales podremos controlar tanto la asociatividad como la precedencia. Estos grupos son los siguientes:

Grupo de PrecedenciaOperadoresAsociatividadPrioridad
AdditionPrecedence+, –izquierdamayor a RangeFormationPrecedence
MultiplicationPrecedence*, /, %izquierdamayor a AdditionPrecedence
ComparisonPrecedence<, <=, >, >=, ==, !=, ===, !== –mayor a LogicalConjunctionPrecedence
LogicalConjunctionPrecedence&&izquierdamayor a LogicalDisjunctionPrecedence
LogicalDisjunctionPrecedence||izquierdamayor a TernaryPrecedence
TernaryPrecedence?:derechamayor a AssignmentPrecedence
AssignmentPrecedence=, *=, /=, %=, +=, -=derecha
RangeFormationPrecedence..<, … – mayor a CastingPrecedence
CastingPrecedenceis, as, as?, as!mayor a NilCoalescingPrecedence
NilCoalescingPrecedence??derechamayor a ComparisonPrecedence
BitwiseShiftPrecedence<<, >> mayor a MultiplicationPrecedence
 DefaultPrecedenceOperador Personalizado –mayor a TernaryPrecedence

En esta tabla cabe aclarar dos cosas: la primera sería que la columna de Prioridad hace referencia a la Precedencia que tiene un grupo de precedencia y sus operadores miembros sobre un grupo determinado, la segunda es acerca de DefaultPrecedence que es el grupo al cual se asigna un nuevo operador cuando no se le ha especificado un grupo, ejemplo:

…estás dos líneas son equivalentes.

Para esta sección final veremos un ejemplo donde definiremos un nuevo operador infix (^^) que nos servirá para calcular potencia donde evaluaremos el primer operando elevado al segundo. Como parte del aprendizaje modificaremos la precedencia que tendría naturalmente o matemáticamente la operación de potencia sobre la de suma, tendremos un resultado incorrecto (fácilmente corregible si has seguido este tutorial) pero cumplirá con el objetivo deseado.

La declaración de un grupo de precedencia luce así:

Aquí tenemos la declaración de los grupos AdditionPrecedence y MultiplicationPrecedence, de igual manera podemos declarar el nuestro, que en este caso y siguiendo el formato sería:

Claramente vemos como establecemos su asociatividad a la izquierda y la precedencia como menor a la del grupo AdditionPrecedence. El uso de lowerThan está condicionado a que el grupo de precedencia (al cual estamos haciendo referencia) tiene que encontrarse en otro modulo, en este caso como AdditionPrecedence ya viene por defecto y evidentemente se encuentra declarado en otro modulo pues no hay problemas, pero si fuese otro grupo de precedencia definido por nosotros tendríamos que usar higherThan en uno de ellos.

El siguiente paso en nuestro ejemplo sería definir nuestro operador personalizado:

En este bloque la línea principal es la primera donde especificamos que este operador adoptará el grupo de precedencia PowerPrecedence. El resto de líneas ya las conocemos y advierto que para mantener el ejemplo simple no hemos adoptado un enfoque genérico esta vez, es decir que el operador personalizado solamente funcionará con valores Int.

Si añadiéramos la siguiente línea:

…la salida sería:

…ya que el cálculo ha sido 5 ^^ (2 + 3) en lugar de (5 ^^ 2) + 3 que es igual a 28.

¿Cómo pudiéramos corregir el ejemplo anterior para que nos entregue el resultado correcto?

El código de ejemplo lo pueden encontrar en un proyecto Playground alojado en la cuenta personal de @josuevhn en GitHub, específicamente en el repositorio asociado a este artículo.

Espero que todo cuanto se ha dicho aquí, de una forma u otra le haya servido de aprendizaje, de referencia, que haya valido su preciado tiempo.

Este artículo, al igual que el resto, será revisado con cierta frecuencia en pos de mantener un contenido de calidad y actualizado.

Cualquier sugerencia, ya sea errores a corregir, información o ejemplos a añadir será, más que bienvenida, necesaria!

Acerca de Josué V. Herrera

Ciudadano del mundo. Me gustan mucho los animales y la naturaleza, la literatura, la música y el cine, aficionado a la aviación y a todo lo que es tecnología y ciencia. Desde el 2005 me dedico de manera profesional a la informática, trabajando principalmente como administrador de redes y sistemas aunque también he fungido como programador, especializándome en Linux y en C++ / Qt. Soy Experto en Administración y Seguridad de Redes por la Universidad Tecnológica Nacional FRVM de Argentina. Desde el 2014 me dedico al estudio de Swift y el entorno que lo rodea, ya sea desde el lado de Apple como del Open Source.

Ver Todos