web-dev-qa-db-fra.com

Caractéristiques cachées de Scala

Quelles sont les fonctionnalités cachées de Scala que chaque développeur Scala devrait être au courant?)?

Une fonctionnalité cachée par réponse, s'il vous plaît.

149
Krzysiek Goj

D'accord, je devais en ajouter un de plus. Chaque objet Regex dans Scala a un extracteur (voir la réponse de oxbox_lakes ci-dessus) qui vous donne accès aux groupes de correspondance. Vous pouvez ainsi faire ce qui suit:

// Regex to split a date in the format Y/M/D.
val regex = "(\\d+)/(\\d+)/(\\d+)".r
val regex(year, month, day) = "2010/1/13"

La deuxième ligne semble confuse si vous n’êtes pas habitué à utiliser la correspondance de modèles et les extracteurs. Chaque fois que vous définissez un val ou var, ce qui vient après le mot-clé n'est pas simplement un identifiant mais plutôt un motif. C'est pourquoi cela fonctionne:

val (a, b, c) = (1, 3.14159, "Hello, world")

L'expression de droite crée un Tuple3[Int, Double, String] qui peut correspondre au motif (a, b, c).

La plupart du temps, vos modèles utilisent des extracteurs appartenant à des objets singleton. Par exemple, si vous écrivez un motif comme

Some(value)

alors vous appelez implicitement l'extracteur Some.unapply.

Mais vous pouvez aussi utiliser des instances de classe dans les patterns, et c'est ce qui se passe ici. Val regex est une instance de Regex, et lorsque vous l'utilisez dans un motif, vous appelez implicitement regex.unapplySeq (unapply versus unapplySeq dépasse le cadre de cette réponse), qui extrait les groupes de correspondance dans un Seq[String], dont les éléments sont affectés dans l’ordre aux variables année, mois et jour.

85
Willis Blackburn

Définitions de type structurelles - c’est-à-dire un type décrit par les méthodes qu’il prend en charge. Par exemple:

object Closer {
    def using(closeable: { def close(): Unit }, f: => Unit) {
      try { 
        f
      } finally { closeable.close }
    }
}

Notez que le type du paramètre closeable n’est pas défini autrement qu’il a une méthode close.

51
oxbow_lakes

Polymorphisme du constructeur du type (a.k.a types de type supérieur)

Sans cette fonctionnalité, vous pouvez, par exemple, exprimer l'idée de mapper une fonction sur une liste pour renvoyer une autre liste ou de mapper une fonction sur un arbre pour renvoyer un autre arbre. Mais vous ne pouvez pas exprimer cette idée en général sans types plus élevés.

Avec les types supérieurs, vous pouvez capturer l’idée de n’importe quel type paramétré avec un autre type. Un constructeur de type qui prend un paramètre est dit de type (*->*). Par exemple, List. Un constructeur de type qui retourne un autre constructeur de type est dit de type (*->*->*). Par exemple, Function1. Mais dans Scala, nous avons des types plus élevés , de sorte que nous pouvons avoir des constructeurs de types paramétrés avec d'autres constructeurs de types. Donc, ils sont du genre ((*->*)->*).

Par exemple:

trait Functor[F[_]] {
  def fmap[A, B](f: A => B, fa: F[A]): F[B]
}

Maintenant, si vous avez un Functor[List], Vous pouvez mapper des listes. Si vous avez un Functor[Tree], Vous pouvez cartographier des arbres. Mais plus important encore, si vous avez Functor[A] pour tout A de type (*->*), vous pouvez mapper une fonction sur A.

45
Apocalisp

Extractors qui vous permettent de remplacer if-elseif-else code de style avec des motifs. Je sais que ce ne sont pas exactement cachés mais j'utilise Scala depuis quelques mois sans vraiment en comprendre le pouvoir. Depuis (longtemps) exemple je peux remplacer:

val code: String = ...
val ps: ProductService = ...
var p: Product = null
if (code.endsWith("=")) {
  p = ps.findCash(code.substring(0, 3)) //e.g. USD=, GBP= etc
}
else if (code.endsWith(".FWD")) {
  //e.g. GBP20090625.FWD
  p = ps.findForward(code.substring(0,3), code.substring(3, 9))
}
else {
  p = ps.lookupProductByRic(code)
}

Avec cela, ce qui est beaucoup plus clair à mon avis

