Caratteristiche nascoste di Scala


149

Quali sono le caratteristiche nascoste di Scala di cui ogni sviluppatore di Scala dovrebbe essere a conoscenza?

Una funzione nascosta per risposta, per favore.


6
Heh, questa domanda è utile sia per i collegamenti agli altri post di funzionalità nascoste che per la domanda stessa. Saluti!
JohnMetta,

1
@mettadore basta guardare i link correlati sul lato destro.
Daniel C. Sobral,

2
@JohnMetta: oppure usa il tag .

Risposte:


85

Ok, ho dovuto aggiungerne un altro. Ogni Regexoggetto in Scala ha un estrattore (vedi la risposta da oxbox_lakes sopra) che ti dà accesso ai gruppi di partite. Quindi puoi fare qualcosa come:

// 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 seconda riga sembra confusa se non sei abituato a usare la corrispondenza dei modelli e gli estrattori. Ogni volta che definisci un valo var, ciò che viene dopo la parola chiave non è semplicemente un identificatore ma piuttosto un modello. Ecco perché funziona:

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

L'espressione della mano destra crea una Tuple3[Int, Double, String]che può corrispondere al modello (a, b, c).

Il più delle volte i tuoi schemi usano estrattori membri di oggetti singleton. Ad esempio, se scrivi uno schema come

Some(value)

allora stai implicitamente chiamando l'estrattore Some.unapply.

Ma puoi anche usare istanze di classe in schemi, ed è quello che sta succedendo qui. Val regex è un'istanza di Regex, e quando lo usi in uno schema, stai implicitamente chiamando regex.unapplySeq( unapplycontro unapplySeqè oltre lo scopo di questa risposta), che estrae i gruppi di corrispondenza in a Seq[String], i cui elementi sono assegnati per le variabili anno, mese e giorno.


1
Grazie per aver pubblicato questo! FYI è menzionato nel capitolo "Estrarre con espressioni regolari" nel libro "Programmazione in Scala" a pagina 503 nella prima edizione e a pagina 611 nella seconda edizione.
terrestre paul

51

Definizioni di tipo strutturale - ovvero un tipo descritto da quali metodi supporta. Per esempio:

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

Si noti che il tipo di parametro closeablenon è definito se non con un closemetodo


1
I tipi strutturali non sono nemmeno menzionati in "Programmazione in Scala". Sono un po 'più lenti di altre tecniche per passare i tipi, dato che usano la riflessione per chiamare i metodi giusti. (Speriamo che escogiteranno un modo per accelerarlo.)
Ken Bloom,

1
E c'è anche la possibilità di creare un alias per loro, che funziona come un'interfaccia assegnata esternamente (molto lenta): type Closeable = {def close (): Unit}
Alexey

45

Polimorfismo costruttore di tipo (aka tipi di tipo superiore)

Senza questa funzione è possibile, ad esempio, esprimere l'idea di mappare una funzione su un elenco per restituire un altro elenco o mappare una funzione su un albero per restituire un altro albero. Ma non puoi esprimere questa idea generalmente senza tipi superiori.

Con tipi superiori, puoi catturare l'idea di qualsiasi tipo parametrizzato con un altro tipo. Un costruttore di tipi che accetta un parametro si dice che sia di tipo (*->*). Ad esempio List,. Un costruttore di tipi che restituisce un altro costruttore di tipi si dice che sia di tipo (*->*->*). Ad esempio Function1,. Ma in Scala abbiamo tipi più elevati , quindi possiamo avere costruttori di tipi che sono parametrizzati con altri costruttori di tipi. Quindi sono di tipi come ((*->*)->*).

Per esempio:

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

Ora, se ne hai uno Functor[List], puoi mappare gli elenchi. Se hai un Functor[Tree], puoi mappare sugli alberi. Ma, cosa ancora più importante, se hai Functor[A] una A di tipo(*->*) , puoi mappare una funzione A.


39

Estrattori che consentono di sostituire il if-elseif-elsecodice di stile disordinato con motivi. So che questi non sono esattamente nascosti, ma sto usando Scala da alcuni mesi senza capire veramente il loro potere. Per un (lungo) esempio posso sostituire:

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)
}

Con questo, che è molto più chiaro secondo me

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

Devo fare un po 'di legwork in background ...

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
    }
  }

Ma il lavoro per le gambe vale la pena per il fatto che separa un pezzo di logica aziendale in un luogo ragionevole. Posso implementare i miei Product.getCodemetodi come segue ..

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

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

non è come un interruttore? forse questo potrebbe essere riformulato di più.
Geo,

14
I pattern sono come interruttori turbo: molto più potenti e chiari
oxbow_lakes

