Quelles sont les fonctionnalités cachées de Scala que chaque développeur Scala devrait être au courant?)?
Une fonctionnalité cachée par réponse, s'il vous plaît.
D'accord, je devais en ajouter un de plus. Chaque objet Regex
dans Scala a un extracteur (voir la réponse de oxbox_lakes ci-dessus) qui vous donne accès aux groupes de correspondance. Vous pouvez ainsi faire ce qui suit:
// 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 deuxième ligne semble confuse si vous n’êtes pas habitué à utiliser la correspondance de modèles et les extracteurs. Chaque fois que vous définissez un val
ou var
, ce qui vient après le mot-clé n'est pas simplement un identifiant mais plutôt un motif. C'est pourquoi cela fonctionne:
val (a, b, c) = (1, 3.14159, "Hello, world")
L'expression de droite crée un Tuple3[Int, Double, String]
qui peut correspondre au motif (a, b, c)
.
La plupart du temps, vos modèles utilisent des extracteurs appartenant à des objets singleton. Par exemple, si vous écrivez un motif comme
Some(value)
alors vous appelez implicitement l'extracteur Some.unapply
.
Mais vous pouvez aussi utiliser des instances de classe dans les patterns, et c'est ce qui se passe ici. Val regex est une instance de Regex
, et lorsque vous l'utilisez dans un motif, vous appelez implicitement regex.unapplySeq
(unapply
versus unapplySeq
dépasse le cadre de cette réponse), qui extrait les groupes de correspondance dans un Seq[String]
, dont les éléments sont affectés dans l’ordre aux variables année, mois et jour.
Définitions de type structurelles - c’est-à-dire un type décrit par les méthodes qu’il prend en charge. Par exemple:
object Closer {
def using(closeable: { def close(): Unit }, f: => Unit) {
try {
f
} finally { closeable.close }
}
}
Notez que le type du paramètre closeable
n’est pas défini autrement qu’il a une méthode close
.
Sans cette fonctionnalité, vous pouvez, par exemple, exprimer l'idée de mapper une fonction sur une liste pour renvoyer une autre liste ou de mapper une fonction sur un arbre pour renvoyer un autre arbre. Mais vous ne pouvez pas exprimer cette idée en général sans types plus élevés.
Avec les types supérieurs, vous pouvez capturer l’idée de n’importe quel type paramétré avec un autre type. Un constructeur de type qui prend un paramètre est dit de type (*->*)
. Par exemple, List
. Un constructeur de type qui retourne un autre constructeur de type est dit de type (*->*->*)
. Par exemple, Function1
. Mais dans Scala, nous avons des types plus élevés , de sorte que nous pouvons avoir des constructeurs de types paramétrés avec d'autres constructeurs de types. Donc, ils sont du genre ((*->*)->*)
.
Par exemple:
trait Functor[F[_]] {
def fmap[A, B](f: A => B, fa: F[A]): F[B]
}
Maintenant, si vous avez un Functor[List]
, Vous pouvez mapper des listes. Si vous avez un Functor[Tree]
, Vous pouvez cartographier des arbres. Mais plus important encore, si vous avez Functor[A]
pour tout A de type (*->*)
, vous pouvez mapper une fonction sur A
.
Extractors qui vous permettent de remplacer if-elseif-else
code de style avec des motifs. Je sais que ce ne sont pas exactement cachés mais j'utilise Scala depuis quelques mois sans vraiment en comprendre le pouvoir. Depuis (longtemps) exemple je peux remplacer:
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)
}
Avec cela, ce qui est beaucoup plus clair à mon avis
implicit val ps: ProductService = ...
val p = code match {
case SyntheticCodes.Cash(c) => c
case SyntheticCodes.Forward(f) => f
case _ => ps.lookupProductByRic(code)
}
Je dois faire un peu de travail en arrière-plan ...
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
}
}
Mais le jeu de loi en vaut la peine car il sépare un élément de la logique métier en un lieu sensible. Je peux mettre en œuvre mon Product.getCode
méthodes comme suit ..
class CashProduct {
def getCode = SyntheticCodes.Cash(this)
}
class ForwardProduct {
def getCode = SyntheticCodes.Forward(this)
}
Manifestes qui sont une sorte de moyen d'obtenir les informations de type à l'exécution, comme si Scala avait des types réifiés.
Les classes de cas se mélangent automatiquement à la caractéristique Produit, fournissant un accès indexé et non typé aux champs sans aucune réflexion:
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)
Cette fonctionnalité fournit également un moyen simplifié de modifier le résultat de la méthode toString
:
case class Person(name: String, age: Int) {
override def productPrefix = "person: "
}
// prints "person: (Aaron,28)" instead of "Person(Aaron, 28)"
println(Person("Aaron", 28))
Dans scala 2.8), vous pouvez utiliser des méthodes tail-récursives en utilisant le package scala.util.control.TailCalls (en fait, il s'agit de trampolining).
Un exemple:
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)
Ce n'est pas exactement caché, mais c'est certainement une fonctionnalité sous-annoncée: scalac -Xprint.
À titre d’illustration, considérez la source suivante:
class A { "xx".r }
La compilation avec scalac -Xprint: typer génère:
package <empty> {
class A extends Java.lang.Object with ScalaObject {
def this(): A = {
A.super.this();
()
};
scala.this.Predef.augmentString("xx").r
}
}
Notez que scala.this.Predef.augmentString("xx").r
, qui est une application du implicit def augmentString
Présent dans Predef.scala.
scalac -Xprint: <phase> imprimera l'arbre de syntaxe après une phase de compilation. Pour voir les phases disponibles, utilisez scalac -Xshow-phases.
C'est un excellent moyen d'apprendre ce qui se passe dans les coulisses.
Essayer avec
case class X(a:Int,b:String)
en utilisant la phase dactylographe pour vraiment sentir à quel point il est utile.
Vous pouvez définir vos propres structures de contrôle. Ce ne sont que des fonctions, des objets et du sucre syntaxique, mais ils ressemblent et se comportent comme des choses réelles.
Par exemple, le code suivant définit dont {...} unless (cond)
et 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
}
}
Maintenant, vous pouvez faire ce qui suit:
/* 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)
@switch
annotation dans Scala 2.8:
Une annotation à appliquer à une expression de correspondance. S'il est présent, le compilateur vérifiera que la correspondance a été compilée dans un commutateur de table ou un commutateur de recherche, et émettra une erreur s'il compile à la place une série d'expressions conditionnelles.
Exemple:
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 {
Je ne sais pas si c'est vraiment caché, mais je trouve ça plutôt sympa.
Les constructeurs de types prenant 2 paramètres de type peuvent être écrits en notation infixe
object Main {
class FooBar[A, B]
def main(args: Array[String]): Unit = {
var x: FooBar[Int, BigInt] = null
var y: Int FooBar BigInt = null
}
}
in scala 2.8, vous pouvez ajouter @specialized à vos classes/méthodes génériques. Ceci créera des versions spéciales de la classe pour les types primitifs (extension de AnyVal) et permettra d'économiser le coût de la boxe/du déballage inutile. : class Foo[@specialized T]...
Vous pouvez sélectionner un sous-ensemble de AnyVals: class Foo[@specialized(Int,Boolean) T]...
Scala 2.8 a introduit les arguments par défaut et nommés, qui ont rendu possible l’ajout d’une nouvelle méthode "copy" que Scala ajoute aux classes de cas. Si vous définissez ceci:
case class Foo(a: Int, b: Int, c: Int, ... z:Int)
et vous voulez créer un nouveau Foo qui ressemble à un Foo existant, mais avec une valeur "n" différente, alors vous pouvez simplement dire:
foo.copy(n = 3)
Étendre la langue. J'ai toujours voulu faire quelque chose comme ceci dans Java (ne pouvait pas). Mais dans Scala je peux avoir:
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
}
et ensuite écrire:
val numbers = List(12, 42, 3, 11, 6, 3, 77, 44)
val sorted = timed { // "timed" is a new "keyword"!
numbers.sortWith(_<_)
}
println(sorted)
et obtenir
Executed in: 6.410311 millisec
List(3, 3, 6, 11, 12, 42, 44, 77)
Vous pouvez désigner un paramètre appel par nom (EDITED: il s'agit d'un paramètre différent d'un paramètre fainéant!) Et il ne sera pas évalué avant d'être utilisé par la fonction (EDIT: en fait, il sera réévalué à chaque fois. utilisé). Voir this faq pour plus de détails
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
*/
Vous pouvez utiliser locally
pour introduire un bloc local sans causer de problèmes d'inférence point-virgule.
tilisation:
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
est défini dans "Predef.scala" comme:
@inline def locally[T](x: T): T = x
En ligne, cela n'impose pas de frais généraux supplémentaires.
Vous pouvez composer des types de structure avec le mot clé '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
}
}
syntaxe d'espace réservé pour les fonctions anonymes
De la Scala Spécification de la langue:
SimpleExpr1 ::= '_'
Une expression (de catégorie syntaxique
Expr
) peut contenir des caractères de soulignement incorporés_
aux endroits où les identifiants sont légaux. Une telle expression représente une fonction anonyme où les occurrences suivantes de traits de soulignement désignent des paramètres successifs.
De changements de langue Scala :
_ + 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)
En utilisant ceci, vous pourriez faire quelque chose comme:
def filesEnding(query: String) =
filesMatching(_.endsWith(query))
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)
Sortie:
In AbstractT2:
AbstractT2: value = 10, inverse = 0.1
c2c.value = 10, inverse = 0.1
Nous instancions une classe interne anonyme, en initialisant le champ
value
du bloc, avant lewith AbstractT2
clause. Cela garantit quevalue
est initialisé avant le corps deAbstractT2
est exécuté, comme indiqué lorsque vous exécutez le script.
Définitions implicites, en particulier les conversions.
Par exemple, supposons une fonction qui formatera une chaîne d’entrée pour s’adapter à une taille, en remplaçant son milieu par "...":
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
}
Vous pouvez l'utiliser avec n'importe quelle chaîne et, bien sûr, utiliser la méthode toString pour convertir n'importe quoi. Mais vous pouvez aussi l'écrire comme ceci:
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
}
Et ensuite, vous pourriez passer des classes d'autres types en procédant comme suit:
implicit def double2String(d: Double) = d.toString
Vous pouvez maintenant appeler cette fonction en passant un double:
sizeBoundedString(12345.12345D, 8)
Le dernier argument est implicite et est passé automatiquement à cause de la déclaration implicite de. De plus, "s" est traité comme une chaîne dans sizeBoundedString car il existe une conversion implicite de celle-ci en chaîne.
Les implications de ce type sont mieux définies pour les types rares afin d'éviter les conversions inattendues. Vous pouvez également explicitement passer une conversion, qui sera toujours utilisée implicitement dans sizeBoundedString:
sizeBoundedString(1234567890L, 8)((l : Long) => l.toString)
Vous pouvez également avoir plusieurs arguments implicites, mais vous devez ensuite les transmettre tous ou ne les transmettre aucun. Il existe également une syntaxe de raccourci pour les conversions implicites:
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
}
Ceci est utilisé exactement de la même manière.
Les implications peuvent avoir n'importe quelle valeur. Ils peuvent être utilisés, par exemple, pour masquer les informations de la bibliothèque. Prenons l'exemple suivant, par exemple:
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")
}
}
Dans cet exemple, l'appel de "f" dans un objet Y enverra le journal au démon par défaut et, sur une instance de X, au démon X du démon. Mais appeler g sur une instance de X enverra le journal au DefaultDaemon explicitement indiqué.
Bien que cet exemple simple puisse être réécrit avec surcharge et état privé, les implications ne nécessitent pas d'état privé et peuvent être mises en contexte avec les importations.
Peut-être pas trop caché, mais je pense que cela est utile:
@scala.reflect.BeanProperty
var firstName:String = _
Cela générera automatiquement un getter et un setter pour le champ qui correspond à la convention de bean.
Description supplémentaire sur developerworks
Arguments implicites dans les fermetures.
Un argument de fonction peut être marqué comme implicite, comme pour les méthodes. Dans le cadre du corps de la fonction, le paramètre implicite est visible et éligible pour une résolution implicite:
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")
})
}
Construisez des structures de données infinies avec Stream
s: de Scala http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient
Les types de résultats dépendent de la résolution implicite. Cela peut vous donner une forme d'envoi multiple:
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
Scala vous permet de créer une sous-classe anonyme avec le corps de la classe (le constructeur) contenant des instructions pour initialiser l'instance de cette classe.
Ce modèle est très utile lors de la création d'interfaces utilisateur basées sur des composants (par exemple, Swing, Vaadin), car il permet de créer des composants d'interface utilisateur et de déclarer leurs propriétés de manière plus concise.
Voir http://spot.colorado.edu/~reids/papers/how-scala-experience-improved-our-Java-development-reid-2011.pdf pour plus d'informations.
Voici un exemple de création d'un bouton Vaadin:
val button = new Button("Click me"){
setWidth("20px")
setDescription("Click on this")
setIcon(new ThemeResource("icons/ok.png"))
}
import
Supposons que vous souhaitiez utiliser un Logger
contenant une méthode println
et un printerr
, mais que vous ne vouliez utiliser que celui-ci pour les messages d'erreur et conserver le bon vieux Predef.println
pour la sortie standard. Vous pourriez faire ceci:
val logger = new Logger(...)
import logger.printerr
mais si logger
contient également douze autres méthodes que vous souhaitez importer et utiliser, il devient peu pratique de les répertorier. Vous pourriez plutôt essayer:
import logger.{println => donotuseprintlnt, _}
mais cela "pollue" encore la liste des membres importés. Entrez le caractère générique über-puissant:
import logger.{println => _, _}
et cela fera juste la bonne chose ™.
require
méthode (définie dans Predef
) permettant de définir des contraintes de fonction supplémentaires à vérifier lors de l'exécution. Imaginez que vous développiez un autre client Twitter et que vous deviez limiter la longueur du tweet à 140 symboles. De plus, vous ne pouvez pas publier de Tweet vide.
def post(Tweet: String) = {
require(Tweet.length < 140 && Tweet.length > 0)
println(Tweet)
}
Appeler maintenant avec un argument de longueur inappropriée provoquera une exception:
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)
Vous pouvez écrire plusieurs exigences ou même ajouter une description à chacune:
def post(Tweet: String) = {
require(Tweet.length > 0, "too short message")
require(Tweet.length < 140, "too long message")
println(Tweet)
}
Les exceptions sont maintenant verbeuses:
scala> post("")
Java.lang.IllegalArgumentException: requirement failed: too short message
at scala.Predef$.require(Predef.scala:157)
at .post(<console>:8)
Un autre exemple est ici .
Vous pouvez effectuer une action chaque fois que l'exigence échoue:
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
Les traits avec les méthodes abstract override
sont une fonctionnalité de Scala qui n'est pas aussi largement annoncé que beaucoup d'autres. Le but des méthodes avec le abstract override
modificateur consiste à effectuer certaines opérations et à déléguer l'appel à super
. Ces caractéristiques doivent ensuite être combinées avec des implémentations concrètes de leurs méthodes abstract override
.
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
Bien que mon exemple ne soit vraiment pas beaucoup plus qu'un AOP d'un homme pauvre, j'ai utilisé ces traits empilables beaucoup à mon goût pour construire Scala instances d'interprète avec des importations prédéfinies, des liaisons personnalisées et des chemins de classes. Les Traits empilables ont permis de créer ma fabrique suivant les lignes de new InterpreterFactory with JsonLibs with LuceneLibs
et d'avoir ainsi des importations utiles et des variables de portée pour les scripts de l'utilisateur.