Características ocultas de Scala

Quais são os resources ocultos do Scala dos quais todos os desenvolvedores do Scala devem estar cientes?

Um recurso oculto por resposta, por favor.

Ok, eu tive que adicionar mais um. Todo object Regex no Scala possui um extrator (veja a resposta de oxbox_lakes acima) que dá access aos grupos de jogos. 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 padrões de correspondência e extratores. Sempre que você define um val ou var , o que vem depois da palavra-chave não é simplesmente um identificador, mas sim um padrão. É por isso que isso funciona:

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

A expressã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 objects 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 classs em padrões, e é isso que está acontecendo aqui. O val regex é uma instância do 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 um Seq[String] , cujos elementos são atribuídos para as variables ​​ano, mês e dia.

Definições de tipos estruturais – isto é, um tipo descrito por quais methods ele suporta. Por exemplo:

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

Observe que o tipo de parâmetro que pode ser close não é definido, além de ter um método de close

Polimorfismo de Construtor de Tipo (também conhecido como tipos de maior qualidade)

Sem esse recurso, você pode, por exemplo, expressar a ideia de mapear uma function em uma lista para retornar outra lista ou mapear uma function em uma tree para retornar outra tree. Mas você não pode expressar essa ideia geralmente sem tipos superiores.

Com tipos superiores, você pode capturar a ideia de qualquer tipo que seja parametrizado com outro tipo. Um construtor de tipo que aceita um parâmetro é considerado de tipo (*->*) . Por exemplo, List . Um construtor de tipo que retorna outro construtor de tipo é considerado de tipo (*->*->*) . Por exemplo, Function1 . Mas no Scala, temos tipos mais altos , portanto, podemos ter construtores de tipo parametrizados com outros construtores de tipo. Então eles são de tipos como ((*->*)->*) .

Por exemplo:

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

Agora, se você tem um Functor[List] , você pode mapear sobre listas. Se você tem um Functor[Tree] , você pode mapear sobre trees. Mas o mais importante, se você tiver o Functor[A] para qualquer tipo de A (*->*) , você pode mapear uma function sobre A

Extratores que permitem replace o código de estilo bagunçado if-elseif-else por padrões. Eu sei que estes não são exatamente escondidos, mas eu tenho usado o Scala por alguns meses sem realmente entender o poder deles. Para (um longo) exemplo, posso replace:

 val code: String = ... val ps: ProductService = ... var p: Product = null if (code.endsWith("=")) { p = ps.findCash(code.substring(0, 3)) //eg USD=, GBP= etc } else if (code.endsWith(".FWD")) { //eg GBP20090625.FWD p = ps.findForward(code.substring(0,3), code.substring(3, 9)) } else { p = ps.lookupProductByRic(code) } 

Com isso, o 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 de equipe no fundo …

 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 legwork vale a pena pelo fato de separar uma lógica comercial em um lugar sensível. Eu posso implementar meus methods Product.getCode seguinte maneira.

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

Manifestos que são uma espécie de maneira de obter as informações de tipo em tempo de execução, como se o Scala tivesse tipos reificados.

No scala 2.8 você pode ter methods recursivos de cauda usando o pacote scala.util.control.TailCalls (na verdade, é trampolining).

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 classs de casos mesclam automaticamente o atributo Produto, fornecendo access indexado, sem tipo, aos campos sem qualquer reflection:

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

Não é exatamente escondido, mas certamente um recurso menos anunciado: scalac -Xprint .

Como ilustração do uso, considere a seguinte fonte:

 class A { "xx".r } 

Compilando isso com scalac -Xprint: outputs de typer :

 package  { 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 em Predef.scala.

scalac -Xprint: irá imprimir a tree de syntax depois de alguma fase de compilador. Para ver as fases disponíveis, use scalac -Xshow-phases .

Esta é 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 objects e um pouco de açúcar sintático, mas eles se 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) 

@switch anotação no Scala 2.8:

Uma anotação a ser aplicada a uma expressão de correspondência. Se estiver presente, o compilador verificará se a correspondência foi compilada para uma mudança 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" | } :6: error: could not emit switch for @switch annotated match val s = (n: @switch) match { 

Não sei se isso é realmente oculto, mas eu acho isso muito legal.

Typeconstructors que levam 2 parâmetros de tipo podem ser escritos em notação infixada

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

O Scala 2.8 introduziu argumentos padrão e nomeados, o que tornou possível a adição de um novo método de “cópia” que o Scala adiciona às classs de casos. Se você definir isto:

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

e você quer criar um novo Foo que seja como um Foo existente, apenas com um valor “n” diferente, então você pode apenas dizer:

 foo.copy(n = 3) 

em scala 2.8, você pode adicionar @especializado às suas classs / methods genéricos. Isso criará versões especiais da class para tipos primitivos (estendendo AnyVal) e economizará o custo de unboxing / boxing desnecessário: class Foo[@specialized T]...

Você pode selecionar um subconjunto de AnyVals: class Foo[@specialized(Int,Boolean) T]...

Estendendo a linguagem. Eu sempre quis fazer algo assim em Java (não podia). Mas no 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 então 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 call-by-name (EDITED: isto é diferente de um parâmetro lazy!) Para uma function e ela não será avaliada até que seja usada pela function (EDIT: de fato, ela será reavaliada 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 */ 

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 

Sendo inline, não impõe nenhuma sobrecarga adicional.

Inicialização Antecipada:

 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) 

Saída:

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

Instanciamos uma class interna anônima, inicializando o campo de value no bloco, antes da cláusula with AbstractT2 . Isso garante que o value seja inicializado antes que o corpo do AbstractT2 seja executado, 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 } } 