1
Bello, ma non mi piace che tu debba usare implicito perché il suo campo di applicazione si estende oltre la corrispondenza {}. Puoi anche aggiungere un metodo a ProductService che cerca un prodotto per codice. Avresti comunque avvolto il tuo frammento refactored in un metodo per poterlo usare ovunque.
Martin Konicek,

35

Manifestazioni che sono una sorta di modo per ottenere le informazioni sui tipi in fase di esecuzione, come se Scala avesse tipi reificati.


8
Penso che sia preferibile spiegare la risposta nella risposta piuttosto che fare riferimento a un collegamento. A proposito, ciao bufalo! :-)
Daniel C. Sobral,

Questa è una funzionalità davvero nascosta ... nemmeno nei documenti API. Molto utile però.
André Laszlo,

35

In scala 2.8 puoi avere metodi ricorsivi di coda usando il pacchetto scala.util.control.TailCalls (in realtà è un trampolino).

Un esempio:

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

Le classi di casi mescolano automaticamente la caratteristica del Prodotto, fornendo accesso non tipizzato e indicizzato ai campi senza alcuna riflessione:

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)

Questa funzione fornisce anche un modo semplificato per modificare l'output del toStringmetodo:

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

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

32

Non è esattamente nascosto, ma certamente una funzionalità sotto pubblicizzata: scalac -Xprint .

A titolo di esempio, considera la seguente fonte:

class A { "xx".r }

Compilando questo con scalac -Xprint: typer output:

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

Avviso scala.this.Predef.augmentString("xx").r, che è un'applicazione del implicit def augmentStringpresente in Predef.scala.

scalac -Xprint: <phase> stamperà l'albero della sintassi dopo alcune fasi del compilatore. Per vedere le fasi disponibili usa scalac -Xshow -hase .

Questo è un ottimo modo per imparare cosa sta succedendo dietro le quinte.

Prova con

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

usando la fase typer per sentire davvero quanto sia utile.


30

Puoi definire le tue strutture di controllo. In realtà sono solo funzioni e oggetti e un po 'di zucchero sintattico, ma sembrano e si comportano come le cose reali.

Ad esempio, il codice seguente definisce dont {...} unless (cond)e 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
  }
}

Ora puoi fare quanto segue:

/* 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) 


Sarei curioso se qualcuno conoscesse un modo per definire blocchi if-then-else con altro facoltativo che controlla i tipi come quelli standard.
Philippe,

@Philippe: zif[A : Zero](cond: => Boolean)(t: => A): A = if(cond) t else mzero. Richiede Scalaz.
missingfaktor

26

@switch annotazione in Scala 2.8:

Un'annotazione da applicare a un'espressione di corrispondenza. Se presente, il compilatore verificherà che la corrispondenza sia stata compilata in un interruttore di tabella o lookups e genererà un errore se invece si compila in una serie di espressioni condizionali.

Esempio:

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

Non so se questo è davvero nascosto, ma lo trovo abbastanza carino.

I costruttori di caratteri che accettano 2 parametri di tipo possono essere scritti in notazione infissa

object Main {                                                                   
  class FooBar[A, B]

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

1
Bello! Posso immaginare che a volte sia utile per migliorare la leggibilità. Ad esempio var foo2barConverter: Foo ConvertTo Bar, renderebbe evidente l'ordine dei parametri di tipo.
Esko Luontola,

4
A volte lo faccio nel codice che utilizza PartialFunction in una certa misura: type ~> [A, B] = PartialFunction [A, B]
raichoo

24

Scala 2.8 ha introdotto argomenti predefiniti e denominati, che hanno reso possibile l'aggiunta di un nuovo metodo "copia" che Scala aggiunge alle classi dei casi. Se lo definisci:

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

e vuoi creare un nuovo Foo che è come un Foo esistente, solo con un valore "n" diverso, quindi puoi semplicemente dire:

foo.copy(n = 3)

3
ATTENZIONE: il metodo di copia non verrà sostituito se si eredita una classe di casi da un'altra. Quindi devi sovrascriverlo manualmente
Alexey

Correlati: modo più pulito per aggiornare strutture nidificate stackoverflow.com/q/3900307/203968
oluies

5
la classe case non è più consentita (Scala 2.8) ereditata da una classe case. Grazie signore della Scala per aver deprecato questa eredità empia.
Olle Kullberg,

24

in scala 2.8 puoi aggiungere @specialized alle tue classi / metodi generici. Ciò creerà versioni speciali della classe per i tipi primitivi (estendendo AnyVal) e risparmierà il costo di boxing / unboxing non necessari: class Foo[@specialized T]...

Puoi selezionare un sottoinsieme di AnyVals: class Foo[@specialized(Int,Boolean) T]...


1
C'è una spiegazione più lunga che potresti indicarmi? Mi piacerebbe saperne di più
Paweł Prażak,

23

Estensione della lingua. Ho sempre voluto fare qualcosa del genere in Java (impossibile). Ma a Scala posso avere:

  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
  }

e poi scrivi:

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

e prendi

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

23

Puoi designare un parametro call-by-name (EDITED: questo è diverso da un parametro pigro!) Per una funzione e non verrà valutato fino a quando non verrà utilizzato dalla funzione (EDIT: infatti, verrà rivalutato ogni volta che lo è Usato). Vedi questo faq per i dettagli

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
*/

