Swift – Sobrecarga de Operadores

El día de hoy aprenderemos sobre la sobrecarga de operadores, veremos como funcionan los operadores y hasta crearemos uno.

La Sobrecarga de Operadores nos permite vincular un tipo con un operador en pos de un comportamiento determinado. Por ejemplo, los que hemos estudiado Java o C#, incluso los que conocemos Swift sabemos que esta es una expresión válida:

let someText = "Hola, esto es" + " un texto concatenado con" + " el operador + en lo que sería una sobrecarga de operadores" + " clásica."

print(someText)

la salida en pantalla sería:

Hola, esto es un texto concatenado con el operador + en lo que sería una sobrecarga de operadores clásica.

Como bien dice el texto es un ejemplo de sobrecarga de operadores clásica, y que está presente por defecto en la mayoría de los lenguajes.

Hemos usado el operador de suma para concatenar cadenas de texto, en lugar de realizar la operación aritmética con la cual siempre lo asociamos, dando como resultado una cadena final donde todos estos segmentos confluyen tal y como podemos comprobar en la salida en pantalla.

¿Qué sucede cuando queremos lograr un comportamiento similar con un tipo propio?

Pues esto no lo podemos lograr con el comportamiento por defecto de los operadores, sobre todo porque el compilador no sabe el significado que tiene para nosotros esta ecuación. El lenguaje Swift no puede prever ni interpretar al vuelo que áreas de nuestras clases o estructuras deben de ser sumadas, concatenadas, o procesadas de cierta manera.

Todos estos comportamientos tenemos que especificarlos nosotros.

Sobre todo esto hablaremos, haciendo uso de la flexibilidad que esto nos brinda  veremos como aplicar la sobrecarga de operadores a favor de tipos propios en pos de lograr los comportamientos deseados.

Una posible necesidad

Imaginemos que tenemos un tipo propio de nombre Product y que en este solamente almacenamos dos valores, el nombre del producto y el precio del mismo.

Ahora necesitamos, a partir de una lista de compras, sumar todos los precios para informar al cliente cuando debe pagar por el total de sus compras. Este problema no representa ninguna complejidad, un solución pudiera ser:

//: Playground - noun: a place where people can play

import Cocoa

struct Product : CustomStringConvertible {
    
    let nombre: String
    let precio: Double
    
    var description: String {
        
        return String("Producto: \(self.nombre.padding(toLength: 12, withPad: " ", startingAt: 0)) Precio: \(String(format: "%.2f", self.precio))$")
        
    }// description
    
} // Product

struct Discount : CustomStringConvertible {
    
    let porciento: Double

    func aplicarDescuento(product: Product) -> Double {
        
        let ahorro = (product.precio * porciento) / 100
        
        return ahorro

    } // aplicarDescuento
    
    var description: String {
        
        return String("Descuento: \(porciento)%")
        
    }// description
    
} // Discount

let leche = Product(nombre: "leche", precio: 0.50)
let pan = Product(nombre: "pan", precio: 0.75)
let queso = Product(nombre: "queso", precio: 3.25)
let pollo = Product(nombre: "pollo", precio: 4.00)
let morzilla = Product(nombre: "morcilla", precio: 2.00)
let tomate = Product(nombre: "tomate", precio: 0.10)

var comprasAPagar: Array<Product> = []

comprasAPagar.append(leche)
comprasAPagar.append(pan)
comprasAPagar.append(queso)
comprasAPagar.append(pollo)
comprasAPagar.append(morcilla)
comprasAPagar.append(tomate)

let descuento = Discount(porciento: 10)

var totalAPagar: Double = 0.0

print("Los productos facturados son:\n")

for product in comprasAPagar {
    
    let ahorro = descuento.aplicarDescuento(product: product)
    
    let precioFinal = product.precio - ahorro

    totalAPagar += precioFinal
    
    print("\(product)   \(descuento)   Ahorro: \(String(format: "%.3f", ahorro))$   Precio final: \(String(format: "%.3f", precioFinal))$")
    
} // for

print("\nEl total a pagar es de: \(String(format: "%.2f", totalAPagar))$")

la salida en pantalla sería:

Los productos facturados son:

