Versteckte Funktionen von Scala

149

Was sind die versteckten Funktionen von Scala, die jeder Scala-Entwickler kennen sollte?

Bitte eine versteckte Funktion pro Antwort.

Krzysiek Goj
quelle
6
Heh, diese Frage ist für die Links zu den anderen versteckten Features genauso nützlich wie für die Frage selbst. Prost!
JohnMetta
1
@mettadore schau dir einfach die zugehörigen Links auf der rechten Seite an.
Daniel C. Sobral
2
@ JohnMetta: Oder benutze das Tag .

Antworten:

85

Okay, ich musste noch einen hinzufügen. Jedes RegexObjekt 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 valoder 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( unapplyversus unapplySeqgeht ü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.

Willis Blackburn
quelle
1
Danke fürs posten! Zu Ihrer Information, es wird im Kapitel "Extrahieren mit regulären Ausdrücken" im Buch "Programmieren in Scala" auf Seite 503 in der ersten Ausgabe und auf Seite 611 in der zweiten Ausgabe erwähnt.
Erdling Paul
51

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 closeablenur mit einer closeMethode definiert ist

oxbow_lakes
quelle
1
Strukturtypen werden in "Programmieren in Scala" nicht einmal erwähnt. Sie sind jedoch etwas langsamer als andere Techniken zum Übergeben von Typen, da sie Reflexion verwenden, um die richtigen Methoden aufzurufen. (Hoffentlich finden sie einen Weg, dies zu beschleunigen.)
Ken Bloom
1
Und es ist auch möglich, einen Alias ​​für sie zu erstellen, der wie eine extern zugewiesene Schnittstelle funktioniert (sehr langsam): Typ Closeable = {def close (): Unit}
Alexey
45

Typ-Konstruktor-Polymorphismus (auch bekannt als höherwertige Typen)

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.

Apocalisp
quelle
39

Extraktoren, mit denen Sie unordentlichen if-elseif-elseCode 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.getCodeMethoden wie folgt implementieren .

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

class ForwardProduct {
  def getCode = SyntheticCodes.Forward(this)     
}
oxbow_lakes
quelle
ist das nicht wie ein Schalter? Vielleicht könnte dies mehr überarbeitet werden.
Geo
14
Muster sind wie turbogeladene Schalter: viel leistungsfähiger und klarer
oxbow_lakes
1
Schön, aber ich mag es nicht, dass Sie implizit verwenden müssen, da der Umfang weiter reicht als die Übereinstimmung {}. Sie können ProductService auch einfach eine Methode hinzufügen, die ein Produkt nach Code sucht. Sie würden Ihr überarbeitetes Snippet sowieso in eine Methode einwickeln, um es überall verwenden zu können.
Martin Konicek
35

Manifeste, mit denen die Typinformationen zur Laufzeit abgerufen werden können, als hätte Scala Typen geändert.

oxbow_lakes
quelle
8
Ich denke, es ist vorzuziehen, die Antwort in der Antwort zu erklären , anstatt auf einen Link zu verweisen. Übrigens, hi agai oxbow! :-)
Daniel C. Sobral
Dies ist eine wirklich versteckte Funktion ... nicht einmal in den API-Dokumenten. Sehr nützlich.
André Laszlo
35

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)
Aymen
quelle
35

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 toStringMethode 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)) 
Aaron Novstrup
quelle
32

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 augmentStringGegenwart 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.

pedrofurla
quelle
30

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) 
Aleksander Kmetec
quelle
Einige weitere Beispiele hier: programmers.stackexchange.com/questions/13072/…
fehlender Faktor
Ich wäre neugierig, wenn jemand eine Möglichkeit kennt, If-Then-else-Blöcke mit optionalem else zu definieren, die die Typprüfung wie die Standardblöcke durchführen.
Philippe
@Philippe : zif[A : Zero](cond: => Boolean)(t: => A): A = if(cond) t else mzero. Benötigt Scalaz.
Fehlender Faktor
26

@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 {
fehlender Faktor
quelle
26

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
  }
}
Raichoo
quelle
1
Nett! Ich kann mir vorstellen, dass dies manchmal nützlich ist, um die Lesbarkeit zu verbessern. Zum Beispiel var foo2barConverter: Foo ConvertTo Barwürde die Reihenfolge der Typparameter offensichtlich sein.
Esko Luontola
4
Ich mache das manchmal in Code, der teilweise PartialFunction verwendet: Typ ~> [A, B] = PartialFunction [A, B]
Raichoo
24

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)
Willis Blackburn
quelle
3
WARNUNG: Die Kopiermethode wird nicht überschrieben, wenn Sie eine Fallklasse von einer anderen erben. Sie müssen es also manuell überschreiben
Alexey
Verwandte: Sauberere
oluies
5
Die Fallklasse darf nicht mehr (Scala 2.8) von einer Fallklasse erben. Vielen Dank, Herr von Scala, dass Sie dieses unheilige Erbe abgelehnt haben.
olle kullberg
24

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]...

