Was sind die versteckten Funktionen von Scala, die jeder Scala-Entwickler kennen sollte?
Bitte eine versteckte Funktion pro Antwort.
Was sind die versteckten Funktionen von Scala, die jeder Scala-Entwickler kennen sollte?
Bitte eine versteckte Funktion pro Antwort.
Antworten:
Okay, ich musste noch einen hinzufügen. Jedes Regex
Objekt in Scala verfügt über einen Extraktor (siehe Antwort von oxbox_lakes oben), mit dem Sie auf die Übereinstimmungsgruppen zugreifen können. Sie können also Folgendes tun:
// 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"
Die zweite Zeile sieht verwirrend aus, wenn Sie nicht daran gewöhnt sind, Mustervergleich und Extraktoren zu verwenden. Wann immer Sie ein val
oder definieren var
, ist das, was nach dem Schlüsselwort kommt, nicht einfach eine Kennung, sondern ein Muster. Deshalb funktioniert das:
val (a, b, c) = (1, 3.14159, "Hello, world")
Der Ausdruck für Tuple3[Int, Double, String]
die rechte Hand erzeugt ein, das dem Muster entsprechen kann (a, b, c)
.
In den meisten Fällen verwenden Ihre Muster Extraktoren, die Mitglieder von Singleton-Objekten sind. Zum Beispiel, wenn Sie ein Muster wie schreiben
Some(value)
dann rufen Sie implizit den Extraktor auf Some.unapply
.
Sie können aber auch Klasseninstanzen in Mustern verwenden, und genau das passiert hier. Der val regex ist eine Instanz von Regex
, und wenn Sie ihn in einem Muster verwenden, rufen Sie implizit auf regex.unapplySeq
( unapply
versus unapplySeq
geht über den Rahmen dieser Antwort hinaus), wodurch die Übereinstimmungsgruppen in a extrahiert werden Seq[String]
, deren Elemente zugewiesen werden, um die Variablen Jahr, Monat und Tag.
Strukturelle Typdefinitionen - dh ein Typ, der durch die unterstützten Methoden beschrieben wird. Beispielsweise:
object Closer {
def using(closeable: { def close(): Unit }, f: => Unit) {
try {
f
} finally { closeable.close }
}
}
Beachten Sie, dass der Typ des Parameters closeable
nur mit einer close
Methode definiert ist
Ohne diese Funktion können Sie beispielsweise die Idee ausdrücken, eine Funktion einer Liste zuzuordnen, um eine andere Liste zurückzugeben, oder eine Funktion einem Baum zuzuordnen, um einen anderen Baum zurückzugeben. Aber ohne höhere Arten kann man diese Idee nicht generell ausdrücken .
Mit höheren Arten können Sie die Idee eines beliebigen Typs erfassen , der mit einem anderen Typ parametrisiert ist. Ein Typkonstruktor, der einen Parameter akzeptiert, wird als Art bezeichnet (*->*)
. Zum Beispiel List
. Ein Typkonstruktor, der einen anderen Typkonstruktor zurückgibt, wird als Art bezeichnet (*->*->*)
. Zum Beispiel Function1
. In Scala gibt es jedoch höhere Arten, sodass wir Typkonstruktoren haben können, die mit anderen Typkonstruktoren parametrisiert werden. Also sind sie von der Art wie ((*->*)->*)
.
Beispielsweise:
trait Functor[F[_]] {
def fmap[A, B](f: A => B, fa: F[A]): F[B]
}
Wenn Sie eine haben Functor[List]
, können Sie jetzt Listen zuordnen. Wenn Sie eine haben Functor[Tree]
, können Sie über Bäume kartieren. Aber was noch wichtiger ist, wenn Sie Functor[A]
für eine Art von A haben(*->*)
, können Sie eine Funktion überordnen A
.
Extraktoren, mit denen Sie unordentlichen if-elseif-else
Code durch Muster ersetzen können. Ich weiß, dass diese nicht genau versteckt sind, aber ich benutze Scala seit einigen Monaten, ohne die Macht von ihnen wirklich zu verstehen. Für (ein langes) Beispiel kann ich ersetzen:
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)
}
Damit ist das meiner Meinung nach viel klarer
implicit val ps: ProductService = ...
val p = code match {
case SyntheticCodes.Cash(c) => c
case SyntheticCodes.Forward(f) => f
case _ => ps.lookupProductByRic(code)
}
Ich muss im Hintergrund ein bisschen Beinarbeit machen ...
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
}
}
Aber die Beinarbeit lohnt sich, weil sie ein Stück Geschäftslogik in einen vernünftigen Ort trennt. Ich kann meine Product.getCode
Methoden wie folgt implementieren .
class CashProduct {
def getCode = SyntheticCodes.Cash(this)
}
class ForwardProduct {
def getCode = SyntheticCodes.Forward(this)
}
Manifeste, mit denen die Typinformationen zur Laufzeit abgerufen werden können, als hätte Scala Typen geändert.
In Scala 2.8 können Sie mithilfe des Pakets scala.util.control.TailCalls (in der Tat Trampolin) Methoden mit rekursiver Schwanzbildung verwenden.
Ein Beispiel:
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)
Fallklassen mischen automatisch das Produktmerkmal und bieten einen untypisierten, indizierten Zugriff auf die Felder ohne Reflexion:
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)
Diese Funktion bietet auch eine vereinfachte Möglichkeit, die Ausgabe der toString
Methode zu ändern :
case class Person(name: String, age: Int) {
override def productPrefix = "person: "
}
// prints "person: (Aaron,28)" instead of "Person(Aaron, 28)"
println(Person("Aaron", 28))
Es ist nicht gerade versteckt, aber sicherlich eine unterbeworbene Funktion: scalac -Xprint .
Betrachten Sie zur Veranschaulichung der Verwendung die folgende Quelle:
class A { "xx".r }
Kompilieren Sie dies mit scalac -Xprint: typer- Ausgaben:
package <empty> {
class A extends java.lang.Object with ScalaObject {
def this(): A = {
A.super.this();
()
};
scala.this.Predef.augmentString("xx").r
}
}
Beachten Sie scala.this.Predef.augmentString("xx").r
, dass dies eine Anwendung der implicit def augmentString
Gegenwart in Predef.scala ist.
scalac -Xprint: <phase> druckt den Syntaxbaum nach einer Compilerphase. Um die verfügbaren Phasen anzuzeigen, verwenden Sie scalac -Xshow-Phasen .
Dies ist eine großartige Möglichkeit, um zu erfahren, was sich hinter den Kulissen abspielt.
Versuche es mit
case class X(a:Int,b:String)
Verwenden Sie die Typer- Phase, um wirklich zu spüren, wie nützlich sie ist.
Sie können Ihre eigenen Kontrollstrukturen definieren. Es sind wirklich nur Funktionen und Objekte und etwas syntaktischer Zucker, aber sie sehen aus und verhalten sich wie echt.
Der folgende Code definiert beispielsweise dont {...} unless (cond)
und 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
}
}
Jetzt können Sie Folgendes tun:
/* 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
. Benötigt Scalaz.
@switch
Anmerkung in Scala 2.8:
Eine Anmerkung, die auf einen Übereinstimmungsausdruck angewendet werden soll. Wenn vorhanden, überprüft der Compiler, ob die Übereinstimmung mit einem Tabellen- oder Suchschalter kompiliert wurde, und gibt einen Fehler aus, wenn er stattdessen in eine Reihe von bedingten Ausdrücken kompiliert wird.
Beispiel:
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 {
Keine Ahnung, ob das wirklich versteckt ist, aber ich finde es ganz nett.
Typkonstruktoren, die zwei Typparameter verwenden, können in Infixnotation geschrieben werden
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
würde die Reihenfolge der Typparameter offensichtlich sein.
Scala 2.8 führte Standard- und benannte Argumente ein, die das Hinzufügen einer neuen "Kopier" -Methode ermöglichten, die Scala zu Fallklassen hinzufügt. Wenn Sie dies definieren:
case class Foo(a: Int, b: Int, c: Int, ... z:Int)
und Sie möchten ein neues Foo erstellen, das einem vorhandenen Foo ähnelt, nur mit einem anderen "n" -Wert, dann können Sie einfach sagen:
foo.copy(n = 3)
In Scala 2.8 können Sie @specialized zu Ihren generischen Klassen / Methoden hinzufügen. Dadurch werden spezielle Versionen der Klasse für primitive Typen erstellt (Erweiterung von AnyVal) und die Kosten für unnötiges Boxen / Unboxing gespart:
class Foo[@specialized T]...
Sie können eine Teilmenge von AnyVals auswählen:
class Foo[@specialized(Int,Boolean) T]...
Sprache erweitern. Ich wollte schon immer so etwas in Java machen (konnte nicht). Aber in Scala kann ich haben:
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
}
und dann schreibe:
val numbers = List(12, 42, 3, 11, 6, 3, 77, 44)
val sorted = timed { // "timed" is a new "keyword"!
numbers.sortWith(_<_)
}
println(sorted)
und bekomme
Executed in: 6.410311 millisec
List(3, 3, 6, 11, 12, 42, 44, 77)
Sie können einen Call-by-Name-Parameter (EDITED: Dies unterscheidet sich von einem Lazy-Parameter!) Zu einer Funktion festlegen. Er wird erst ausgewertet, wenn er von der Funktion verwendet wird (EDIT: Tatsächlich wird er jedes Mal neu bewertet gebraucht). Einzelheiten finden Sie in dieser FAQ
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
in Ihrer Methode tun und von diesem Moment an nur noch verwenden xx
.
Sie können locally
einen lokalen Block einführen, ohne Semikolon-Inferenzprobleme zu verursachen.
Verwendung:
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
wird in "Predef.scala" definiert als:
@inline def locally[T](x: T): T = x
Inline zu sein, bedeutet keinen zusätzlichen Overhead.
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)
Ausgabe:
In AbstractT2:
AbstractT2: value = 10, inverse = 0.1
c2c.value = 10, inverse = 0.1
Wir instanziieren eine anonyme innere Klasse und initialisieren das
value
Feld im Block vor derwith AbstractT2
Klausel. Dies garantiert, dassvalue
es initialisiert wird, bevor der Body vonAbstractT2
ausgeführt wird, wie beim Ausführen des Skripts gezeigt.
Sie können Strukturtypen mit dem Schlüsselwort 'with' erstellen
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
}
}
Platzhaltersyntax für anonyme Funktionen
Aus der Scala-Sprachspezifikation:
SimpleExpr1 ::= '_'
Ein Ausdruck (der syntaktischen Kategorie
Expr
) kann eingebettete Unterstrichsymbole_
an Stellen enthalten, an denen Bezeichner zulässig sind. Ein solcher Ausdruck stellt eine anonyme Funktion dar, bei der nachfolgende Unterstriche aufeinanderfolgende Parameter bezeichnen.
Von Scala Sprachänderungen :
_ + 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)
Mit diesem könnten Sie etwas tun wie:
def filesEnding(query: String) =
filesMatching(_.endsWith(query))
Implizite Definitionen, insbesondere Konvertierungen.
Nehmen Sie beispielsweise eine Funktion an, die eine Eingabezeichenfolge so formatiert, dass sie zu einer Größe passt, indem Sie die Mitte durch "..." ersetzen:
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
}
Sie können dies mit jedem String verwenden und natürlich die toString-Methode verwenden, um alles zu konvertieren. Man könnte es aber auch so schreiben:
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
}
Und dann könnten Sie Klassen anderer Typen übergeben, indem Sie dies tun:
implicit def double2String(d: Double) = d.toString
Jetzt können Sie diese Funktion aufrufen, indem Sie ein Double übergeben:
sizeBoundedString(12345.12345D, 8)
Das letzte Argument ist implizit und wird aufgrund der impliziten Deklaration automatisch übergeben. Darüber hinaus „s“ wird behandelt wie eine Zeichenfolge innerhalb sizeBoundedString weil es eine implizite Konvertierung von ihm Zeichenfolge ist.
Implizite dieses Typs sind für ungewöhnliche Typen besser definiert, um unerwartete Konvertierungen zu vermeiden. Sie können eine Konvertierung auch explizit übergeben, und sie wird weiterhin implizit in sizeBoundedString verwendet:
sizeBoundedString(1234567890L, 8)((l : Long) => l.toString)
Sie können auch mehrere implizite Argumente haben, aber dann müssen Sie entweder alle übergeben oder keines von ihnen übergeben. Es gibt auch eine Verknüpfungssyntax für implizite Konvertierungen:
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
}
Dies wird genauso verwendet.
Implizite können jeden Wert haben. Sie können beispielsweise verwendet werden, um Bibliotheksinformationen auszublenden. Nehmen Sie zum Beispiel das folgende Beispiel:
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")
}
}
In diesem Beispiel wird beim Aufrufen von "f" in einem Y-Objekt das Protokoll an den Standarddämon und auf einer Instanz von X an den Daemon X-Dämon gesendet. Wenn Sie jedoch g für eine Instanz von X aufrufen, wird das Protokoll an den explizit angegebenen DefaultDaemon gesendet.
Während dieses einfache Beispiel mit Überladung und privatem Status neu geschrieben werden kann, erfordern Implizite keinen privaten Status und können mit Importen in einen Kontext gebracht werden.
Vielleicht nicht zu versteckt, aber ich denke, das ist nützlich:
@scala.reflect.BeanProperty
var firstName:String = _
Dadurch werden automatisch ein Getter und ein Setter für das Feld generiert, die der Bean-Konvention entsprechen.
Weitere Beschreibung bei developerworks
Implizite Argumente in Abschlüssen.
Ein Funktionsargument kann genauso wie bei Methoden als implizit markiert werden. Im Rahmen des Funktionskörpers ist der implizite Parameter sichtbar und kann implizit aufgelöst werden:
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")
})
}
Erstellen Sie unendliche Datenstrukturen mit Scala Stream
:
http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient
Ergebnistypen hängen von der impliziten Auflösung ab. Dies kann Ihnen eine Form des Mehrfachversands geben:
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
Verwendungen, a
die vor der Ausführung dieser Befehle in der Umgebung vorhanden sein müssen. Ich nehme an, du meintest z.perform(x)
.
Mit Scala können Sie eine anonyme Unterklasse mit dem Hauptteil der Klasse (dem Konstruktor) erstellen, die Anweisungen zum Initialisieren der Instanz dieser Klasse enthält.
Dieses Muster ist sehr nützlich beim Erstellen komponentenbasierter Benutzeroberflächen (z. B. Swing, Vaadin), da es das Erstellen von UI-Komponenten und das präzisere Deklarieren ihrer Eigenschaften ermöglicht.
Weitere Informationen finden Sie unter http://spot.colorado.edu/~reids/papers/how-scala-experience-improved-our-java-development-reid-2011.pdf .
Hier ist ein Beispiel für das Erstellen einer Vaadin-Schaltfläche:
val button = new Button("Click me"){
setWidth("20px")
setDescription("Click on this")
setIcon(new ThemeResource("icons/ok.png"))
}
import
Erklärungen ausschließenAngenommen, Sie möchten eine Methode verwenden Logger
, die eine println
und eine printerr
Methode enthält, aber nur die für Fehlermeldungen verwenden und die gute alte Predef.println
für die Standardausgabe beibehalten. Sie könnten dies tun:
val logger = new Logger(...)
import logger.printerr
Wenn Sie jedoch logger
weitere zwölf Methoden enthalten, die Sie importieren und verwenden möchten, ist es unpraktisch, diese aufzulisten. Sie könnten stattdessen versuchen:
import logger.{println => donotuseprintlnt, _}
Dies "verschmutzt" jedoch immer noch die Liste der importierten Mitglieder. Geben Sie den übermächtigen Platzhalter ein:
import logger.{println => _, _}
und das wird genau das Richtige tun ™.
require
Methode (definiert in Predef
), mit der Sie zusätzliche Funktionseinschränkungen definieren können, die zur Laufzeit überprüft werden. Stellen Sie sich vor, Sie entwickeln einen weiteren Twitter-Client und müssen die Tweet-Länge auf 140 Symbole beschränken. Außerdem kannst du keinen leeren Tweet posten.
def post(tweet: String) = {
require(tweet.length < 140 && tweet.length > 0)
println(tweet)
}
Wenn Sie jetzt einen Beitrag mit einem Argument mit unangemessener Länge aufrufen, wird eine Ausnahme ausgelöst:
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)
Sie können mehrere Anforderungen schreiben oder sogar eine Beschreibung hinzufügen:
def post(tweet: String) = {
require(tweet.length > 0, "too short message")
require(tweet.length < 140, "too long message")
println(tweet)
}
Jetzt sind Ausnahmen ausführlich:
scala> post("")
java.lang.IllegalArgumentException: requirement failed: too short message
at scala.Predef$.require(Predef.scala:157)
at .post(<console>:8)
Ein weiteres Beispiel ist hier .
Sie können jedes Mal eine Aktion ausführen, wenn die Anforderung fehlschlägt:
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
ist kein reserviertes Wort. Es ist nur eine Methode definiert in Predef
.
Merkmale mit abstract override
Methoden sind ein Merkmal in Scala, das nicht so häufig beworben wird wie viele andere. Mit Methoden mit dem abstract override
Modifikator sollen einige Operationen ausgeführt und der Aufruf an delegiert werden super
. Dann müssen diese Merkmale mit konkreten Implementierungen ihrer abstract override
Methoden gemischt werden.
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
Während mein Beispiel wirklich nicht viel mehr ist als das AOP eines armen Mannes, habe ich diese stapelbaren Eigenschaften sehr nach meinem Geschmack verwendet, um Scala-Interpreter-Instanzen mit vordefinierten Importen, benutzerdefinierten Bindungen und Klassenpfaden zu erstellen. Die stapelbaren Eigenschaften ermöglichten es, meine Factory nach dem Vorbild von zu erstellen new InterpreterFactory with JsonLibs with LuceneLibs
und dann nützliche Importe und Bereichsvariablen für die Benutzerskripte zu haben.