syntax 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 underscore embutidos _ em locais onde os identificadores são legais. Essa expressão representa uma function anônima em que ocorrências subseqüentes de sublinhados denotam parâmetros sucessivos.

De alterações de linguagem Scala :

 _ + 1 x => x + 1 _ * _ (x1, x2) => x1 * x2 (_: Int) * 2 (x: Int) => x * 2 if (_) x else yz => if (z) x else y _.map(f) x => x.map(f) _.map(_ + 1) x => x.map(y => y + 1) 

Usando isso, você poderia fazer algo como:

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

Definições implícitas, principalmente conversões.

Por exemplo, suponha que uma function formatará uma string de input para se ajustar a 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ê poderia passar classs de outros tipos fazendo isso:

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

Agora você pode chamar essa function passando um duplo:

 sizeBoundedString(12345.12345D, 8) 

O último argumento está implícito e está sendo passado automaticamente por causa da declaração implícita de. Além disso, “s” está sendo tratado como um String dentro sizeBoundedString porque há uma conversão implícita dele para String.

Implicações desse tipo são melhor definidas para tipos incomuns para evitar conversões inesperadas. Você também pode passar uma conversão explicitamente e ainda será implicitamente usado dentro de sizeBoundedString:

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

Você também pode ter vários argumentos implícitos, mas você deve passar por todos eles ou não passar por nenhum deles. Há também uma syntax 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. Tome o seguinte exemplo, 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 object Y enviará o log para o daemon padrão e em uma instância de X para o daemon X daemon. Mas chamar g em uma instância de X enviará o log para o DefaultDaemon explicitamente fornecido.

Embora esse exemplo simples possa ser reescrito com sobrecarga e estado privado, os implícitos não requerem estado privado e podem ser incluídos no contexto com importações.

Talvez não muito escondido, mas acho que isso é útil:

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

Isso gerará automaticamente um getter e setter para o campo que corresponde à convenção do bean.

Mais descrições no developerworks

Argumentos implícitos em encerramentos.

Um argumento de function pode ser marcado como implícito, assim como com methods. Dentro do escopo do corpo da function, o parâmetro implícito é visível e qualificado 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 os streams do Scala: http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient

Os tipos de resultados dependem da resolução implícita. Isso pode lhe dar uma forma de despacho 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 

Equivalente ao Scala do inicializador de chave dupla Java.

O Scala permite criar uma subclass anônima com o corpo da class (o construtor) contendo instruções para inicializar a instância dessa class.

Esse padrão é muito útil ao criar interfaces com o usuário baseadas em componentes (por exemplo, Swing, Vaadin), pois permite criar componentes de UI e declarar suas propriedades de maneira mais concisa.

Veja http://spot.colorado.edu/~reids/papers/how-scala-experience-improved-our-java-development-reid-2011.pdf para 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")) } 

Excluindo membros de declarações de import

Suponha que você queira usar um printerr que contenha um método println e um printerr , mas deseja apenas usar o método para mensagens de erro e manter o bom e velho Predef.println para saída padrão. Você poderia fazer isso:

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

mas se o logger também contém outros doze methods que você gostaria de importar e usar, torna-se inconveniente listá-los. Você poderia tentar:

 import logger.{println => donotuseprintlnt, _} 

mas isso ainda “polui” a lista de membros importados. Digite o super-poderoso wildcard:

 import logger.{println => _, _} 

e isso fará a coisa certa .

require method (definido em Predef ) que permite definir restrições adicionais de function que seriam verificadas durante o tempo de execução. Imagine que você está desenvolvendo outro cliente no Twitter e precisa limitar o tamanho do tweet a 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 a postagem com argumento de tamanho 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(:8) scala> post("way to looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong tweet") java.lang.IllegalArgumentException: requirement failed at scala.Predef$.require(Predef.scala:145) at .post(:8) 

Você pode escrever vários requisitos ou até mesmo adicionar 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 verbosas:

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

Mais um exemplo está aqui .


Bônus

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(:9) ... scala> errorcount res16: Int = 1 

Traços com methods de abstract override são um recurso no Scala que não é amplamente divulgado como muitos outros. A intenção dos methods com o modificador de abstract override é fazer algumas operações e delegar a chamada para super . Então esses traços têm que ser misturados com implementações concretas de seus methods 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> aa("a lotta as") Called a with s=a lotta as Executed a in 0 ms res4: String = sa attol a 

Embora o meu exemplo não seja muito mais do que um AOP para pessoas de baixa renda, usei estas Funções Empilháveis ao meu gosto para criar instâncias do interpretador Scala com importações predefinidas, ligações personalizadas e caminhos de class. O Stackable Traits tornou possível criar minha fábrica nos moldes do new InterpreterFactory with JsonLibs with LuceneLibs e, em seguida, ter importações e variâncias de escopo úteis para os scripts dos usuários.