scala hidden-features

Características ocultas de Scala



hidden-features (28)

¿Cuáles son las características ocultas de Scala que todo desarrollador de Scala debería conocer?

Una función oculta por respuesta, por favor.


El equivalente de Scala del inicializador de llave doble Java.

Scala le permite crear una subclase anónima con el cuerpo de la clase (el constructor) que contiene instrucciones para inicializar la instancia de esa clase.

Este patrón es muy útil cuando se construyen interfaces de usuario basadas en componentes (por ejemplo, Swing, Vaadin), ya que permite crear componentes de UI y declarar sus propiedades de forma más concisa.

Consulte http://spot.colorado.edu/~reids/papers/how-scala-experience-improved-our-java-development-reid-2011.pdf para obtener más información.

Aquí hay un ejemplo de cómo crear un botón Vaadin:

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


Excluir miembros de estados de import

Supongamos que desea utilizar un Logger que contenga un println y un método de printerr , pero solo desea usar uno para los mensajes de error, y mantener el antiguo Predef.println para la salida estándar. Podrías hacer esto:

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

pero si el logger también contiene otros doce métodos que le gustaría importar y usar, resulta inconveniente enumerarlos. En su lugar, podría intentar:

import logger.{println => donotuseprintlnt, _}

pero esto todavía "contamina" la lista de miembros importados. Ingresa el comodín über-poderoso:

import logger.{println => _, _}

y eso hará lo correcto ™.


Polimorfismo de tipo constructor (también conocido como tipos de alto grado)

Sin esta característica, puede, por ejemplo, expresar la idea de mapear una función sobre una lista para devolver otra, o mapear una función sobre un árbol para devolver otro árbol. Pero no se puede expresar esta idea en general sin tipos superiores.

Con tipos superiores, puede capturar la idea de cualquier tipo que esté parametrizado con otro tipo. Se dice que un constructor de tipo que toma un parámetro es de tipo (*->*) . Por ejemplo, List . Un constructor de tipo que devuelve otro constructor de tipo se dice que es de tipo (*->*->*) . Por ejemplo, Function1 . Pero en Scala, tenemos tipos superiores , por lo que podemos tener constructores de tipo que se parametricen con otros constructores de tipo. Entonces son de tipos como ((*->*)->*) .

Por ejemplo:

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

Ahora, si tiene un Functor[List] , puede mapear sobre listas. Si tiene un Functor[Tree] , puede mapear árboles. Pero más importante aún, si tiene Functor[A] para cualquier A de tipo (*->*) , puede asignar una función a A


Argumentos implícitos en cierres.

Un argumento de función se puede marcar como implícito al igual que con los métodos. Dentro del alcance del cuerpo de la función, el parámetro implícito es visible y elegible para la resolución implícita:

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



Definiciones de tipos estructurales : es decir, un tipo descrito por qué métodos admite. Por ejemplo:

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

Tenga en cuenta que el tipo del parámetro que se closeable no está definido, salvo que tiene un método de close


Definiciones implícitas, particularmente conversiones.

Por ejemplo, asuma una función que formateará una cadena de entrada para que se ajuste a un tamaño, reemplazando la mitad 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 }

Puede usarlo con cualquier String y, por supuesto, usar el método toString para convertir cualquier cosa. Pero también podrías escribirlo así:

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 }

Y luego, podría pasar clases de otros tipos haciendo esto:

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

Ahora puedes llamar a esa función pasando un doble:

sizeBoundedString(12345.12345D, 8)

El último argumento es implícito y se pasa automáticamente debido a la declaración implícita. Además, "s" se está tratando como una Cadena dentro de sizeBoundedString porque hay una conversión implícita de esto a String.

Los implicits de este tipo se definen mejor para tipos poco comunes para evitar conversiones inesperadas. También puede pasar explícitamente una conversión, y todavía se usará implícitamente dentro de sizeBoundedString:

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

También puede tener múltiples argumentos implícitos, pero luego debe pasarlos a todos o no pasar ninguno de ellos. También hay una sintaxis de acceso directo para conversiones implícitas:

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 }

Esto se usa exactamente de la misma manera.