Producto: leche        Precio: 0.50$   Descuento: 10.0%   Ahorro: 0.050$   Precio final: 0.450$
Producto: pan          Precio: 0.75$   Descuento: 10.0%   Ahorro: 0.075$   Precio final: 0.675$
Producto: queso        Precio: 3.25$   Descuento: 10.0%   Ahorro: 0.325$   Precio final: 2.925$
Producto: pollo        Precio: 4.00$   Descuento: 10.0%   Ahorro: 0.400$   Precio final: 3.600$
Producto: morcilla     Precio: 2.00$   Descuento: 10.0%   Ahorro: 0.200$   Precio final: 1.800$
Producto: tomate       Precio: 0.10$   Descuento: 10.0%   Ahorro: 0.010$   Precio final: 0.090$

El total a pagar es de: 9.54$

No me voy a detener mucho en este ejemplo, si tienen alguna duda me la dejan en los comentarios. Solamente haré referencia a las expresiones de la línea 24:

let ahorro = (product.precio * porciento) / 100

y la línea 64:

let precioFinal = product.precio - ahorro

En ambas líneas nos apoyamos en la propiedad constante precio de la estructura Product para así poder acceder a su valor, ya que si obviamos este punto y ejecutáramos algo como:

let precioFinal = product - ahorro

el compilador de Swift nos mostraría el siguiente error:

error: binary operator ‘-‘ cannot be applied to operands of type ‘Product’ and ‘Double’

en el cual se nos informa que el operador binario de resta (-) no puede ser usado cuando sus operandos son de tipo Product o Double, y esto no tiene nada que ver con Double, la razón es que el compilador no sabe como interactuar con Product en el caso de una suma, una resta o cualquier otra operación aritmética.

¿Qué deseamos lograr?

Con todo cuanto hemos visto hasta ahora en nuestra mente y a sabiendas de que no tenemos ningún error, pasemos a implementar una sobrecarga de operadores que nos permita simplificar un poco el código que hasta ahora tenemos. Haciendo esto nuestras instancias de tipo Product podrán ser usadas en operaciones aritméticas logrando expresiones como la siguiente:

totalAPagar = product - descuento

veamos como lograrlo, no sin antes una pequeña introducción técnica.

La sobrecarga de operadores en Swift

Lo primero que debemos de conocer son los siguientes términos que agrupan a los operadores:

  • Prefix: El operador unario aparecerá antes del operando (Ejemplo: +=, -=, !, ~…)
  • Infix: El operador binario aparecerá en el medio de los dos operandos (Ejemplo: +, -, *, &+, &-…)
  • Postfix: El operador unario aparecerá después del operando (Ejemplo: &, ?…)

Conociendo ya la utilidad de la sobrecarga de operadores y lo que nos permite lograr veamos como luce una sobrecarga del operador de multiplicación (*), un ejemplo sencillo:

func *(lhs: String, rhs: Int) -> String {
    
    var result = lhs

    for _ in 2...rhs {
        
        result = String("\(result) \(lhs)")
        
    } // for
    
    return result

} // operator overload (String * Int) -> String

Aquí podemos percatarnos una vez más sobre eso que comentan que en Swift todo (casi todo) es una función o se logra con una función. El nombre de esta función es el operador al que le estamos agregando una nueva interacción y dentro de los paréntesis tenemos a los dos operandos que interactúan con este operador binario.

Para los nombres de estos operandos hemos usado un término matemático (lhs y rhs) que usualmente se usa para hacer referencia a estos, en otras palabras viene siendo ya como una norma pero pudieran tener cualquier nombre (Left, Right…).

En el caso de la derecha el nombre lhs son las siglas en inglés para left-hand side y rhs para right-hand side, que en castellano se traduciría como lado izquierdo y lado derecho. A cada uno de estos se le especifica su tipo de dato, dejando claro al compilador que esta sobrecarga de operadores está enfocada en el lado izquierdo a un tipo String y en el derecho a un Int, para finalizar devolviendo un String.

Podemos resumir lo que acabamos de comentar en la siguiente tabla:

ÁreaOperando IzquierdoOperadorOperando Derecho
Infix2+3
Prefix+=5
PostfixisEmpty()!

Continuando con el ejemplo anterior donde sobrecargábamos al operador de multiplicación,  este código nos permite escribir la siguiente expresión:

let result = someString * 5

