Quais são os recursos ocultos do Scala que todo desenvolvedor de Scala deve conhecer?
Um recurso oculto por resposta, por favor.
Quais são os recursos ocultos do Scala que todo desenvolvedor de Scala deve conhecer?
Um recurso oculto por resposta, por favor.
Respostas:
Ok, eu tive que adicionar mais um. Todo Regex
objeto no Scala possui um extrator (veja a resposta de oxbox_lakes acima) que dá acesso aos grupos de correspondência. Então você pode fazer 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"
A segunda linha parece confusa se você não está acostumado a usar correspondência e extratores de padrões. Sempre que você define um val
ou var
, o que vem depois da palavra-chave não é simplesmente um identificador, mas um padrão. É por isso que isso funciona:
val (a, b, c) = (1, 3.14159, "Hello, world")
A expressão da mão direita cria um Tuple3[Int, Double, String]
que pode corresponder ao padrão (a, b, c)
.
Na maioria das vezes, seus padrões usam extratores que são membros de objetos singleton. Por exemplo, se você escrever um padrão como
Some(value)
então você está implicitamente chamando o extrator Some.unapply
.
Mas você também pode usar instâncias de classe em padrões, e é isso que está acontecendo aqui. O val regex é uma instância de Regex
, e quando você o usa em um padrão, você está implicitamente chamando regex.unapplySeq
( unapply
versus unapplySeq
está além do escopo desta resposta), que extrai os grupos de correspondência em a Seq[String]
, cujos elementos são atribuídos para as variáveis ano, mês e dia.
Definições de tipo estrutural - ou seja, um tipo descrito por quais métodos ele suporta. Por exemplo:
object Closer {
def using(closeable: { def close(): Unit }, f: => Unit) {
try {
f
} finally { closeable.close }
}
}
Observe que o tipo do parâmetro closeable
não está definido, exceto por ter um close
método
Sem esse recurso, você pode, por exemplo, expressar a idéia de mapear uma função sobre uma lista para retornar outra lista ou mapear uma função sobre uma árvore para retornar outra árvore. Mas você não pode expressar essa ideia geralmente sem tipos mais elevados.
Com tipos mais altos, você pode capturar a ideia de qualquer tipo parametrizado com outro tipo. Diz-se que um construtor de tipos que usa um parâmetro é do tipo (*->*)
. Por exemplo List
,. Diz-se que um construtor de tipos que retorna outro construtor de tipos é do tipo (*->*->*)
. Por exemplo Function1
,. Porém, no Scala, temos tipos mais altos , portanto, podemos ter construtores de tipo que são parametrizados com outros construtores de tipo. Então eles são do tipo ((*->*)->*)
.
Por exemplo:
trait Functor[F[_]] {
def fmap[A, B](f: A => B, fa: F[A]): F[B]
}
Agora, se você tiver um Functor[List]
, poderá mapear as listas. Se você tiver um Functor[Tree]
, poderá mapear sobre árvores. Mas o mais importante é que, se você tiver Functor[A]
algum tipo A(*->*)
, poderá mapear uma função A
.
Extratores que permitem substituir o if-elseif-else
código de estilo confuso por padrões. Sei que eles não estão exatamente ocultos, mas estou usando o Scala há alguns meses sem realmente entender o poder deles. Para (um longo) exemplo, eu posso substituir:
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)
}
Com isso, que é muito mais claro na minha opinião
implicit val ps: ProductService = ...
val p = code match {
case SyntheticCodes.Cash(c) => c
case SyntheticCodes.Forward(f) => f
case _ => ps.lookupProductByRic(code)
}
Eu tenho que fazer um pouco de trabalho braçal em segundo plano ...
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
}
}
Mas o trabalho braçal vale a pena pelo fato de separar uma parte da lógica de negócios em um local sensato. Eu posso implementar meus Product.getCode
métodos da seguinte maneira ..
class CashProduct {
def getCode = SyntheticCodes.Cash(this)
}
class ForwardProduct {
def getCode = SyntheticCodes.Forward(this)
}
Manifesta que são uma forma de obter as informações de tipo em tempo de execução, como se Scala tivesse reificado tipos.
No scala 2.8, você pode ter métodos recursivos de cauda usando o pacote scala.util.control.TailCalls (na verdade, é um trampolim).
Um exemplo:
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)
As classes de caso combinam automaticamente a característica do Produto, fornecendo acesso indexado e sem tipo aos campos sem nenhum reflexo:
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)
Esse recurso também fornece uma maneira simplificada de alterar a saída do 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))
Não é exatamente oculto, mas certamente um recurso sub-anunciado: scalac -Xprint .
Como ilustração do uso, considere a seguinte fonte:
class A { "xx".r }
Compilando isso com 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 é a aplicação do implicit def augmentString
presente no Predef.scala.
scalac -Xprint: <phase> imprimirá a árvore de sintaxe após alguma fase do compilador. Para ver as fases disponíveis, use scalac -Xshow-phase .
Essa é uma ótima maneira de aprender o que está acontecendo nos bastidores.
Tente com
case class X(a:Int,b:String)
usando a fase typer para realmente sentir como é útil.
Você pode definir suas próprias estruturas de controle. É realmente apenas funções e objetos e um pouco de açúcar sintático, mas eles parecem e se comportam como a coisa real.
Por exemplo, o código a seguir define 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
}
}
Agora você pode fazer o seguinte:
/* 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
. Requer Scalaz.
@switch
anotação no Scala 2.8:
Uma anotação a ser aplicada a uma expressão de correspondência. Se presente, o compilador verificará se a correspondência foi compilada em um comutador de tabela ou de pesquisa e emitirá um erro se, em vez disso, compilar em uma série de expressões condicionais.
Exemplo:
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 {
Não sei se isso está realmente oculto, mas acho muito bom.
Os construtores de tipos que usam dois parâmetros de tipo podem ser escritos em notação infix
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
, tornaria evidente a ordem dos parâmetros de tipo.
O Scala 2.8 introduziu argumentos padrão e nomeados, o que possibilitou a adição de um novo método de "cópia" que o Scala adiciona às classes de caso. Se você definir isso:
case class Foo(a: Int, b: Int, c: Int, ... z:Int)
e você deseja criar um novo Foo que seja como um Foo existente, apenas com um valor "n" diferente, basta dizer:
foo.copy(n = 3)
no scala 2.8, você pode adicionar @specialized às suas classes / métodos genéricos. Isso criará versões especiais da classe para tipos primitivos (estendendo o AnyVal) e economize o custo do boxe / unboxing desnecessário:
class Foo[@specialized T]...
Você pode selecionar um subconjunto de AnyVals:
class Foo[@specialized(Int,Boolean) T]...
Estendendo o idioma. Eu sempre quis fazer algo assim em Java (não podia). Mas em Scala eu posso ter:
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 escreva:
val numbers = List(12, 42, 3, 11, 6, 3, 77, 44)
val sorted = timed { // "timed" is a new "keyword"!
numbers.sortWith(_<_)
}
println(sorted)
e pegue
Executed in: 6.410311 millisec
List(3, 3, 6, 11, 12, 42, 44, 77)
Você pode designar um parâmetro de chamada por nome (EDITADO: é diferente de um parâmetro lento!) Para uma função e não será avaliado até que seja usado pela função (EDIT: na verdade, será reavaliado toda vez que for usava). Veja este FAQ para detalhes
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
no seu método e a partir desse momento você só usar xx
.
Você pode usar locally
para introduzir um bloco local sem causar problemas de inferência de ponto e vírgula.
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
é definido em "Predef.scala" como:
@inline def locally[T](x: T): T = x
Estando em linha, ele não impõe nenhuma sobrecarga 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)
Resultado:
In AbstractT2:
AbstractT2: value = 10, inverse = 0.1
c2c.value = 10, inverse = 0.1
Instanciamos uma classe interna anônima, inicializando o
value
campo no bloco, antes dawith AbstractT2
cláusula. Isso garante quevalue
é inicializado antes daAbstractT2
execução do corpo , conforme mostrado quando você executa o script.
Você pode compor tipos estruturais com a palavra-chave '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
}
}
sintaxe de espaço reservado para funções anônimas
Da especificação da linguagem Scala:
SimpleExpr1 ::= '_'
Uma expressão (da categoria sintática
Expr
) pode conter símbolos de sublinhado incorporados_
em locais onde os identificadores são legais. Essa expressão representa uma função anônima em que ocorrências subsequentes de sublinhados indicam parâmetros sucessivos.
Das alterações de idioma do 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)
Usando isso, você pode fazer algo como:
def filesEnding(query: String) =
filesMatching(_.endsWith(query))
Definições implícitas, principalmente conversões.
Por exemplo, assuma uma função que formate uma sequência de entrada para caber em um tamanho, substituindo o meio por "...":
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
}
Você pode usar isso com qualquer String e, é claro, usar o método toString para converter qualquer coisa. Mas você também pode escrever assim:
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 então, você pode passar classes de outros tipos fazendo o seguinte:
implicit def double2String(d: Double) = d.toString
Agora você pode chamar essa função passando um duplo:
sizeBoundedString(12345.12345D, 8)
O último argumento está implícito e está sendo passado automaticamente devido à declaração implícita. Além disso, "s" está sendo tratado como uma String dentro de sizeBoundedString porque há uma conversão implícita para String.
Implícitos desse tipo são melhor definidos para tipos incomuns, a fim de evitar conversões inesperadas. Você também pode passar explicitamente uma conversão, e ela ainda será usada implicitamente dentro de sizeBoundedString:
sizeBoundedString(1234567890L, 8)((l : Long) => l.toString)
Você também pode ter vários argumentos implícitos, mas deve passar por todos eles ou não por nenhum deles. Há também uma sintaxe de atalho para conversões 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
}
Isso é usado exatamente da mesma maneira.
Implícitos podem ter qualquer valor. Eles podem ser usados, por exemplo, para ocultar informações da biblioteca. Veja o exemplo a seguir, por exemplo:
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")
}
}
Neste exemplo, chamar "f" em um objeto Y enviará o log para o daemon padrão e em uma instância de X para o daemon X do Daemon. Mas chamar g em uma instância do X enviará o log para o DefaultDaemon explicitamente fornecido.
Embora este exemplo simples possa ser reescrito com sobrecarga e estado privado, os implícitos não requerem estado privado e podem ser contextualizados com importações.
Talvez não seja muito oculto, mas acho que isso é útil:
@scala.reflect.BeanProperty
var firstName:String = _
Isso gerará automaticamente um getter e um setter para o campo que corresponde à convenção do bean.
Descrição adicional em developerworks
Argumentos implícitos nos fechamentos.
Um argumento de função pode ser marcado como implícito, assim como nos métodos. Dentro do escopo do corpo da função, o parâmetro implícito é visível e elegível para resolução 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")
})
}
Crie estruturas de dados infinitas com o Scala's Stream
:
http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient
Os tipos de resultados dependem da resolução implícita. Isso pode fornecer uma forma de envio múltiplo:
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
usos a
que devem estar presentes no ambiente antes da execução desses comandos. Eu suponho que você quis dizer z.perform(x)
.
Scala permite criar uma subclasse anônima com o corpo da classe (o construtor) contendo instruções para inicializar a instância dessa classe.
Esse padrão é muito útil ao criar interfaces de usuário baseadas em componentes (por exemplo, Swing, Vaadin), pois permite criar componentes da interface do usuário e declarar suas propriedades de forma mais concisa.
Consulte http://spot.colorado.edu/~reids/papers/how-scala-experience-improved-our-java-development-reid-2011.pdf para obter mais informações.
Aqui está um exemplo de criação de um botão Vaadin:
val button = new Button("Click me"){
setWidth("20px")
setDescription("Click on this")
setIcon(new ThemeResource("icons/ok.png"))
}
import
declaraçõesSuponha que você queira usar um Logger
que contenha um println
e um printerr
método, mas deseje usar apenas aquele para mensagens de erro e mantenha o bom e velho estilo Predef.println
para a saída padrão. Você poderia fazer isso:
val logger = new Logger(...)
import logger.printerr
mas se logger
também contiver outros doze métodos que você gostaria de importar e usar, será inconveniente listá-los. Você poderia tentar:
import logger.{println => donotuseprintlnt, _}
mas isso ainda "polui" a lista de membros importados. Digite o curinga super poderoso:
import logger.{println => _, _}
e isso fará a coisa certa ™.
require
(definido em Predef
) que permite definir restrições adicionais de função que seriam verificadas durante o tempo de execução. Imagine que você está desenvolvendo outro cliente do twitter e precisa limitar o tamanho do tweet até 140 símbolos. Além disso, você não pode postar um tweet vazio.
def post(tweet: String) = {
require(tweet.length < 140 && tweet.length > 0)
println(tweet)
}
Agora, chamar post com argumento de comprimento inadequado causará uma exceção:
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)
Você pode escrever vários requisitos ou até mesmo adicionar uma descrição a cada um:
def post(tweet: String) = {
require(tweet.length > 0, "too short message")
require(tweet.length < 140, "too long message")
println(tweet)
}
Agora as exceções são detalhadas:
scala> post("")
java.lang.IllegalArgumentException: requirement failed: too short message
at scala.Predef$.require(Predef.scala:157)
at .post(<console>:8)
Mais um exemplo está aqui .
Você pode executar uma ação sempre que o requisito falhar:
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
não é uma palavra reservada. É apenas um método definido em Predef
.
Traços com abstract override
métodos são um recurso do Scala que não é tão amplamente divulgado como muitos outros. A intenção dos métodos com o abstract override
modificador é executar algumas operações e delegar a chamada para super
. Então essas características precisam ser combinadas com implementações concretas de seus 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
Embora meu exemplo não seja muito mais do que um pobre AOP, usei essas características empilháveis para criar instâncias de interpretador Scala com importações predefinidas, ligações personalizadas e caminhos de classe. As características empilháveis tornaram possível criar minha fábrica de acordo com as linhas de new InterpreterFactory with JsonLibs with LuceneLibs
e, em seguida, ter importações úteis e variáveis de escopo para os scripts dos usuários.