Los Implicits pueden tener cualquier valor. Se pueden usar, por ejemplo, para ocultar información de la biblioteca. Tome el siguiente ejemplo, por ejemplo:

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

En este ejemplo, al llamar a "f" en un objeto Y se enviará el registro al daemon predeterminado, y en una instancia de X al Daemon Daemon X. Pero al llamar g a una instancia de X enviará el registro al DefaultDaemon explícitamente dado.

Si bien este ejemplo simple se puede volver a escribir con sobrecarga y estado privado, las implicidades no requieren un estado privado y pueden ponerse en contexto con las importaciones.


En scala 2.8 puede tener métodos recursivos de cola utilizando el paquete scala.util.control.TailCalls (de hecho es trampolín).

Un ejemplo:

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)


Extendiendo el idioma. Siempre quise hacer algo como esto en Java (no pude). Pero en Scala puedo tener:

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 }

y luego escribe:

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

y obten

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


Las clases de casos mezclan automáticamente el rasgo del Producto, proporcionando acceso indexado sin tipo a los campos sin ninguna reflexión:

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)

Esta función también proporciona una forma simplificada de alterar el resultado del método 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))


Los rasgos con métodos de abstract override son una característica de Scala que no se publicita ampliamente como muchos otros. La intención de los métodos con el modificador de abstract override es realizar algunas operaciones y delegar la llamada a super . Entonces estos rasgos deben mezclarse con implementaciones concretas de sus métodos 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

Si bien mi ejemplo no es mucho más que un AOP de personas pobres, utilicé estos rasgos apilables muy a mi gusto para construir instancias de intérpretes de Scala con importaciones predefinidas, enlaces personalizados y paletas de clase. Los Stackable Traits hicieron posible crear mi fábrica a lo largo de las líneas de la new InterpreterFactory with JsonLibs with LuceneLibs y luego tener importaciones útiles y varibles de alcance para las secuencias de comandos de los usuarios.


Los tipos de resultados dependen de la resolución implícita. Esto puede darle una forma de despacho múltiple:

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


No está exactamente oculto, pero ciertamente es una función que no está publicitada: scalac -Xprint .

Como ilustración del uso, considere la siguiente fuente:

class A { "xx".r }

Compilando esto con scalac -Xprint: typer outputs:

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

Observe scala.this.Predef.augmentString("xx").r , que es una aplicación de la implicit def augmentString presente en Predef.scala.

scalac -Xprint: <fase> imprimirá el árbol de sintaxis después de alguna fase de compilación. Para ver las fases disponibles, use scalac -Xshow-phases .

Esta es una gran manera de aprender lo que está sucediendo detrás de escena.

Prueba con

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

usando la fase typer para sentir realmente lo útil que es.


No sé si esto está realmente escondido, pero me parece bastante agradable.

Los constructores de tipos que toman 2 parámetros de tipo pueden escribirse en notación infija

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


Puede componer tipos estructurales con la palabra clave ''con''

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


Puede definir sus propias estructuras de control. En realidad son solo funciones y objetos y algo de azúcar sintáctico, pero se ven y se comportan como si fueran reales.

Por ejemplo, el siguiente código define dont {...} unless (cond) y 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 } }

Ahora puedes hacer lo siguiente:

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


Puede designar un parámetro de llamada por nombre (EDITADO: ¡esto es diferente de un parámetro perezoso!) De una función y no se evaluará hasta que lo use la función (EDITAR: de hecho, se volverá a evaluar cada vez que se use). usado). Vea esta pregunta frecuente para más detalles

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


Puede usar locally para introducir un bloque local sin causar problemas de inferencia de punto y coma.

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 se define en "Predef.scala" como:

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

Al estar en línea, no impone ningún gasto adicional.


Quizás no esté demasiado oculto, pero creo que esto es útil:

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

Esto generará automáticamente un getter y setter para el campo que coincida con la convención de beans.

Más descripción en developerworks


Scala 2.8 introdujo argumentos por defecto y nombrados, lo que hizo posible la adición de un nuevo método de "copia" que Scala agrega a las clases de casos. Si defines esto:

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

