1
votes

Existe-t-il un moyen simple et général d'encapsuler le code d'un objet scala avec du code à exécuter avant et après le code du corps de l'objet?

J'essaie de créer des fonctionnalités générales qui me permettent d'ajouter du code à exécuter avant et après le corps d'un objet Scala (étendu avec le trait App) de manière générique - quelque chose qui ressemble à BeforeAndAfterAll de Scalatest mais pour le code général

J'adorerais trouver un moyen de le faire via un trait, mais j'arrive à court - quelque chose comme

I am executed before
I am generic code in the body that's wrapped
I am executed after

et la sortie cible lors de l'exécution de l'application serait (la caractéristique clé étant la commande):

trait MyBeforeAndAfter {
    def before = println("I am executed before!")
    def after = println("I am executed after!")
}


object MyApp extends App with MyBeforeAndAfter {
    println("I am generic code in the body that's wrapped") 
}

Deux approches que j'ai envisagées mais je ne peux pas comprendre comment faire sont:

  1. Passer d'une manière ou d'une autre le code dans le corps de l'objet à une fonction qui s'occupe de l'encapsulation - mais je ne sais pas comment je peux accéder au bloc de code comme quelque chose que je pourrais passer à une fonction. J'ai regardé à la fois le trait App lui-même et BeforeAndAfterAll de Scalatest pour voir leurs approches. Le premier semble être contrôlé dans le compilateur. Ce dernier, je suis au cinquième niveau de création et je ne sais pas encore comment cela fonctionne.

  2. Divisez l'avant et l'après en traits séparés et mélangez-les, et bien que l'ordre des traits soit assez simple, je ne vois aucun moyen d'annoter qu'un trait devrait être exécuté après l'objet / la classe de base / cible .

Gods of Stack Overflow ... aide?

Modifier selon la question de Luis - pourquoi voulez-vous faire cela? Je travaillais un peu avec akka pour construire récemment et avec akka il y a beaucoup de passe-partout de configuration / démontage d'ActorSystem - saisir des choses dans les configurations, lever le système, etc. et je pensais à des moyens de nettoyer une partie de cette parties uniques de l'application principale plus visibles. J'ai rencontré des désirs similaires dans d'autres contextes également et j'ai continué à me demander s'il y avait un moyen général de vider le passe-partout de manière "propre" - ce qui signifie similaire au trait App (aucun remplacement requis, il suffit de mélanger et go), mais permet également la flexibilité d'exécuter du code avant et après le blocage.


6 commentaires

Il serait également bon d'expliquer pourquoi vous en avez besoin? BTW, l'approche simple serait de ne pas vraiment faire dans le corps de l'objet mais plutôt de fournir d'autres méthodes qui devraient être remplacées.


DelayedInit peut le faire, mais il est obsolète. Cela ne peut pas être trop difficile de faire une définition abstraite


Le code dans le corps d'un objet est exécuté au moment où / où l'objet est référencé pour la première fois. Les méthodes de l'objet (c'est-à-dire def s) sont définies mais pas invoquées. Un objet qui étend l'application a tout le code du corps enveloppé dans une méthode main () . Il semble que ce que vous voulez sera différent selon que l'objet étend ou non App .


@ LuisMiguelMejíaSuárez - vient de mettre à jour la question pour fournir plus d'informations.


@user a accepté re: DelayedInit - Je ne suis pas lu dans toute l'histoire de la raison pour laquelle il est obsolète et je me demandais s'il y avait une autre façon de faire cela qui me manquait


@six_minute_abs à moins que vous ne vouliez des maux de tête et que vous ayez passé plusieurs fois à déboguer et à faire en sorte que vos coéquipiers vous détestent. Allez avec la première approche que Dmytro a montrée. Je vais fournir une autre approche qui, je pense, sera plus adaptée à votre cas d'utilisation. Mais s'il vous plaît, n'allez pas avec des macros, pas d'initialisation retardée, la première n'est pas vraiment simple et la seconde peut provoquer d'étranges erreurs à l'exécution.


3 Réponses :


2
votes

Vous devez utiliser avant et après comme fonctions, puis les appeler dans la méthode execute . La méthode execute prend une fonction personnalisée comme entrée et s'exécute dans un certain ordre. Il s'agit d'une approche standard qui fonctionne indépendamment du fait que vous l'appeliez dans main ou dans une autre classe.

before
printing main
after

Résultats:

trait BeforeAfter {
      def execute(myfunction : () => Unit) = {
        before()
        myfunction()
        after()
      }
      def before() = println("before")
      def after() = println("after")
    }
object MyApp extends App with BeforeAfter{
      def myprinter() = println("printing main")
      execute(myprinter)
    
    }


1 commentaires

Merci pour la réponse. J'aime l'approche ici - j'ai accepté celui de Dmytro par mon commentaire sur son



3
votes

L'approche standard serait d'exécuter le code non pas dans le corps d'un objet mais dans une méthode

import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

class beforeAndAfter extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro BeforeAndAfterMacro.impl
}

object BeforeAndAfterMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._
    annottees match {
      case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil =>
        val parents1 = parents :+ tq"MyBeforeAndAfter"
        q"""$mods object $tname extends { ..$earlydefns } with ..$parents1 { $self =>
          def run(): Unit = {
            ..$body
          }
        }"""
      case _ =>
        c.abort(c.enclosingPosition, "annottee must be an object")
    }
  }
}

@beforeAndAfter
object MyApp {
  println("I am generic code in the body that's wrapped")
}

//Warning:scalac: object MyApp extends scala.AnyRef with MyBeforeAndAfter {
//  def <init>() = {
//    super.<init>();
//    ()
//  };
//  def run(): Unit = println("I am generic code in the body that\'s wrapped")
//}

Ensuite, si vous exécutez MyApp , il sera imprimé p>

I am executed before!
I am generic code in the body that's wrapped
I am executed after!

Si vous voulez vraiment exécuter du code dans le corps d'un objet, vous pouvez définir l'annotation de macro

trait MyBeforeAndAfter {
  def before() = println("I am executed before!")
  def after() = println("I am executed after!")
  def run(): Unit

  def main(args: Array[String]): Unit = {
    before()
    run()
    after()
  }
}


object MyApp extends MyBeforeAndAfter {
  override def run(): Unit = {
    println("I am generic code in the body that's wrapped")
  }
}


1 commentaires

Accepter l'approche macro - je pense que cela montre de manière concluante que oui - peut être fait via des substitutions ou des macros. Ces approches répondent ensemble à la question "dans une partie simple" (code pour facile) de ma question. Merci d'avoir répondu.



2
votes

Voici une approche similaire de @DmytroMitin
La seule différence est que cela vous permet de personnaliser et de réutiliser les méthodes before et foreach .

trait MyBeforeAndAfter extends BeforeAndAfter {
  override final def before(): Unit = {
    println("Setup")
  }
  
  override final def after(): Unit = {
    println("Clean up")
  }
}

object MyApp extends Program with MyBeforeAndAfter {
  override final def run(args: List[String]): Unit = {
    println("Program")
  }
}

Que vous pouvez utiliser comme ceci: p>

trait BeforeAndAfter {
  def before(): Unit
  def after(): Unit
}

trait Program { self: BeforeAndAfter =>
  def run(args: List[String]): Unit
  
  final def main(args: Array[String]): Unit = {
    self.before()
    run(args.toList)
    self.after()
  }
}

Vous pouvez le voir s'exécuter ici a >.


0 commentaires