algo que no tendría sentido a no ser que nuestra intención sea multiplicar someString cinco veces, una necesidad bien especifica y rara cuya sobrecarga del operador de multiplicación tiene que ser provista por nosotros. El ejemplo completo:

func *(lhs: String, rhs: Int) -> String {
    
    var result = lhs

    for _ in 2...rhs {
        
        result = String("\(result) \(lhs)")
        
    } // for
    
    return result

} // operator overload (String * Int) -> String

let someString = "abc"

let result = someString * 5

print(result)

la salida en pantalla sería:

abc abc abc abc abc

La implementación

En este punto ya con todo mucho más claro, retomaremos el ejemplo del inicio e implementaremos una sobrecarga sobre nuestro tipo de dato Product.

Para esto comenzaremos afrontando el caso:

totalAPagar += product

el código asociado sería:

static func +=(lhs: inout Double, rhs: Product) {
        
    lhs = lhs + rhs.precio
        
}// Overloading operator += (Double, Product)

Comenzamos haciendo uso de la palabra clave static ya que este segmento de código se encuentra dentro de la clase Product. Luego continuamos declarando nuestro operando de la izquierda, que vendría a ser la variable totalAPagar la cual recibe el retorno de la expresión.

Como deben de haberse dado cuenta, este operando ha sido marcado como inout ya que no queremos trabajar con una copia de este, más bien con una referencia a su posición de memoria para poder almacenar el valor de retorno.

El otro operando (rhs) sería la instancia de Product que como podemos leer en la línea 3 hacemos uso de la propiedad precio que es donde se almacena el valor de este producto. Esta sobrecarga viene siendo como una especie de enmascaramiento de la operación que hacíamos previamente.

El otro caso que nos interesa es:

totalAPagar += (product - descuento)

y para que esta expresión sea válida necesitaríamos añadir a la clase Product el código:

static func -(lhs: Product, rhs: Discount) -> Double {
        
    let ahorro = (lhs.precio * rhs.porciento) / 100
        
    let precioFinal = lhs.precio - ahorro
        
    return precioFinal

} // Overloading operator - (Product, Discount)

Como podemos constatar el código que antes formaba parte del bucle for ahora se encuentra segmentado bajo la operación matemática de la resta siempre y cuando los operandos sean en el lado izquierdo una instancia de Product y en el derecho una instancia de Discount.

La estructura Product ahora luce así:

struct Product : CustomStringConvertible {
    
    let nombre: String
    let precio: Double

    var description: String {
        
        return String("Producto: \(self.nombre.padding(toLength: 12, withPad: " ", startingAt: 0)) Precio: \(String(format: "%.2f", self.precio))$")
        
    }// description
    
    static func +=(lhs: inout Double, rhs: Product) {
        
        lhs = lhs + rhs.precio
        
    }// Overloading operator += (Double, Product)
    
    static func -(lhs: Product, rhs: Discount) -> Double {
        
        let ahorro = (lhs.precio * rhs.porciento) / 100
        
        let precioFinal = lhs.precio - ahorro
        
        return precioFinal

    } // Overloading operator - (Product, Discount)
    
} // Product

mientras que el aspecto del área correspondiente al bucle for:

for product in comprasAPagar {
    
    //let ahorro = descuento.aplicarDescuento(product: product)
    
    //let precioFinal = product.precio - ahorro
    
    totalAPagar += (product - descuento)

    //totalAPagar += precioFinal
    
    //print("\(product)   \(descuento)   Ahorro: \(String(format: "%.3f", ahorro))$   Precio final: \(String(format: "%.3f", precioFinal))$")
    
    print("\(product)   \(descuento)")
    
} // for

Aquí podemos observar de que hay varias líneas comentadas y esto es debido a que los últimos cambios que hemos efectuado no evitan que la antigua versión del código siga funcionando. Ya queda del lado del programador cual de las dos versiones usar, cual de estas le resulta más legible y menos propensa a errores.

la nueva salida en pantalla sería:

Los productos facturados son:

Producto: leche        Precio: 0.50$   Descuento: 10.0%
Producto: pan          Precio: 0.75$   Descuento: 10.0%
Producto: queso        Precio: 3.25$   Descuento: 10.0%
Producto: pollo        Precio: 4.00$   Descuento: 10.0%
Producto: morcilla     Precio: 2.00$   Descuento: 10.0%
Producto: tomate       Precio: 0.10$   Descuento: 10.0%

