Scala tiene una característica muy interesante: permite que el compilador encuentre ciertos valores o funcionalidades sin que tengamos que escribirlos explícitamente cada vez que los necesitamos.
Esta característica se conoce tradicionalmente como implicits (implícitos).
En Scala 3, los mecanismos principales para trabajar con implícitos son given y using. También existen otras herramientas relacionadas, como las conversiones automáticas mediante Conversion y las extensiones mediante extension.
Pero antes de hablar de palabras reservadas, vamos a entender el problema que intentan resolver.
Imaginemos que estamos desarrollando un programa que saluda a las personas.
Podríamos escribir una función como esta:
def saludar(nombre: String, idioma: String): String =
if idioma == "es"
then s"Hola, $nombre"
else s"Hello, $nombre"
Para saludar a una persona, debemos indicar su nombre y el idioma:
saludar("Ana", "es")
saludar("Juan", "en")
Funciona perfectamente.
Sin embargo, imaginemos que toda nuestra aplicación está en español. Cada vez que llamamos a saludar, debemos repetir "es".
¿Qué pasaría si pudiéramos indicarle al programa que, por defecto, el idioma es español y que la función lo utilice automáticamente?
En Scala 3 podemos hacerlo mediante un parámetro contextual.
Veamos una implementación:
given idioma: String = "es"
def saludar(nombre: String)(using idioma: String): String =
if idioma == "es"
then s"Hola, $nombre"
else s"Hello, $nombre"
Hay dos conceptos nuevos.
given: declarar un valor disponible para ser encontrado por el compilador.
given idioma: String = "es"
Estamos declarando un valor de tipo String que el compilador puede utilizar cuando necesite encontrar un valor de ese tipo en un contexto compatible.
using: declarar un parámetro que el compilador debe buscar.
def saludar(nombre: String)(using idioma: String): String =
...
La función necesita dos datos: el nombre y el idioma. El nombre se pasa normalmente, mientras que el idioma es un parámetro contextual.
Ahora podemos escribir:
saludar("Ana")
saludar("Juan")
El compilador encuentra el given de tipo String y lo utiliza para completar el parámetro using.
El resultado es:
Hola, Ana
Hola, Juan
¡No necesitamos escribir el idioma en cada llamada!
Es importante entender algo: el idioma no aparece mágicamente. Lo declaramos nosotros con given. Lo que hace Scala es encontrarlo y pasarlo automáticamente cuando corresponde.
¿Qué hace realmente el compilador?
Podemos pensar que el compilador completa la llamada a la función.
Nosotros escribimos:
saludar("Ana")
Conceptualmente, el compilador resuelve esa llamada como si hubiéramos escrito:
saludar("Ana")(using idioma)
No es necesario escribir esa segunda forma cuando el compilador puede encontrar el valor contextual adecuado.
Esta característica se conoce como contextual parameters, o parámetros contextuales.
El mecanismo permite separar dos responsabilidades:
- La función declara qué información necesita.
- El contexto proporciona esa información.
La función no necesita saber dónde se definió el idioma. Solo necesita recibir un String.
