Quali sono le caratteristiche nascoste di Scala di cui ogni sviluppatore di Scala dovrebbe essere a conoscenza?
Una funzione nascosta per risposta, per favore.
Quali sono le caratteristiche nascoste di Scala di cui ogni sviluppatore di Scala dovrebbe essere a conoscenza?
Una funzione nascosta per risposta, per favore.
Risposte:
Ok, ho dovuto aggiungerne un altro. Ogni Regex
oggetto 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 val
o 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
( unapply
contro 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.
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 closeable
non è definito se non con un close
metodo
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
.
Estrattori che consentono di sostituire il if-elseif-else
codice 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.getCode
metodi come segue ..
class CashProduct {
def getCode = SyntheticCodes.Cash(this)
}
class ForwardProduct {
def getCode = SyntheticCodes.Forward(this)
}
Manifestazioni che sono una sorta di modo per ottenere le informazioni sui tipi in fase di esecuzione, come se Scala avesse tipi reificati.
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)
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 toString
metodo:
case class Person(name: String, age: Int) {
override def productPrefix = "person: "
}
// prints "person: (Aaron,28)" instead of "Person(Aaron, 28)"
println(Person("Aaron", 28))
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 augmentString
presente 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.
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)
zif[A : Zero](cond: => Boolean)(t: => A): A = if(cond) t else mzero
. Richiede Scalaz.
@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 {
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
}
}
var foo2barConverter: Foo ConvertTo Bar
, renderebbe evidente l'ordine dei parametri di tipo.
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)
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]...
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)
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
*/
lazy val xx: Bar = x
nel tuo metodo e da quel momento in poi usi solo xx
.
È possibile utilizzare locally
per 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.
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
value
campo nel blocco, prima dellawith AbstractT2
clausola. Questo garantisce l'value
inizializzazione prima dell'esecuzione del corpo diAbstractT2
, come mostrato quando si esegue lo script.
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
}
}
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))
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.
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
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")
})
}
Costruisci infinite strutture di dati con Scala's Stream
:
http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient
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
foo
usi a
che deve essere stata presente nell'ambiente prima dell'esecuzione di questi comandi. Suppongo che intendevi z.perform(x)
.
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"))
}
import
dichiarazioniSupponiamo che tu voglia usare un Logger
che contiene un println
e un printerr
metodo, ma vuoi usare solo quello per i messaggi di errore e mantenere il vecchio Predef.println
per l'output standard. Potresti farlo:
val logger = new Logger(...)
import logger.printerr
ma se logger
contiene 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 ™.
require
metodo (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 .
È 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
require
non è una parola riservata. È solo un metodo definito in Predef
.
I tratti con i abstract override
metodi sono una caratteristica di Scala che non è ampiamente pubblicizzata come molti altri. L'intenzione di metodi con il abstract override
modificatore è fare alcune operazioni e delegare la chiamata a super
. Quindi questi tratti devono essere mescolati con implementazioni concrete dei loro abstract override
metodi.
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 LuceneLibs
e quindi avere utili importazioni e variabili di ambito per gli script degli utenti.