El total a pagar es de: 9.54$

Operadores personalizados

Ahora adentrémonos en los operadores personalizados que podemos definir, veamos también como cambiar la precedencia de los mismos, todo esto a favor de lograr comportamientos aún más específicos.

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.

2 + 3 % 4 * 5

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:

2 + ((3 % 4) * 5)

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
1Ficha 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.

[anuncio_b30 id=2]

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:

let number = 9

let factorial = !number

print("\(number)! = \(factorial)")

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

prefix operator !

prefix func ! (number: Int) -> Int {
    
    var result: Int = 0;
    
    guard (number - 1) > 0 else {
        
        return 1
        
    } // guard
    
    result = number * (!(number - 1))
    
    return result
    
} // prefix operator !

Comenzamos declarando 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.

[anuncio_b30 id=3]

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:

protocol NumericType {
    
    var doubleValue: Double { get }

} // NumericType

infix operator ¿%
infix operator %?

func ¿% <T: NumericType>(percentage: T, ofTotal: Double) -> Double {
    
    return percentage.doubleValue * ofTotal / 100.0

} // infix operator ¿%

func %? <T: NumericType>(segment: T, ofTotal: Double) -> Double {
    
    return segment.doubleValue * 100 / ofTotal
    
} // infix operator %?

extension Double: NumericType {
    
    var doubleValue: Double { return self }
    
} // extension Double : NumericType

extension Int: NumericType {
    
    var doubleValue: Double { return Double(self) }
    
} // // extension Int : NumericType

let percentage: Double = 8
let price: Double = 45

let save = percentage ¿% price

print("\(percentage) % of \(price) $ = \(save) $")

print("\(save) $ of \(price) $ = \(save %? price) %")

…la salida en pantalla sería:

8.0 % of 45.0 $ = 3.6 $
3.6 $ of 45.0 $ = 8.0 %

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 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:

infix operator ^!^ : DefaultPrecedence
infix operator ^!^

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í:

precedencegroup AdditionPrecedence {
    associativity: left
    higherThan: RangeFormationPrecedence
} // AdditionPrecedence group

precedencegroup MultiplicationPrecedence {
    associativity: left
    higherThan: AdditionPrecedence
} // MultiplicationPrecedence group

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:

precedencegroup PowerPrecedence {
    associativity: left
    lowerThan: AdditionPrecedence
} // PowerPrecedence group

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:

infix operator ^^ : PowerPrecedence

func ^^ (base: Int, power: Int) -> Double {
    
    return pow(Double(base), Double(power))
    
} // operator ^^^

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:

print(5 ^^ 2 + 3)

la salida sería:

3125.0

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

Conclusiones

Ya en este punto conocemos todo cuanto se puede hacer con la sobrecarga de operadores y nos damos cuenta de que siempre hemos interactuado con ella.

Es evidente que detrás de cada lenguaje de programación hay un análisis léxico que interpreta las expresiones que escribimos, solamente con poner 5 + 5 no se genera de manera mágica el número 10, detrás de esta operación hay una sobrecarga del operador de suma (+) para el caso lhs (Int) y rhs (Int).

La expresión 5 + 5 no genera un 10 por arte de magia, es un caso lhs (Int) y rhs (Int), una sobrecarga por defecto del operador de suma (+).

Falta aún mucho por aprender en nuestro camino a convertirnos en iOS Developer. Suscríbete a nuestra lista de correo y síguenos en nuestras redes sociales. Mantente al tanto de todas nuestras publicaciones.

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 duda o sugerencia, ya sea errores a corregir o ejemplos a añadir, será más que bienvenida, necesaria!

Deja una respuesta

Su dirección de correo electrónico no será publicada.

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para mejorar su experiencia. Asumiremos que está de acuerdo con esto, pero puede optar por no participar si lo desea. Aceptar Leer Más

RECIBE CONTENIDO SIMILAR EN TU CORREO

RECIBE CONTENIDO SIMILAR EN TU CORREO

Suscríbete a nuestra lista de correo y mantente actualizado con las nuevas publicaciones.

Se ha suscrito correctamente!