Aymen
quelle
1
Gibt es eine längere Erklärung, auf die Sie mich hinweisen könnten? Ich würde gerne mehr erfahren.
Paweł Prażak
23

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)
Adrian
quelle
23

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
*/
Agilefall
quelle
Ich dachte, "x: => Balken" bedeutet, dass x eine Funktion ist, die keine Parameter akzeptiert und einen Balken zurückgibt. "New bar (22)" ist also nur eine anonyme Funktion und wird wie jede andere Funktion als Funktion ausgewertet.
Alex Black
1
"x: () => Balken" definiert eine xa-Funktion, die keine Parameter akzeptiert und einen Balken zurückgibt. x: => Balken definiert x als Aufruf nach Namen. Werfen Sie einen Blick auf scala.sygneca.com/faqs/… für weitere Details
Agilefall
3
Was Sie anzeigen, sind Call-by-Name-Parameter. Lazy-Parameter sind noch nicht implementiert: lampsvn.epfl.ch/trac/scala/ticket/240
ArtemGr
Ich denke, dass Sie es als faulen Parameter verwenden können, wenn Sie so etwas wie lazy val xx: Bar = xin Ihrer Methode tun und von diesem Moment an nur noch verwenden xx.
Cristian Vrabie
20

Sie können locallyeinen 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.

fehlender Faktor
quelle
3
Dies wird besser unter stackoverflow.com/questions/3237727/…
Esko Luontola
17

Frühe Initialisierung:

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 valueFeld im Block vor der with AbstractT2Klausel. Dies garantiert, dass valuees initialisiert wird, bevor der Body von AbstractT2ausgeführt wird, wie beim Ausführen des Skripts gezeigt.

fehlender Faktor
quelle
1
Das Konstrukt wird "frühe Initialisierung" genannt.
Randall Schulz
17

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
  }
}
Raichoo
quelle
17

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))
Eugene Yokota
quelle
2
Dies sollte als "Platzhaltersyntax für anonyme Funktionen" bezeichnet werden. Implizit hat in Scala eine eindeutige Bedeutung und ist damit nicht verbunden.
Retronym
Der Link hat eine nicht offensichtliche Beziehung zur Antwort. "implizit" ist hierfür nicht der richtige Begriff. Wie oben sollte es "Platzhalter" sein.
Alain O'Dea
2
Es ist nicht wirklich "versteckt", ich habe diese Verwendung in fast allen Tutorials auf Scala gesehen, die ich gelesen habe ... :-) Aber ich schätze die formale Definition, die ich noch nicht gesehen habe.
PhiLho
@PhiLho vielleicht war es 2009 weniger bekannt. Ich weiß es nicht.
Eugene Yokota
Ich habe das ursprüngliche Datum verpasst, da nur das Datum der letzten Bearbeitung angezeigt wird. Und nun, nicht alle in diesem Thread erläuterten Funktionen sind "versteckt". Cooler Thread und trotzdem gute Antwort.
PhiLho
16

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.

Daniel C. Sobral
quelle
13

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

Agilefall
quelle
6
Und Sie können eine Verknüpfung dafür erstellen, wenn Sie es häufig verwenden, z. B.: Import scala.reflect. {BeanProperty => BP}
Alexey
13

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")
  })
}
axel22
quelle
12

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
jsuereth
quelle
Das mag der Fall sein, aber die obige Sitzung ist irreführend. Die Definition von fooVerwendungen, adie vor der Ausführung dieser Befehle in der Umgebung vorhanden sein müssen. Ich nehme an, du meintest z.perform(x).
Daniel C. Sobral
4

Scalas Äquivalent zum Java Double Brace Initialisierer.

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"))
}
Guillaume Belrose
quelle
3

Mitglieder von importErklärungen ausschließen

Angenommen, Sie möchten eine Methode verwenden Logger, die eine printlnund eine printerrMethode enthält, aber nur die für Fehlermeldungen verwenden und die gute alte Predef.printlnfür die Standardausgabe beibehalten. Sie könnten dies tun:

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

Wenn Sie jedoch loggerweitere 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 ™.

Philippe
quelle
2

requireMethode (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 .


Bonus

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
om-nom-nom
quelle
1
requireist kein reserviertes Wort. Es ist nur eine Methode definiert in Predef.
fehlender Faktor
1

Merkmale mit abstract overrideMethoden sind ein Merkmal in Scala, das nicht so häufig beworben wird wie viele andere. Mit Methoden mit dem abstract overrideModifikator sollen einige Operationen ausgeführt und der Aufruf an delegiert werden super. Dann müssen diese Merkmale mit konkreten Implementierungen ihrer abstract overrideMethoden 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 LuceneLibsund dann nützliche Importe und Bereichsvariablen für die Benutzerskripte zu haben.

MxFr
quelle