y desea crear un nuevo Foo que sea como un Foo existente, solo con un valor "n" diferente, entonces usted puede simplemente decir:

foo.copy(n = 3)


en scala 2.8 puede agregar @specialized a sus clases / métodos genéricos. Esto creará versiones especiales de la clase para tipos primitivos (ampliando AnyVal) y ahorrará el costo de boxeo / class Foo[@specialized T]... : class Foo[@specialized T]...

Puede seleccionar un subconjunto de AnyVals: class Foo[@specialized(Int,Boolean) T]...


Inicialización temprana:

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)

Salida:

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

Instanciamos una clase interna anónima, inicializando el campo de value en el bloque, antes de la cláusula with AbstractT2 . Esto garantiza que el value se inicialice antes de que se ejecute el cuerpo de AbstractT2 , como se muestra al ejecutar el script.


Manifests que son una especie de forma de obtener la información de tipo en tiempo de ejecución, como si Scala hubiera reificado los tipos.


@switch anotación en Scala 2.8:

Una anotación que se aplicará a una expresión de coincidencia. Si está presente, el compilador verificará que la coincidencia se haya compilado con un conmutador de tabla o un conmutador de búsqueda, y emitirá un error si en su lugar se compila en una serie de expresiones condicionales.

Ejemplo:

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 {


Extractors que le permiten reemplazar el código de estilo sucio if-elseif-else con patrones. Sé que estos no están exactamente ocultos, pero he estado usando Scala durante unos meses sin realmente entender su poder. Para (un largo) ejemplo, puedo reemplazar:

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 esto, que es mucho más claro en mi opinión

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

Tengo que hacer un poco de trabajo en el fondo ...

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

Pero el trabajo de campo lo vale por el hecho de que separa una parte de la lógica de negocios en un lugar sensato. Puedo implementar mis métodos Product.getCode siguiente manera ..

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


require método (definido en Predef ) que le permite definir restricciones de función adicionales que se verificarán durante el tiempo de ejecución. Imagine que está desarrollando otro cliente de Twitter y necesita limitar la longitud del tweet hasta 140 símbolos. Además, no puedes publicar un tweet vacío.

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

Ahora, llamar a la publicación con argumento de longitud inadecuada provocará una excepción:

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)

Puede escribir múltiples requisitos o incluso agregar una descripción a cada uno:

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

Ahora las excepciones son detalladas

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

Un ejemplo más está here .

Prima

Puede realizar una acción cada vez que el requisito falla:

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


sintaxis de marcador de posición para funciones anónimas

De la especificación del lenguaje Scala:

SimpleExpr1 ::= ''_''

Una expresión (de la categoría sintáctica Expr ) puede contener símbolos de subrayado incrustados _ en lugares donde los identificadores son legales. Dicha expresión representa una función anónima donde las apariciones posteriores de guiones bajos denotan parámetros sucesivos.

De Scala Language Changes :

_ + 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 esto podrías hacer algo como:

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


De acuerdo, tuve que agregar uno más. Cada objeto Regex en Scala tiene un extractor (ver respuesta de oxbox_lakes arriba) que le da acceso a los grupos de partidos. Entonces puedes hacer algo como:

// 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 segunda línea parece confusa si no está acostumbrado a usar coincidencias de patrones y extractores. Siempre que defina un val o var , lo que viene después de la palabra clave no es simplemente un identificador, sino un patrón. Es por eso que esto funciona:

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

La expresión de la mano derecha crea un Tuple3[Int, Double, String] que puede coincidir con el patrón (a, b, c) .

La mayoría de las veces tus patrones usan extractores que son miembros de objetos singleton. Por ejemplo, si escribe un patrón como

Some(value)

entonces estás llamando implícitamente al extractor Some.unapply .

Pero también puedes usar instancias de clases en patrones, y eso es lo que está sucediendo aquí. La valgegex es una instancia de Regex , y cuando la usas en un patrón, estás llamando implícitamente a regex.unapplySeq ( unapply versus unapplySeq está más allá del alcance de esta respuesta), que extrae los grupos de coincidencia en un Seq[String] , cuyos elementos se asignan para las variables año, mes y día.