Ho pensato che "x: => Bar" significava che x era una funzione che non prendeva parametri e restituiva una barra. Pertanto, "new bar (22)" è solo una funzione anonima e viene valutata come una funzione come qualsiasi altra funzione.
Alex Black,

1
"x: () => Bar" definisce la funzione xa che non accetta parametri e restituisce una barra. x: => La barra definisce x come chiamata per nome. Dai un'occhiata a scala.sygneca.com/faqs/… per maggiori dettagli
agilefall

3
Ciò che mostri sono i parametri di chiamata per nome. I parametri pigri non sono ancora implementati: lampsvn.epfl.ch/trac/scala/ticket/240
ArtemGr

Penso che puoi usarlo come parametro pigro se fai qualcosa come lazy val xx: Bar = xnel tuo metodo e da quel momento in poi usi solo xx.
Cristian Vrabie,

20

È possibile utilizzare locallyper introdurre un blocco locale senza causare problemi di inferenza punto e virgola.

Uso:

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 è definito in "Predef.scala" come:

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

Essendo in linea, non impone alcun sovraccarico aggiuntivo.



17

Inizializzazione anticipata:

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)

Produzione:

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

Istanziamo una classe interna anonima, inizializzando il valuecampo nel blocco, prima della with AbstractT2clausola. Questo garantisce l' valueinizializzazione prima dell'esecuzione del corpo di AbstractT2, come mostrato quando si esegue lo script.


1
Il costrutto si chiama "inizializzazione anticipata".
Randall Schulz,

17

Puoi comporre i tipi strutturali con la parola chiave "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

sintassi segnaposto per funzioni anonime

Dalle specifiche del linguaggio Scala:

SimpleExpr1 ::= '_'

Un'espressione (della categoria sintattica Expr) può contenere simboli di sottolineatura incorporati _nei punti in cui gli identificatori sono legali. Tale espressione rappresenta una funzione anonima in cui ricorrenze successive di trattini bassi indicano parametri successivi.

Da Scala Cambiamenti linguistici :

_ + 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)

Usando questo potresti fare qualcosa del tipo:

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

2
Questo dovrebbe essere indicato come "sintassi segnaposto per funzioni anonime". Implicito ha un significato distinto in Scala e non è collegato a questo.
retronym,

Il collegamento ha una relazione non ovvia con la risposta. "implicito" non è il termine corretto per questo. Come sopra dovrebbe essere "segnaposto".
Alain O'Dea,

2
Non è proprio "nascosto", ho visto questo uso in quasi tutti i tutorial su Scala che ho letto ... :-) Ma apprezzo la definizione formale che non ho ancora visto.
PhiLho,

@PhiLho forse era meno conosciuto nel 2009. Non lo so.
Eugene Yokota,

Ho perso la data originale, poiché viene mostrata solo l'ultima data di modifica. E bene, non tutte le funzioni spiegate in questo thread sono "nascoste". Discussione interessante e buona risposta comunque.
PhiLho,

16

Definizioni implicite, in particolare conversioni.

Ad esempio, supponi una funzione che formatterà una stringa di input per adattarla a una dimensione, sostituendola con "...":

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
}

Puoi usarlo con qualsiasi stringa e, naturalmente, usare il metodo toString per convertire qualsiasi cosa. Ma potresti anche scriverlo in questo modo:

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
}

E quindi, potresti passare classi di altri tipi facendo questo:

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

Ora puoi chiamare quella funzione passando un doppio:

sizeBoundedString(12345.12345D, 8)

L'ultimo argomento è implicito e viene passato automaticamente a causa della dichiarazione implicita. Inoltre, "s" viene trattato come una stringa all'interno di sizeBoundedString perché esiste una conversione implicita da esso a String.

Impliciti di questo tipo sono meglio definiti per tipi non comuni per evitare conversioni impreviste. Puoi anche passare esplicitamente una conversione, che verrà comunque utilizzata implicitamente all'interno di sizeBoundedString:

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

