En lenguaje Kotlin tenemos la posibilidad de definir una clase la cual funciona como un función; es decir, una vez creado el objeto de la clase, operamos con la instancia como si fuera una función. En la presente entrada, Objetos invocables como funciones en lenguaje Kotlin, mostraré unos ejemplos básicos de uso de esta características de clase.
Los objetos invocables en Kotlin me traen como recuerdo las first class en Scala. Las first class son funciones que se definen como una variable, un argumento de función, o bien, el resultado de una función. El compilador de Scala, realiza la transformación de la función a una clase transparente al desarrollador. Un ejemplo de first class en Scala es el siguiente:
val multiplyBy2 = (elem: Int) => (elem * 2)
Los ejemplos de objetos invocados que presento en la entrada son tres:
- Instanciación de una clase invocable básica.
- Instanciación de una clase invovable a partir de un interface.
- Definición de un predicado en una clase invocable.
Una clase invocable es aquella clase que define una función específica defina en un método con nombre invoke el cual está definida como un operador. La clase define un constructor y, para la invocación de la función invoke, no es necesario especificar el nombre de la función.
Instancia de una clase invocable básica
En el siguiente ejercicio presento un ejemplo básico. Defino una clase Greeter con un único método invoke que recibe un parámetro; la funcionalidad del método, es la escritura por pantalla del atributo de la clase y el parámetro. El snippet del código es el siguiente:
class Greeter(val greeting: String){ operator fun invoke(name: String){ println("Greeting=${greeting} Name=${name}") } } val obj1 = Greeter("Test1") obj1("ParamInvoke")
La instancia de la clase Greeter se realiza como cualquier clase típica; pero, la invocación del método, no se especifica sino que se emplea la referencia de la instancia con los parámetros requeridos en la firma del método invoke.
La salida por consola es la siguiente:
Greeting=Test1 Name=ParamInvoke
Instanciación de una clase invovable a partir de un interface
Incrementando el nivel de dificultad, presento una clase SpecialFunction con su método invoke. El método invoke tiene una definición especial porque se utiliza un interface donde se definen los tipos de entrada y de salida de forma genérica; en concreto, se definen dos parámetros de entrada: p1 de tipo X y p2 de tipo Y; y, por último, se define un tipo de salida de tipo Z. El snippet del código es el siguiente:
interface MyFuntion2<in X, in Y, out Z>{ operator fun invoke(p1:X, p2: Y): Z } class SpecialFunction(): MyFuntion2<Int, Int, String>{ override fun invoke(p1: Int, p2: Int): String { return StringBuilder() .append(p1) .append("+") .append(p2) .append("=") .append((p1+p2).toString()) .toString() } } val objectFunctionSum = SpecialFunction() println("Suma=>${objectFunctionSum(p1 =2,p2=3)}")
De la misma manera que el anterior caso, se realiza la instancia de la clase y, con ésta, realizamos la invocación del método invoke con los parámetros requeridos en su firma.
La salida por consola es la siguiente:
Suma=>2+3=5
Definición de un predicado en una clase invocable
El último caso que presento es una clase que representa un predicado semántico dentro de un contexto funcional. El escenario es el siguiente: dado un sistema que trabaja con proyectos en los cuales se presentan problemas o incidencias representadas por la entidad Issue; la entidad Issue está compuesta por los siguientes campos: un identificador, un nombre de proyecto, un tipo, una prioridad y una descripción del problema. La clase pretende definir lo siguiente: para un proyecto determinado, se quiere determinar si una entidad Issue es de tipo project y si es importante o no. Un elemento Issue es importante si es de tipo tiene valor «BUG» y su prioridad es Critical. El snippet del código es el siguiente:
data class Issue( val id:String, val project: String, val type: String, val priority: String, val description: String ) class ImportantIssuesPredicative(val project: String): (Issue) -> Boolean{ override fun invoke(p1: Issue): Boolean { return p1.project == project && p1.isImportant() } private fun Issue.isImportant(): Boolean{ return this.type == "BUG" && this.priority == "Critical" } } val issue1 = Issue(id = "1", project = "project1", type = "PBI", priority = "Medium", description = "description1") val issue2 = Issue(id = "2", project = "project2", type = "PBI", priority = "High", description = "description1") val issue3 = Issue(id = "3", project = "project1", type = "BUG", priority = "Critical", description = "description1") val listIssues = listOf(issue1, issue2, issue3) val predicate = ImportantIssuesPredicative("project1") val result = listIssues.filter(predicate) println("Result->${result}")
La definición y la instancia del predicado se realiza de la misma forma que en los ejemplos anteriores, residiendo la diferencia en la funcionalidad que se añade en el mótodo invoke; en ella, se utiliza una extensión de la clase String definida en la misma clase.
La salida por consola es la siguiente:
Result->[Issue(id=3, project=project1, type=BUG, priority=Critical, description=description1)]
Para finalizar, destacar el parecido con las funciones first class de Scala y la verbosidad de código Kotlin en comparación a Scala.
Al lector interesado, puede acceder al código completo del ejemplo en el siguiente enlace.