¿Cuáles son las características ocultas de Scala que todo desarrollador de Scala debe tener en cuenta?
Una característica oculta por respuesta, por favor.
¿Cuáles son las características ocultas de Scala que todo desarrollador de Scala debe tener en cuenta?
Una característica oculta por respuesta, por favor.
Respuestas:
Bien, tuve que agregar uno más. Cada Regex
objeto en Scala tiene un extractor (vea la respuesta de oxbox_lakes arriba) que le da acceso a los grupos de coincidencias. 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 patrones coincidentes 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. Por eso funciona esto:
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 sus patrones usan extractores que son miembros de objetos singleton. Por ejemplo, si escribes un patrón como
Some(value)
entonces estás llamando implícitamente al extractor Some.unapply
.
Pero también puede usar instancias de clase en patrones, y eso es lo que está sucediendo aquí. Val regex es una instancia de Regex
, y cuando lo usa en un patrón, está llamando implícitamente regex.unapplySeq
( unapply
versus unapplySeq
está más allá del alcance de esta respuesta), que extrae los grupos de coincidencia en a Seq[String]
, cuyos elementos se asignan para las variables año, mes y día.
Definiciones de tipo estructural , es decir, un tipo descrito por los métodos que admite. Por ejemplo:
object Closer {
def using(closeable: { def close(): Unit }, f: => Unit) {
try {
f
} finally { closeable.close }
}
}
Observe que el tipo del parámetro closeable
no se define más que si tiene un close
método
Sin esta característica, puede, por ejemplo, expresar la idea de asignar una función sobre una lista para devolver otra lista, o asignar una función sobre un árbol para devolver otro árbol. Pero no se puede expresar esta idea en general sin clases superiores.
Con tipos más altos, 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
. Se dice que un constructor de tipo que devuelve otro constructor de tipo es de tipo (*->*->*)
. Por ejemplo, Function1
. Pero en Scala, tenemos tipos más altos , por lo que podemos tener constructores de tipos que se parametrizan con otros constructores de tipos. Entonces son de tipo 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 asignar sobre listas. Si tiene un Functor[Tree]
, puede mapear sobre árboles. Pero lo más importante, si tiene Functor[A]
alguna A de tipo(*->*)
, puede asignar una función A
.
Extractores que le permiten reemplazar el if-elseif-else
código de estilo desordenado con patrones. Sé que estos no están exactamente ocultos, pero he estado usando Scala durante unos meses sin comprender realmente el poder de ellos. 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 de fondo 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 preliminar vale la pena por el hecho de que separa una pieza de lógica empresarial en un lugar sensible. Puedo implementar mis Product.getCode
métodos de la siguiente manera ...
class CashProduct {
def getCode = SyntheticCodes.Cash(this)
}
class ForwardProduct {
def getCode = SyntheticCodes.Forward(this)
}
Manifiestos que son una forma de obtener la información de tipo en tiempo de ejecución, como si Scala hubiera reificado los tipos.
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)
Las clases de casos mezclan automáticamente el rasgo del Producto, proporcionando acceso indexado y 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 característica también proporciona una forma simplificada de alterar la salida del toString
método:
case class Person(name: String, age: Int) {
override def productPrefix = "person: "
}
// prints "person: (Aaron,28)" instead of "Person(Aaron, 28)"
println(Person("Aaron", 28))
No está exactamente oculto, pero ciertamente es una característica poco anunciada: scalac -Xprint .
Como ilustración del uso, considere la siguiente fuente:
class A { "xx".r }
Compilando esto con scalac -Xprint: salidas typer :
package <empty> {
class A extends java.lang.Object with ScalaObject {
def this(): A = {
A.super.this();
()
};
scala.this.Predef.augmentString("xx").r
}
}
Aviso scala.this.Predef.augmentString("xx").r
, que es una aplicación del implicit def augmentString
presente en Predef.scala.
scalac -Xprint: <phase> imprimirá el árbol de sintaxis después de alguna fase del compilador. Para ver las fases disponibles, use scalac -Xshow-phase .
Esta es una excelente manera de aprender lo que está sucediendo detrás de escena.
Probar con
case class X(a:Int,b:String)
usando la fase typer para sentir realmente lo útil que es.
Puede definir sus propias estructuras de control. Realmente son solo funciones y objetos y algo de azúcar sintáctica, 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)
zif[A : Zero](cond: => Boolean)(t: => A): A = if(cond) t else mzero
. Requiere Scalaz.
@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 en un conmutador de tabla o 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 {
No sé si esto está realmente oculto, 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
}
}
var foo2barConverter: Foo ConvertTo Bar
, el orden de los parámetros de tipo sería evidente.
Scala 2.8 introdujo argumentos predeterminados y con nombre, lo que hizo posible la adición de un nuevo método de "copia" que Scala agrega a las clases de casos. Si define 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 que con un valor "n" diferente, entonces simplemente puede 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 (extendiendo AnyVal) y ahorrará el costo de boxeo / unboxing innecesario:
class Foo[@specialized T]...
Puede seleccionar un subconjunto de AnyVals:
class Foo[@specialized(Int,Boolean) T]...
Extendiendo el lenguaje. Siempre quise hacer algo así 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)
Puede designar un parámetro de llamada por nombre (EDITADO: ¡esto es diferente a un parámetro diferido!) A una función y no se evaluará hasta que la función lo use (EDITAR: de hecho, se volverá a evaluar cada vez que se usado). Vea este faq 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
*/
lazy val xx: Bar = x
en tu método y desde ese momento solo lo usas xx
.
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.
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
value
campo en el bloque, antes de lawith AbstractT2
cláusula. Esto garantiza quevalue
se inicializa antes deAbstractT2
ejecutar el cuerpo de , como se muestra cuando ejecuta el script.
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
}
}
sintaxis de marcador de posición para funciones anónimas
De la especificación del lenguaje Scala:
SimpleExpr1 ::= '_'
Una expresión (de categoría sintáctica
Expr
) puede contener símbolos de subrayado incrustados_
en lugares donde los identificadores son legales. Tal expresión representa una función anónima donde las apariciones subsiguientes de guiones bajos denotan parámetros sucesivos.
_ + 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))
Definiciones implícitas, particularmente conversiones.
Por ejemplo, suponga una función que formateará una cadena de entrada para que se ajuste a un tamaño, reemplazando el centro de la misma 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 usar eso con cualquier Cadena 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" está siendo tratada como una cadena dentro de sizeBoundedString porque hay una conversión implícita de ella a cadena.
Los impedimentos de este tipo están mejor definidos 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 todos o no pasar ninguno de ellos. También hay una sintaxis de acceso directo para las 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, llamar a "f" en un objeto Y enviará el registro al daemon predeterminado y, en una instancia de X, al daemon X de Daemon. Pero llamar a g en una instancia de X enviará el registro al DefaultDaemon dado explícitamente.
Si bien este ejemplo simple puede reescribirse con sobrecarga y estado privado, las implicaciones no requieren un estado privado y pueden ponerse en contexto con las importaciones.
Tal vez 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 bean.
Descripción adicional en developerworks
Argumentos implícitos en los 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")
})
}
Cree estructuras de datos infinitas con los Stream
s de Scala :
http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient
Los tipos de resultados dependen de la resolución implícita. Esto puede darle una forma de envío 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
foo
utiliza un a
que debe haber estado presente en el entorno antes de la ejecución de estos comandos. Supongo que quisiste decir z.perform(x)
.
Scala le permite crear una subclase anónima con el cuerpo de la clase (el constructor) que contiene declaraciones para inicializar la instancia de esa clase.
Este patrón es muy útil cuando se crean interfaces de usuario basadas en componentes (por ejemplo, Swing, Vaadin), ya que permite crear componentes de IU y declarar sus propiedades de manera 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"))
}
import
declaracionesSuponga que desea utilizar un método Logger
que contenga ay println
un printerr
método, pero solo desea utilizar el que se usa para mensajes de error y mantener el valor antiguo Predef.println
para la salida estándar. Podrías hacer esto:
val logger = new Logger(...)
import logger.printerr
pero si 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. Ingrese el comodín über-poderoso:
import logger.{println => _, _}
y eso hará lo correcto ™.
require
Método (definido en Predef
) que le permite definir restricciones de funciones adicionales que se comprobarían durante el tiempo de ejecución. Imagine que está desarrollando otro cliente de Twitter y necesita limitar la longitud del tweet a 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 post con un argumento de longitud inapropiada causará 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á aquí .
Puede realizar una acción cada vez que falle el requisito:
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
No es una palabra reservada. Es solo un método definido en Predef
.
Los rasgos con abstract override
métodos son una característica en Scala que no se anuncia tan ampliamente como muchos otros. La intención de los métodos con el abstract override
modificador es hacer algunas operaciones y delegar la llamada a super
. Luego, estos rasgos deben mezclarse con implementaciones concretas de sus abstract override
métodos.
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 pobre para hombres, utilicé estos Rasgos apilables para mi gusto para construir instancias de intérpretes Scala con importaciones predefinidas, enlaces personalizados y patrones de clase. Los Rasgos Apilables hicieron posible crear mi fábrica en la línea de new InterpreterFactory with JsonLibs with LuceneLibs
y luego tener importaciones útiles y variables de alcance para los scripts de los usuarios.