Jakie są ukryte funkcje Scali, o których powinien wiedzieć każdy programista Scali?
Proszę o jedną ukrytą funkcję na odpowiedź.
Jakie są ukryte funkcje Scali, o których powinien wiedzieć każdy programista Scali?
Proszę o jedną ukrytą funkcję na odpowiedź.
Odpowiedzi:
OK, musiałem dodać jeszcze jednego. Każdy Regex
obiekt w Scali ma ekstraktor (patrz odpowiedź z oxbox_lakes powyżej), który daje dostęp do grup dopasowań. Możesz więc zrobić coś takiego:
// 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"
Druga linia wygląda myląco, jeśli nie jesteś przyzwyczajony do używania dopasowywania wzorców i ekstraktorów. Za każdym razem, gdy definiujesz val
lub var
, to, co pojawia się po słowie kluczowym, nie jest po prostu identyfikatorem, ale raczej wzorcem. Dlatego to działa:
val (a, b, c) = (1, 3.14159, "Hello, world")
Wyrażenie po prawej stronie tworzy, Tuple3[Int, Double, String]
które może pasować do wzorca (a, b, c)
.
Przez większość czasu wzorce używają ekstraktorów, które są członkami obiektów pojedynczych. Na przykład, jeśli napiszesz wzór, taki jak
Some(value)
to niejawnie wywołujesz ekstraktor Some.unapply
.
Ale możesz także używać instancji klas we wzorcach i to właśnie się tutaj dzieje. Val regex jest instancją Regex
, a kiedy używasz go we wzorcu, wywołujesz niejawnie regex.unapplySeq
(w unapply
przeciwieństwie do tego, że unapplySeq
jest poza zakresem tej odpowiedzi), co wyodrębnia grupy dopasowań do a Seq[String]
, których elementy są przypisane w celu zmienne rok, miesiąc i dzień.
Definicje typów strukturalnych - tj. Typ opisywany przez obsługiwane metody. Na przykład:
object Closer {
def using(closeable: { def close(): Unit }, f: => Unit) {
try {
f
} finally { closeable.close }
}
}
Zauważ, że typ parametru closeable
nie jest zdefiniowany inaczej niż ma close
metodę
Bez tej funkcji możesz na przykład wyrazić pomysł odwzorowania funkcji na liście w celu zwrócenia innej listy lub odwzorowania funkcji na drzewie w celu zwrócenia innego drzewa. Ale nie możesz ogólnie wyrazić tego pomysłu bez wyższych rodzajów.
W przypadku wyższych rodzajów można uchwycić ideę dowolnego typu sparametryzowaną innym typem. Mówi się, że konstruktor typu, który przyjmuje jeden parametr, jest rodzaju (*->*)
. Na przykład List
. Mówi się, że konstruktor typu, który zwraca inny konstruktor typu, jest rodzaju (*->*->*)
. Na przykład Function1
. Ale w Scali mamy wyższe rodzaje, więc możemy mieć konstruktory typów, które są sparametryzowane za pomocą innych konstruktorów typów. Więc są tego rodzaju ((*->*)->*)
.
Na przykład:
trait Functor[F[_]] {
def fmap[A, B](f: A => B, fa: F[A]): F[B]
}
Teraz, jeśli masz Functor[List]
, możesz mapować listy. Jeśli masz Functor[Tree]
, możesz mapować drzewa. Ale co ważniejsze, jeśli masz Functor[A]
dla dowolnego rodzaju A(*->*)
, możesz zmapować funkcję A
.
Ekstraktory, które pozwalają zastąpić niechlujny if-elseif-else
kod stylu wzorami. Wiem, że nie są one dokładnie ukryte, ale używam Scali od kilku miesięcy, nie rozumiejąc ich mocy. Na (długi) przykład mogę wymienić:
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)
}
Dzięki temu, co moim zdaniem jest znacznie jaśniejsze
implicit val ps: ProductService = ...
val p = code match {
case SyntheticCodes.Cash(c) => c
case SyntheticCodes.Forward(f) => f
case _ => ps.lookupProductByRic(code)
}
Muszę trochę popracować w tle ...
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
}
}
Ale praca jest tego warta, ponieważ oddziela kawałek logiki biznesowej w rozsądne miejsce. Mogę zaimplementować moje Product.getCode
metody w następujący sposób.
class CashProduct {
def getCode = SyntheticCodes.Cash(this)
}
class ForwardProduct {
def getCode = SyntheticCodes.Forward(this)
}
Manifesty, które są swego rodzaju sposobem uzyskiwania informacji o typie w czasie wykonywania, tak jakby Scala miała typy zreifikowane.
W scali 2.8 możesz mieć metody rekurencyjne typu tail, używając pakietu scala.util.control.TailCalls (w rzeczywistości jest to trampolinowanie).
Przykład:
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)
Klasy Case automatycznie łączą cechę Product, zapewniając nietypowy, indeksowany dostęp do pól bez żadnej refleksji:
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)
Ta funkcja zapewnia również uproszczony sposób zmiany wyniku toString
metody:
case class Person(name: String, age: Int) {
override def productPrefix = "person: "
}
// prints "person: (Aaron,28)" instead of "Person(Aaron, 28)"
println(Person("Aaron", 28))
Nie jest to dokładnie ukryte, ale z pewnością niedostatecznie reklamowana funkcja: scalac -Xprint .
Jako ilustrację zastosowania rozważ następujące źródło:
class A { "xx".r }
Kompilując to za pomocą wyjścia scalac -Xprint: typer :
package <empty> {
class A extends java.lang.Object with ScalaObject {
def this(): A = {
A.super.this();
()
};
scala.this.Predef.augmentString("xx").r
}
}
Uwaga scala.this.Predef.augmentString("xx").r
, która jest aplikacją implicit def augmentString
obecną w Predef.scala.
scalac -Xprint: <faza> wypisze drzewo składni po jakiejś fazie kompilatora. Aby zobaczyć dostępne fazy, użyj scalac -Xshow-phases .
To świetny sposób, aby dowiedzieć się, co dzieje się za kulisami.
Spróbuj z
case class X(a:Int,b:String)
wykorzystując fazę typer, aby naprawdę poczuć, jak jest ona użyteczna.
Możesz zdefiniować własne struktury sterowania. To tak naprawdę tylko funkcje i obiekty oraz trochę cukru syntaktycznego, ale wyglądają i zachowują się jak prawdziwe.
Na przykład poniższy kod definiuje dont {...} unless (cond)
i 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
}
}
Teraz możesz wykonać następujące czynności:
/* 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)
zif[A : Zero](cond: => Boolean)(t: => A): A = if(cond) t else mzero
. Wymaga Scalaz.
@switch
adnotacja w Scali 2.8:
Adnotacja, która ma być zastosowana do wyrażenia dopasowującego. Jeśli jest obecny, kompilator sprawdzi, czy dopasowanie zostało skompilowane do przełącznika tabeli lub przełącznika wyszukiwania, i wygeneruje błąd, jeśli zamiast tego skompiluje się w serię wyrażeń warunkowych.
Przykład:
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 {
Nie wiem, czy to jest naprawdę ukryte, ale uważam to za całkiem miłe.
Konstruktory typów, które przyjmują 2 parametry typu, można zapisać w notacji wrostkowej
object Main {
class FooBar[A, B]
def main(args: Array[String]): Unit = {
var x: FooBar[Int, BigInt] = null
var y: Int FooBar BigInt = null
}
}
var foo2barConverter: Foo ConvertTo Bar
sprawi, że kolejność parametrów typu będzie oczywista.
Scala 2.8 wprowadziła domyślne i nazwane argumenty, co umożliwiło dodanie nowej metody "kopiowania", którą Scala dodaje do klas przypadków. Jeśli to zdefiniujesz:
case class Foo(a: Int, b: Int, c: Int, ... z:Int)
i chcesz utworzyć nowe Foo podobne do istniejącego Foo, tylko z inną wartością „n”, możesz po prostu powiedzieć:
foo.copy(n = 3)
w scali 2.8 możesz dodać @specialized do swoich klas / metod ogólnych. Spowoduje to utworzenie specjalnych wersji klasy dla typów pierwotnych (rozszerzenie AnyVal) i zaoszczędzenie kosztów niepotrzebnego pakowania / rozpakowywania:
class Foo[@specialized T]...
Możesz wybrać podzbiór AnyVals:
class Foo[@specialized(Int,Boolean) T]...
Rozszerzenie języka. Zawsze chciałem zrobić coś takiego w Javie (nie mogłem). Ale w Scali mogę mieć:
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
}
a potem napisz:
val numbers = List(12, 42, 3, 11, 6, 3, 77, 44)
val sorted = timed { // "timed" is a new "keyword"!
numbers.sortWith(_<_)
}
println(sorted)
i dostać
Executed in: 6.410311 millisec
List(3, 3, 6, 11, 12, 42, 44, 77)
Możesz wyznaczyć parametr wywołania według nazwy (EDYCJA: to jest inny niż parametr leniwy!) Do funkcji i nie będzie on oceniany, dopóki nie zostanie użyty przez funkcję (EDYCJA: w rzeczywistości zostanie ponownie oszacowany za każdym razem, gdy jest używany). Zobacz ten FAQ, aby uzyskać szczegółowe informacje
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
*/
lazy val xx: Bar = x
w swojej metodzie i od tego momentu używasz tylko xx
.
Możesz użyć, locally
aby wprowadzić blok lokalny bez powodowania problemów z wnioskami średnikami.
Stosowanie:
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
jest zdefiniowany w „Predef.scala” jako:
@inline def locally[T](x: T): T = x
Będąc w linii, nie nakłada żadnych dodatkowych kosztów.
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)
Wynik:
In AbstractT2:
AbstractT2: value = 10, inverse = 0.1
c2c.value = 10, inverse = 0.1
Tworzymy instancję anonimowej klasy wewnętrznej, inicjalizując
value
pole w bloku przedwith AbstractT2
klauzulą. Gwarantujevalue
to, że zostanie zainicjowany przed wykonaniem treści programuAbstractT2
, jak pokazano po uruchomieniu skryptu.
Możesz komponować typy strukturalne za pomocą słowa kluczowego „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
}
}
składnia symbolu zastępczego dla funkcji anonimowych
Ze specyfikacji języka Scala:
SimpleExpr1 ::= '_'
Wyrażenie (kategorii składniowej
Expr
) może zawierać osadzone symbole podkreślenia_
w miejscach, w których identyfikatory są dozwolone. Takie wyrażenie reprezentuje anonimową funkcję, w której kolejne wystąpienia podkreślenia oznaczają kolejne parametry.
_ + 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)
Używając tego, możesz zrobić coś takiego:
def filesEnding(query: String) =
filesMatching(_.endsWith(query))
Niejawne definicje, zwłaszcza konwersje.
Na przykład załóżmy, że funkcja, która sformatuje ciąg wejściowy tak, aby pasował do rozmiaru, zastępując jego środek znakiem „...”:
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
}
Możesz tego użyć z dowolnym ciągiem i oczywiście użyć metody toString do konwersji wszystkiego. Ale możesz też napisać to tak:
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
}
Następnie możesz przejść zajęcia innego typu, wykonując następujące czynności:
implicit def double2String(d: Double) = d.toString
Teraz możesz wywołać tę funkcję, przekazując double:
sizeBoundedString(12345.12345D, 8)
Ostatni argument jest niejawny i jest przekazywany automatycznie z powodu niejawnej deklaracji. Ponadto "s" jest istnieniem traktowane jak String wewnątrz sizeBoundedString, ponieważ następuje niejawna konwersja z niego na String.
Implikacje tego typu są lepiej zdefiniowane dla nietypowych typów, aby uniknąć nieoczekiwanych konwersji. Możesz również jawnie przekazać konwersję i nadal będzie ona niejawnie używana wewnątrz sizeBoundedString:
sizeBoundedString(1234567890L, 8)((l : Long) => l.toString)
Możesz również mieć wiele niejawnych argumentów, ale wtedy musisz albo przekazać je wszystkie, albo nie przekazywać żadnego z nich. Istnieje również składnia skrótów dla niejawnych konwersji:
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
}
To jest używane dokładnie w ten sam sposób.
Implikacje mogą mieć dowolną wartość. Można ich użyć na przykład do ukrycia informacji bibliotecznych. Weźmy na przykład następujący przykład:
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")
}
}
W tym przykładzie wywołanie „f” w obiekcie Y spowoduje wysłanie dziennika do demona domyślnego, a na instancji X do demona Daemon X. Ale wywołanie g na instancji X wyśle dziennik do jawnie podanego DefaultDaemon.
Chociaż ten prosty przykład można ponownie napisać z przeciążeniem i stanem prywatnym, implicity nie wymagają stanu prywatnego i można je umieścić w kontekście za pomocą importu.
Może niezbyt ukryte, ale myślę, że jest to przydatne:
@scala.reflect.BeanProperty
var firstName:String = _
Spowoduje to automatyczne wygenerowanie metody pobierającej i ustawiającej dla pola zgodnego z konwencją fasoli.
Dalszy opis w pracach deweloperskich
Niejawne argumenty w domknięciach.
Argument funkcji można oznaczyć jako niejawny, tak jak w przypadku metod. W zakresie treści funkcji niejawny parametr jest widoczny i kwalifikuje się do niejawnego rozwiązania:
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")
})
}
Twórz nieskończone struktury danych za pomocą Scali Stream
:
http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient
Typy wyników zależą od niejawnego rozwiązania. Może to dać formę wielokrotnej wysyłki:
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
foo
zastosowań, a
które musiały być obecne w środowisku przed wykonaniem tych poleceń. Zakładam, że miałeś na myśli z.perform(x)
.
Scala umożliwia utworzenie anonimowej podklasy z treścią klasy (konstruktora) zawierającą instrukcje do zainicjowania instancji tej klasy.
Ten wzorzec jest bardzo przydatny podczas budowania interfejsów użytkownika opartych na komponentach (na przykład Swing, Vaadin), ponieważ umożliwia tworzenie komponentów UI i bardziej zwięzłe deklarowanie ich właściwości.
Więcej informacji można znaleźć pod adresem http://spot.colorado.edu/~reids/papers/how-scala-experience-improved-our-java-development-reid-2011.pdf .
Oto przykład tworzenia przycisku Vaadin:
val button = new Button("Click me"){
setWidth("20px")
setDescription("Click on this")
setIcon(new ThemeResource("icons/ok.png"))
}
import
oświadczeńZałóżmy, że chcesz użyć metody Logger
zawierającej a println
i printerr
metodę, ale chcesz używać tylko tej dla komunikatów o błędach i zachować stare dobre Predef.println
dla standardowego wyjścia. Możesz to zrobić:
val logger = new Logger(...)
import logger.printerr
ale jeśli logger
zawiera również kolejne dwanaście metod, które chciałbyś zaimportować i użyć, ich lista staje się niewygodna. Zamiast tego możesz spróbować:
import logger.{println => donotuseprintlnt, _}
ale to nadal „zanieczyszcza” listę importowanych członków. Wpisz niezwykle potężną kartę wieloznaczną:
import logger.{println => _, _}
a to wystarczy ™.
require
metoda (zdefiniowana w Predef
), która umożliwia zdefiniowanie dodatkowych ograniczeń funkcji, które byłyby sprawdzane w czasie wykonywania. Wyobraź sobie, że tworzysz kolejnego klienta Twittera i musisz ograniczyć długość tweeta do 140 symboli. Ponadto nie możesz publikować pustego tweeta.
def post(tweet: String) = {
require(tweet.length < 140 && tweet.length > 0)
println(tweet)
}
Teraz wywołanie posta z niewłaściwym argumentem długości spowoduje wyjątek:
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)
Możesz napisać wiele wymagań lub nawet dodać opis do każdego:
def post(tweet: String) = {
require(tweet.length > 0, "too short message")
require(tweet.length < 140, "too long message")
println(tweet)
}
Teraz wyjątki są szczegółowe:
scala> post("")
java.lang.IllegalArgumentException: requirement failed: too short message
at scala.Predef$.require(Predef.scala:157)
at .post(<console>:8)
Tutaj jest jeszcze jeden przykład .
Możesz wykonać akcję za każdym razem, gdy wymaganie nie powiedzie się:
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
require
nie jest słowem zastrzeżonym. To tylko metoda zdefiniowana w Predef
.
Cechy z abstract override
metodami to funkcja w Scali, która nie jest tak szeroko reklamowana, jak wiele innych. Celem metod z abstract override
modyfikatorem jest wykonanie pewnych operacji i delegowanie wywołania do super
. Następnie cechy te należy wymieszać z konkretnymi implementacjami ich abstract override
metod.
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
Chociaż mój przykład jest naprawdę niewiele więcej niż kiepskim człowiekiem AOP, użyłem tych Stackable Traits bardzo do moich upodobań do zbudowania instancji interpretera Scala z predefiniowanymi importami, niestandardowymi powiązaniami i klasami. W Nakładane cechy pozwoliły stworzyć moją fabrykę wzdłuż linii new InterpreterFactory with JsonLibs with LuceneLibs
, a następnie mieć użytecznych importu i zakres varibles dla skryptów użytkowników.