Introducció

En aquest tema, explorarem dos conceptes avançats de Scala: les macros i la reflexió. Aquests conceptes permeten als programadors escriure codi més dinàmic i flexible, i són especialment útils en situacions on es necessita metaprogramació.

Macros

Què són les Macros?

Les macros en Scala permeten generar codi durant la compilació. Això significa que pots escriure codi que escriu codi, proporcionant una manera poderosa de crear abstraccions i optimitzacions.

Tipus de Macros

  1. Macros Definides per l'Usuari: Permeten als usuaris definir les seves pròpies macros.
  2. Macros Anotades: Utilitzen anotacions per modificar el comportament del codi.

Exemple de Macro

A continuació, es mostra un exemple senzill d'una macro que imprimeix el tipus d'una expressió:

import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context

object Macros {
  def printType[T](expr: T): Unit = macro printTypeImpl[T]

  def printTypeImpl[T: c.WeakTypeTag](c: Context)(expr: c.Expr[T]): c.Expr[Unit] = {
    import c.universe._
    val tpe = weakTypeOf[T]
    c.Expr[Unit](q"""println("Type: " + $tpe)""")
  }
}

Explicació del Codi

  1. Importacions Necessàries: Importem les llibreries necessàries per treballar amb macros.
  2. Definició de la Macro: Definim la funció printType que serà la nostra macro.
  3. Implementació de la Macro: La funció printTypeImpl és la implementació de la macro. Utilitza el context de macros (c: Context) per accedir a la informació del tipus de l'expressió.

Exercici Pràctic

Exercici: Escriu una macro que compti el nombre de caràcters en una cadena literal.

Solució:

import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context

object StringMacros {
  def countChars(str: String): Int = macro countCharsImpl

  def countCharsImpl(c: Context)(str: c.Expr[String]): c.Expr[Int] = {
    import c.universe._
    str.tree match {
      case Literal(Constant(s: String)) =>
        val length = s.length
        c.Expr[Int](q"$length")
      case _ =>
        c.abort(c.enclosingPosition, "Expected a string literal")
    }
  }
}

Reflexió

Què és la Reflexió?

La reflexió és la capacitat d'un programa per inspeccionar i modificar la seva pròpia estructura i comportament en temps d'execució. En Scala, la reflexió es pot utilitzar per accedir a informació sobre classes, mètodes, camps, etc.

Exemple de Reflexió

A continuació, es mostra un exemple senzill d'ús de la reflexió per obtenir els noms dels mètodes d'una classe:

import scala.reflect.runtime.universe._

object ReflectionExample {
  def printMethodNames[T: TypeTag](obj: T): Unit = {
    val tpe = typeOf[T]
    val methods = tpe.decls.collect {
      case m: MethodSymbol if m.isMethod => m.name.toString
    }
    methods.foreach(println)
  }
}

class SampleClass {
  def method1(): Unit = {}
  def method2(): Unit = {}
}

object Main extends App {
  val sample = new SampleClass
  ReflectionExample.printMethodNames(sample)
}

Explicació del Codi

  1. Importació de la Reflexió: Importem el paquet scala.reflect.runtime.universe.
  2. Funció printMethodNames: Aquesta funció utilitza la reflexió per obtenir els noms dels mètodes d'un objecte.
  3. Obtenció del Tipus: Utilitzem typeOf[T] per obtenir el tipus de l'objecte.
  4. Recollida de Mètodes: Utilitzem tpe.decls.collect per recollir els noms dels mètodes.
  5. Impressió dels Noms: Imprimim els noms dels mètodes.

Exercici Pràctic

Exercici: Escriu una funció que utilitzi la reflexió per obtenir els noms dels camps d'una classe.

Solució:

import scala.reflect.runtime.universe._

object FieldReflectionExample {
  def printFieldNames[T: TypeTag](obj: T): Unit = {
    val tpe = typeOf[T]
    val fields = tpe.decls.collect {
      case f: TermSymbol if f.isVal || f.isVar => f.name.toString
    }
    fields.foreach(println)
  }
}

class SampleClass {
  val field1: Int = 0
  var field2: String = ""
}

object Main extends App {
  val sample = new SampleClass
  FieldReflectionExample.printFieldNames(sample)
}

Conclusió

En aquest tema, hem explorat les macros i la reflexió en Scala. Les macros permeten generar codi durant la compilació, mentre que la reflexió permet inspeccionar i modificar el codi en temps d'execució. Aquests conceptes són poderosos i poden ser molt útils en situacions on es necessita metaprogramació.

Resum

  • Macros: Generen codi durant la compilació.
  • Reflexió: Permet inspeccionar i modificar el codi en temps d'execució.
  • Exemples Pràctics: Hem vist exemples de com utilitzar macros i reflexió en Scala.

Amb aquests coneixements, estàs preparat per explorar més profundament la metaprogramació en Scala i aplicar aquests conceptes en els teus projectes.

© Copyright 2024. Tots els drets reservats