Puoi anche avere più argomenti impliciti, ma poi devi passarli tutti o non passare nessuno di essi. Esiste anche una sintassi del collegamento per le conversioni implicite:

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
}

Questo è usato esattamente allo stesso modo.

Gli impliciti possono avere qualsiasi valore. Possono essere utilizzati, ad esempio, per nascondere le informazioni sulla libreria. Prendi il seguente esempio, ad esempio:

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 questo esempio, chiamando "f" in un oggetto Y verrà inviato il registro al demone predefinito e su un'istanza di X al demone Daemon X. Ma chiamare g su un'istanza di X invierà il registro a DefaultDaemon esplicitamente fornito.

Mentre questo semplice esempio può essere riscritto con sovraccarico e stato privato, le implicazioni non richiedono uno stato privato e possono essere contestualizzate con le importazioni.


13

Forse non troppo nascosto, ma penso che sia utile:

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

Ciò genererà automaticamente un getter e un setter per il campo che corrisponde alla convenzione bean.

Ulteriore descrizione su developerworks


6
E puoi crearne una scorciatoia se la usi molto, ad esempio: import scala.reflect. {BeanProperty => BP}
Alexey

13

Argomenti impliciti nelle chiusure.

Un argomento di funzione può essere contrassegnato come implicito proprio come con i metodi. Nell'ambito del corpo della funzione il parametro implicito è visibile e idoneo alla risoluzione implicita:

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")
  })
}


12

I tipi di risultati dipendono dalla risoluzione implicita. Questo può darti una forma di spedizione multipla:

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

Questo potrebbe essere il caso, ma la sessione sopra è fuorviante. La definizione di foousi ache deve essere stata presente nell'ambiente prima dell'esecuzione di questi comandi. Suppongo che intendevi z.perform(x).
Daniel C. Sobral,

4

Equivalente alla Scala dell'inizializzatore Java a doppio controvento.

Scala consente di creare una sottoclasse anonima con il corpo della classe (il costruttore) contenente le istruzioni per inizializzare l'istanza di quella classe.

Questo modello è molto utile quando si creano interfacce utente basate su componenti (ad esempio Swing, Vaadin) in quanto consente di creare componenti dell'interfaccia utente e dichiarare le loro proprietà in modo più conciso.

Vedi http://spot.colorado.edu/~reids/papers/how-scala-experience-improved-our-java-development-reid-2011.pdf per ulteriori informazioni.

Ecco un esempio di creazione di un pulsante Vaadin:

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

3

Esclusione di membri dalle importdichiarazioni

Supponiamo che tu voglia usare un Loggerche contiene un printlne un printerrmetodo, ma vuoi usare solo quello per i messaggi di errore e mantenere il vecchio Predef.printlnper l'output standard. Potresti farlo:

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

ma se loggercontiene anche altri dodici metodi che desideri importare e utilizzare, diventa scomodo elencarli. Potresti invece provare:

import logger.{println => donotuseprintlnt, _}

ma questo "inquina" ancora l'elenco dei membri importati. Inserisci il carattere jolly über-potente:

import logger.{println => _, _}

e questo farà esattamente la cosa giusta ™.


2

requiremetodo (definito in Predef) che consente di definire ulteriori vincoli di funzione che verrebbero verificati durante il runtime. Immagina di sviluppare ancora un altro client Twitter e di limitare la lunghezza del tweet fino a 140 simboli. Inoltre non puoi pubblicare tweet vuoti.

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

Ora chiamare un post con argomento lunghezza inappropriato causerà un'eccezione:

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)

Puoi scrivere più requisiti o persino aggiungere una descrizione a ciascuno:

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

Ora le eccezioni sono dettagliate:

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

Un altro esempio è qui .


indennità

È possibile eseguire un'azione ogni volta che il requisito non riesce:

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

1
requirenon è una parola riservata. È solo un metodo definito in Predef.
missingfaktor il

1

I tratti con i abstract overridemetodi sono una caratteristica di Scala che non è ampiamente pubblicizzata come molti altri. L'intenzione di metodi con il abstract overridemodificatore è fare alcune operazioni e delegare la chiamata a super. Quindi questi tratti devono essere mescolati con implementazioni concrete dei loro abstract overridemetodi.

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

Mentre il mio esempio non è in realtà molto più di un AOP mans povero, ho usato questi tratti impilabili a mio piacimento per costruire istanze di interprete Scala con importazioni predefinite, associazioni personalizzate e percorsi di classe. I Tratti impilabili hanno reso possibile creare la mia fabbrica seguendo le linee new InterpreterFactory with JsonLibs with LuceneLibse quindi avere utili importazioni e variabili di ambito per gli script degli utenti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.