implicit val ps: ProductService = ...
val p = code match {
  case SyntheticCodes.Cash(c) => c
  case SyntheticCodes.Forward(f) => f
  case _ => ps.lookupProductByRic(code)
}

Je dois faire un peu de travail en arrière-plan ...

object SyntheticCodes {
  // Synthetic Code for a CashProduct
  object Cash extends (CashProduct => String) {
    def apply(p: CashProduct) = p.currency.name + "="

    //EXTRACTOR
    def unapply(s: String)(implicit ps: ProductService): Option[CashProduct] = {
      if (s.endsWith("=") 
        Some(ps.findCash(s.substring(0,3))) 
      else None
    }
  }
  //Synthetic Code for a ForwardProduct
  object Forward extends (ForwardProduct => String) {
    def apply(p: ForwardProduct) = p.currency.name + p.date.toString + ".FWD"

    //EXTRACTOR
    def unapply(s: String)(implicit ps: ProductService): Option[ForwardProduct] = {
      if (s.endsWith(".FWD") 
        Some(ps.findForward(s.substring(0,3), s.substring(3, 9)) 
      else None
    }
  }

Mais le jeu de loi en vaut la peine car il sépare un élément de la logique métier en un lieu sensible. Je peux mettre en œuvre mon Product.getCode méthodes comme suit ..

class CashProduct {
  def getCode = SyntheticCodes.Cash(this)
}

class ForwardProduct {
  def getCode = SyntheticCodes.Forward(this)     
}
39
oxbow_lakes

Manifestes qui sont une sorte de moyen d'obtenir les informations de type à l'exécution, comme si Scala avait des types réifiés.

35
oxbow_lakes

Les classes de cas se mélangent automatiquement à la caractéristique Produit, fournissant un accès indexé et non typé aux champs sans aucune réflexion:

case class Person(name: String, age: Int)

val p = Person("Aaron", 28)
val name = p.productElement(0) // name = "Aaron": Any
val age = p.productElement(1) // age = 28: Any
val fields = p.productIterator.toList // fields = List[Any]("Aaron", 28)

Cette fonctionnalité fournit également un moyen simplifié de modifier le résultat de la méthode toString:

case class Person(name: String, age: Int) {
   override def productPrefix = "person: "
}

// prints "person: (Aaron,28)" instead of "Person(Aaron, 28)"
println(Person("Aaron", 28)) 
35
Aaron Novstrup

Dans scala 2.8), vous pouvez utiliser des méthodes tail-récursives en utilisant le package scala.util.control.TailCalls (en fait, il s'agit de trampolining).

Un exemple:

def u(n:Int):TailRec[Int] = {
  if (n==0) done(1)
  else tailcall(v(n/2))
}
def v(n:Int):TailRec[Int] = {
  if (n==0) done(5)
  else tailcall(u(n-1))
}
val l=for(n<-0 to 5) yield (n,u(n).result,v(n).result)
println(l)
35
Aymen

Ce n'est pas exactement caché, mais c'est certainement une fonctionnalité sous-annoncée: scalac -Xprint.

À titre d’illustration, considérez la source suivante:

class A { "xx".r }

La compilation avec scalac -Xprint: typer génère:

package <empty> {
  class A extends Java.lang.Object with ScalaObject {
    def this(): A = {
      A.super.this();
      ()
    };
    scala.this.Predef.augmentString("xx").r
  }
}

Notez que scala.this.Predef.augmentString("xx").r, qui est une application du implicit def augmentString Présent dans Predef.scala.

scalac -Xprint: <phase> imprimera l'arbre de syntaxe après une phase de compilation. Pour voir les phases disponibles, utilisez scalac -Xshow-phases.

C'est un excellent moyen d'apprendre ce qui se passe dans les coulisses.

Essayer avec

case class X(a:Int,b:String)

en utilisant la phase dactylographe pour vraiment sentir à quel point il est utile.

33
pedrofurla

Vous pouvez définir vos propres structures de contrôle. Ce ne sont que des fonctions, des objets et du sucre syntaxique, mais ils ressemblent et se comportent comme des choses réelles.

Par exemple, le code suivant définit dont {...} unless (cond) et dont {...} until (cond):

def dont(code: => Unit) = new DontCommand(code)

class DontCommand(code: => Unit) {
  def unless(condition: => Boolean) =
    if (condition) code

  def until(condition: => Boolean) = {
    while (!condition) {}
    code
  }
}

Maintenant, vous pouvez faire ce qui suit:

/* This will only get executed if the condition is true */
dont {
  println("Yep, 2 really is greater than 1.")
} unless (2 > 1) 

/* Just a helper function */
var number = 0;
def nextNumber() = {
  number += 1
  println(number)
  number
}

/* This will not be printed until the condition is met. */
dont {
  println("Done counting to 5!")
} until (nextNumber() == 5) 
30
Aleksander Kmetec

@switch annotation dans Scala 2.8:

Une annotation à appliquer à une expression de correspondance. S'il est présent, le compilateur vérifiera que la correspondance a été compilée dans un commutateur de table ou un commutateur de recherche, et émettra une erreur s'il compile à la place une série d'expressions conditionnelles.

Exemple:

scala> val n = 3
n: Int = 3

scala> import annotation.switch
import annotation.switch

scala> val s = (n: @switch) match {
     |   case 3 => "Three"
     |   case _ => "NoThree"
     | }
<console>:6: error: could not emit switch for @switch annotated match
       val s = (n: @switch) match {
26
missingfaktor

Je ne sais pas si c'est vraiment caché, mais je trouve ça plutôt sympa.

Les constructeurs de types prenant 2 paramètres de type peuvent être écrits en notation infixe

object Main {                                                                   
  class FooBar[A, B]

  def main(args: Array[String]): Unit = {
    var x: FooBar[Int, BigInt] = null
    var y: Int FooBar BigInt   = null
  }
}
26
raichoo

in scala 2.8, vous pouvez ajouter @specialized à vos classes/méthodes génériques. Ceci créera des versions spéciales de la classe pour les types primitifs (extension de AnyVal) et permettra d'économiser le coût de la boxe/du déballage inutile. : class Foo[@specialized T]...

Vous pouvez sélectionner un sous-ensemble de AnyVals: class Foo[@specialized(Int,Boolean) T]...

24
Aymen

Scala 2.8 a introduit les arguments par défaut et nommés, qui ont rendu possible l’ajout d’une nouvelle méthode "copy" que Scala ajoute aux classes de cas. Si vous définissez ceci:

case class Foo(a: Int, b: Int, c: Int, ... z:Int)

et vous voulez créer un nouveau Foo qui ressemble à un Foo existant, mais avec une valeur "n" différente, alors vous pouvez simplement dire:

foo.copy(n = 3)
24
Willis Blackburn

Étendre la langue. J'ai toujours voulu faire quelque chose comme ceci dans Java (ne pouvait pas). Mais dans Scala je peux avoir:

  def timed[T](thunk: => T) = {
    val t1 = System.nanoTime
    val ret = thunk
    val time = System.nanoTime - t1
    println("Executed in: " + time/1000000.0 + " millisec")
    ret
  }

et ensuite écrire:

val numbers = List(12, 42, 3, 11, 6, 3, 77, 44)
val sorted = timed {   // "timed" is a new "keyword"!
  numbers.sortWith(_<_)
}
println(sorted)

et obtenir

Executed in: 6.410311 millisec
List(3, 3, 6, 11, 12, 42, 44, 77)
23
Adrian

Vous pouvez désigner un paramètre appel par nom (EDITED: il s'agit d'un paramètre différent d'un paramètre fainéant!) Et il ne sera pas évalué avant d'être utilisé par la fonction (EDIT: en fait, il sera réévalué à chaque fois. utilisé). Voir this faq pour plus de détails

class Bar(i:Int) {
    println("constructing bar " + i)
    override def toString():String = {
        "bar with value: " + i
    }
}

// NOTE the => in the method declaration.  It indicates a lazy paramter
def foo(x: => Bar) = {
    println("foo called")
    println("bar: " + x)
}


foo(new Bar(22))

/*
prints the following:
foo called
constructing bar 22
bar with value: 22
*/
23
agilefall

Vous pouvez utiliser locally pour introduire un bloc local sans causer de problèmes d'inférence point-virgule.

tilisation:

scala> case class Dog(name: String) {
     |   def bark() {
     |     println("Bow Vow")
     |   }
     | }
defined class Dog

scala> val d = Dog("Barnie")
d: Dog = Dog(Barnie)

scala> locally {
     |   import d._
     |   bark()
     |   bark()
     | }
Bow Vow
Bow Vow

locally est défini dans "Predef.scala" comme:

@inline def locally[T](x: T): T = x

En ligne, cela n'impose pas de frais généraux supplémentaires.

20
missingfaktor

Vous pouvez composer des types de structure avec le mot clé 'with'

object Main {
  type A = {def foo: Unit}
  type B = {def bar: Unit}

  type C = A with B

  class myA {
    def foo: Unit = println("myA.foo")
  }


  class myB {
    def bar: Unit = println("myB.bar")
  }
  class myC extends myB {
    def foo: Unit = println("myC.foo")
  }

  def main(args: Array[String]): Unit = { 
    val a: A = new myA 
    a.foo
    val b: C = new myC 
    b.bar
    b.foo
  }
}
17
raichoo

syntaxe d'espace réservé pour les fonctions anonymes

De la Scala Spécification de la langue:

SimpleExpr1 ::= '_'

Une expression (de catégorie syntaxique Expr) peut contenir des caractères de soulignement incorporés _ aux endroits où les identifiants sont légaux. Une telle expression représente une fonction anonyme où les occurrences suivantes de traits de soulignement désignent des paramètres successifs.

De changements de langue Scala :

_ + 1                  x => x + 1
_ * _                  (x1, x2) => x1 * x2
(_: Int) * 2           (x: Int) => x * 2
if (_) x else y        z => if (z) x else y
_.map(f)               x => x.map(f)
_.map(_ + 1)           x => x.map(y => y + 1)

En utilisant ceci, vous pourriez faire quelque chose comme:

def filesEnding(query: String) =
  filesMatching(_.endsWith(query))
17
Eugene Yokota

Initialisation:

trait AbstractT2 {
  println("In AbstractT2:")
  val value: Int
  val inverse = 1.0/value
  println("AbstractT2: value = "+value+", inverse = "+inverse)
}

val c2c = new {
  // Only initializations are allowed in pre-init. blocks.
  // println("In c2c:")
  val value = 10
} with AbstractT2

println("c2c.value = "+c2c.value+", inverse = "+c2c.inverse)

Sortie:

In AbstractT2:  
AbstractT2: value = 10, inverse = 0.1  
c2c.value = 10, inverse = 0.1

Nous instancions une classe interne anonyme, en initialisant le champ value du bloc, avant le with AbstractT2 clause. Cela garantit que value est initialisé avant le corps de AbstractT2 est exécuté, comme indiqué lorsque vous exécutez le script.

17
missingfaktor

Définitions implicites, en particulier les conversions.

Par exemple, supposons une fonction qui formatera une chaîne d’entrée pour s’adapter à une taille, en remplaçant son milieu par "...":

def sizeBoundedString(s: String, n: Int): String = {
  if (n < 5 && n < s.length) throw new IllegalArgumentException
  if (s.length > n) {
    val trailLength = ((n - 3) / 2) min 3
    val headLength = n - 3 - trailLength
    s.substring(0, headLength)+"..."+s.substring(s.length - trailLength, s.length)
  } else s
}

Vous pouvez l'utiliser avec n'importe quelle chaîne et, bien sûr, utiliser la méthode toString pour convertir n'importe quoi. Mais vous pouvez aussi l'écrire comme ceci:

def sizeBoundedString[T](s: T, n: Int)(implicit toStr: T => String): String = {
  if (n < 5 && n < s.length) throw new IllegalArgumentException
  if (s.length > n) {
    val trailLength = ((n - 3) / 2) min 3
    val headLength = n - 3 - trailLength
    s.substring(0, headLength)+"..."+s.substring(s.length - trailLength, s.length)
  } else s
}

Et ensuite, vous pourriez passer des classes d'autres types en procédant comme suit:

implicit def double2String(d: Double) = d.toString

Vous pouvez maintenant appeler cette fonction en passant un double:

sizeBoundedString(12345.12345D, 8)

Le dernier argument est implicite et est passé automatiquement à cause de la déclaration implicite de. De plus, "s" est traité comme une chaîne dans sizeBoundedString car il existe une conversion implicite de celle-ci en chaîne.

Les implications de ce type sont mieux définies pour les types rares afin d'éviter les conversions inattendues. Vous pouvez également explicitement passer une conversion, qui sera toujours utilisée implicitement dans sizeBoundedString:

sizeBoundedString(1234567890L, 8)((l : Long) => l.toString)

Vous pouvez également avoir plusieurs arguments implicites, mais vous devez ensuite les transmettre tous ou ne les transmettre aucun. Il existe également une syntaxe de raccourci pour les conversions implicites:

def sizeBoundedString[T <% String](s: T, n: Int): String = {
  if (n < 5 && n < s.length) throw new IllegalArgumentException
  if (s.length > n) {
    val trailLength = ((n - 3) / 2) min 3
    val headLength = n - 3 - trailLength
    s.substring(0, headLength)+"..."+s.substring(s.length - trailLength, s.length)
  } else s
}

Ceci est utilisé exactement de la même manière.

Les implications peuvent avoir n'importe quelle valeur. Ils peuvent être utilisés, par exemple, pour masquer les informations de la bibliothèque. Prenons l'exemple suivant, par exemple:

case class Daemon(name: String) {
  def log(msg: String) = println(name+": "+msg)
}

object DefaultDaemon extends Daemon("Default")

trait Logger {
  private var logd: Option[Daemon] = None
  implicit def daemon: Daemon = logd getOrElse DefaultDaemon

  def logTo(daemon: Daemon) = 
    if (logd == None) logd = Some(daemon) 
    else throw new IllegalArgumentException

  def log(msg: String)(implicit daemon: Daemon) = daemon.log(msg)
}

class X extends Logger {
  logTo(Daemon("X Daemon"))

  def f = {
    log("f called")
    println("Stuff")
  }

  def g = {
    log("g called")(DefaultDaemon)
  }
}

class Y extends Logger {
  def f = {
    log("f called")
    println("Stuff")
  }
}

Dans cet exemple, l'appel de "f" dans un objet Y enverra le journal au démon par défaut et, sur une instance de X, au démon X du démon. Mais appeler g sur une instance de X enverra le journal au DefaultDaemon explicitement indiqué.

Bien que cet exemple simple puisse être réécrit avec surcharge et état privé, les implications ne nécessitent pas d'état privé et peuvent être mises en contexte avec les importations.

16
Daniel C. Sobral

Peut-être pas trop caché, mais je pense que cela est utile:

@scala.reflect.BeanProperty
var firstName:String = _

Cela générera automatiquement un getter et un setter pour le champ qui correspond à la convention de bean.

Description supplémentaire sur developerworks

13
agilefall

Arguments implicites dans les fermetures.

Un argument de fonction peut être marqué comme implicite, comme pour les méthodes. Dans le cadre du corps de la fonction, le paramètre implicite est visible et éligible pour une résolution implicite:

trait Foo { def bar }

trait Base {
  def callBar(implicit foo: Foo) = foo.bar
}

object Test extends Base {
  val f: Foo => Unit = { implicit foo =>
    callBar
  }
  def test = f(new Foo {
    def bar = println("Hello")
  })
}
13
axel22

Construisez des structures de données infinies avec Streams: de Scala http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient

12
Aymen

Les types de résultats dépendent de la résolution implicite. Cela peut vous donner une forme d'envoi multiple:

scala> trait PerformFunc[A,B] { def perform(a : A) : B }
defined trait PerformFunc

scala> implicit val stringToInt = new PerformFunc[String,Int] {
  def perform(a : String)  = 5
}
stringToInt: Java.lang.Object with PerformFunc[String,Int] = $anon$1@13ccf137

scala> implicit val intToDouble = new PerformFunc[Int,Double] {
  def perform(a : Int) = 1.0
}
intToDouble: Java.lang.Object with PerformFunc[Int,Double] = $anon$1@74e551a4

scala> def foo[A, B](x : A)(implicit z : PerformFunc[A,B]) : B = z.perform(x)
foo: [A,B](x: A)(implicit z: PerformFunc[A,B])B

scala> foo("HAI")
res16: Int = 5

scala> foo(1)
res17: Double = 1.0
12
jsuereth

L'équivalent de Scala Java initialisateur à double accolade.

Scala vous permet de créer une sous-classe anonyme avec le corps de la classe (le constructeur) contenant des instructions pour initialiser l'instance de cette classe.

Ce modèle est très utile lors de la création d'interfaces utilisateur basées sur des composants (par exemple, Swing, Vaadin), car il permet de créer des composants d'interface utilisateur et de déclarer leurs propriétés de manière plus concise.

Voir http://spot.colorado.edu/~reids/papers/how-scala-experience-improved-our-Java-development-reid-2011.pdf pour plus d'informations.

Voici un exemple de création d'un bouton Vaadin:

val button = new Button("Click me"){
 setWidth("20px")
 setDescription("Click on this")
 setIcon(new ThemeResource("icons/ok.png"))
}
4
Guillaume Belrose

Exclusion des membres des instructions import

Supposons que vous souhaitiez utiliser un Logger contenant une méthode println et un printerr, mais que vous ne vouliez utiliser que celui-ci pour les messages d'erreur et conserver le bon vieux Predef.println pour la sortie standard. Vous pourriez faire ceci:

val logger = new Logger(...)
import logger.printerr

mais si logger contient également douze autres méthodes que vous souhaitez importer et utiliser, il devient peu pratique de les répertorier. Vous pourriez plutôt essayer:

import logger.{println => donotuseprintlnt, _}

mais cela "pollue" encore la liste des membres importés. Entrez le caractère générique über-puissant:

import logger.{println => _, _}

et cela fera juste la bonne chose ™.

3
Philippe

require méthode (définie dans Predef) permettant de définir des contraintes de fonction supplémentaires à vérifier lors de l'exécution. Imaginez que vous développiez un autre client Twitter et que vous deviez limiter la longueur du tweet à 140 symboles. De plus, vous ne pouvez pas publier de Tweet vide.

def post(Tweet: String) = {
  require(Tweet.length < 140 && Tweet.length > 0) 
  println(Tweet)
 }

Appeler maintenant avec un argument de longueur inappropriée provoquera une exception:

scala> post("that's ok")
that's ok

scala> post("")
Java.lang.IllegalArgumentException: requirement failed
    at scala.Predef$.require(Predef.scala:145)
    at .post(<console>:8)

scala> post("way to looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong Tweet") 
Java.lang.IllegalArgumentException: requirement failed
    at scala.Predef$.require(Predef.scala:145)
    at .post(<console>:8)

Vous pouvez écrire plusieurs exigences ou même ajouter une description à chacune:

def post(Tweet: String) = {
  require(Tweet.length > 0, "too short message")
  require(Tweet.length < 140, "too long message")
  println(Tweet)
}

Les exceptions sont maintenant verbeuses:

scala> post("")
Java.lang.IllegalArgumentException: requirement failed: too short message
    at scala.Predef$.require(Predef.scala:157)
    at .post(<console>:8)

Un autre exemple est ici .


Prime

Vous pouvez effectuer une action chaque fois que l'exigence échoue:

scala> var errorcount = 0
errorcount: Int = 0

def post(Tweet: String) = {
  require(Tweet.length > 0, {errorcount+=1})
  println(Tweet)
  }

scala> errorcount
res14: Int = 0

scala> post("")
Java.lang.IllegalArgumentException: requirement failed: ()
    at scala.Predef$.require(Predef.scala:157)
    at .post(<console>:9)
...

scala> errorcount
res16: Int = 1
2
om-nom-nom

Les traits avec les méthodes abstract override sont une fonctionnalité de Scala qui n'est pas aussi largement annoncé que beaucoup d'autres. Le but des méthodes avec le abstract override modificateur consiste à effectuer certaines opérations et à déléguer l'appel à super. Ces caractéristiques doivent ensuite être combinées avec des implémentations concrètes de leurs méthodes abstract override.

trait A {
  def a(s : String) : String
}

trait TimingA extends A {
  abstract override def a(s : String) = {
    val start = System.currentTimeMillis
    val result = super.a(s)
    val dur = System.currentTimeMillis-start
    println("Executed a in %s ms".format(dur))
    result
  }
}

trait ParameterPrintingA extends A {
  abstract override def a(s : String) = {
    println("Called a with s=%s".format(s))
    super.a(s)
  }
}

trait ImplementingA extends A {
  def a(s: String) = s.reverse
}

scala> val a = new ImplementingA with TimingA with ParameterPrintingA

scala> a.a("a lotta as")
Called a with s=a lotta as
Executed a in 0 ms
res4: String = sa attol a

Bien que mon exemple ne soit vraiment pas beaucoup plus qu'un AOP d'un homme pauvre, j'ai utilisé ces traits empilables beaucoup à mon goût pour construire Scala instances d'interprète avec des importations prédéfinies, des liaisons personnalisées et des chemins de classes. Les Traits empilables ont permis de créer ma fabrique suivant les lignes de new InterpreterFactory with JsonLibs with LuceneLibs et d'avoir ainsi des importations utiles et des variables de portée pour les scripts de l'utilisateur.

1
MxFr