Скрытые возможности Scala

Какие скрытые особенности Scala должны знать все разработчики Scala?

Одна скрытая функция в ответе, пожалуйста.

28 ответов

Хорошо, я должен был добавить еще один. каждый Regex У объекта в Scala есть экстрактор (см. ответ от oxbox_lakes выше), который дает вам доступ к группам совпадений. Так что вы можете сделать что-то вроде:

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

Вторая строка выглядит сбивающей с толку, если вы не привыкли использовать сопоставление с образцом и экстракторы. Всякий раз, когда вы определяете val или же varпосле ключевого слова появляется не просто идентификатор, а шаблон. Вот почему это работает:

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

Правое выражение создает Tuple3[Int, Double, String] который может соответствовать шаблону (a, b, c),

В большинстве случаев ваши шаблоны используют экстракторы, которые являются членами одноэлементных объектов. Например, если вы напишите шаблон как

Some(value)

тогда вы неявно вызываете экстрактор Some.unapply,

Но вы также можете использовать экземпляры классов в шаблонах, и это то, что здесь происходит. Val regex является примером Regexи когда вы используете его в шаблоне, вы неявно вызываете regex.unapplySeq (unapply против unapplySeq выходит за рамки этого ответа), который извлекает группы соответствия в Seq[String]элементы которого присваиваются по порядку переменным год, месяц и день.

Определенияструктурного типа - то есть тип, описываемый теми методами, которые он поддерживает. Например:

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

Обратите внимание, что тип параметра closeable не определяется, кроме как имеет close метод

Тип-конструктор Полиморфизм (ака типизированный тип)

Без этой функции вы можете, например, выразить идею отображения функции над списком для возврата другого списка или сопоставления функции через дерево для возврата другого дерева. Но вы не можете выразить эту идею вообще без высших видов.

С более высокими типами вы можете захватить идею любого типа, который параметризован с другим типом. Говорят, что конструктор типа, который принимает один параметр, имеет вид (*->*), Например, List, Считается, что конструктор типа, который возвращает другой конструктор типа (*->*->*), Например, Function1, Но в Scala у нас есть более высокие виды, поэтому у нас могут быть конструкторы типов, параметризованные с помощью других конструкторов типов. Так что они типа ((*->*)->*),

Например:

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

Теперь, если у вас есть Functor[List]Вы можете отобразить списки. Если у тебя есть Functor[Tree]Вы можете наносить на карту над деревьями. Но что более важно, если у вас есть Functor[A] для любого вида(*->*)Вы можете отобразить функцию над A,

Экстракторы, которые позволяют заменить грязный if-elseif-else код стиля с узорами. Я знаю, что они не совсем скрыты, но я использую Scala в течение нескольких месяцев, не понимая их силу. Для (длинного) примера я могу заменить:

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

С этим, что гораздо понятнее, на мой взгляд

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

Я должен сделать немного работы в фоновом режиме...

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

Но работа стоит того, чтобы разделить часть бизнес-логики на разумное место. Я могу реализовать свой Product.getCode методы следующим образом..

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

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

В scala 2.8 вы можете использовать хвостовые рекурсивные методы, используя пакет scala.util.control.TailCalls (фактически это батут).

Пример:

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)

Манифесты, которые являются своего рода способом получения информации о типе во время выполнения, как если бы Scala имела усовершенствованные типы.

Классы Case автоматически смешивают черту Product, предоставляя нетипизированный, индексированный доступ к полям без какого-либо отражения:

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)

Эта функция также предоставляет упрощенный способ изменить вывод 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)) 

Это не совсем скрыто, но, безусловно, недооцененная функция: scalac -Xprint.

В качестве иллюстрации использования рассмотрим следующий источник:

class A { "xx".r }

Компилируем это с помощью scalac -Xprint: typer output:

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

уведомление scala.this.Predef.augmentString("xx").r, который является приложением implicit def augmentString присутствует в Predef.scala.

scalac -Xprint: напечатает дерево синтаксиса после некоторой фазы компилятора. Чтобы увидеть доступные фазы, используйте scalac -Xshow-фазы.

Это отличный способ узнать, что происходит за кулисами.

Попробуй с

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

используя фазу Typer, чтобы действительно почувствовать, насколько это полезно.

Вы можете определить свои собственные структуры управления. На самом деле это просто функции, объекты и некоторый синтаксический сахар, но они выглядят и ведут себя как настоящие.

Например, следующий код определяет dont {...} unless (cond) а также 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
  }
}

Теперь вы можете сделать следующее:

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

Не знаю, если это действительно скрыто, но я нахожу это довольно хорошим.

Типовые конструкторы, которые принимают 2 параметра типа, могут быть записаны в инфиксной записи

object Main {                                                                   
  class FooBar[A, B]

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

@switch аннотация в Scala 2.8:

Аннотация, которая будет применена к выражению совпадения. Если он присутствует, компилятор проверит, что совпадение было скомпилировано с табличным или поисковым переключателем, и выдаст ошибку, если вместо этого он скомпилирует в серию условных выражений.

Пример:

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 {

В Scala 2.8 вы можете добавить @specialized к вашим родовым классам / методам. Это создаст специальные версии класса для примитивных типов (расширяющих AnyVal) и сэкономит стоимость ненужного бокса / распаковки:class Foo[@specialized T]...

Вы можете выбрать подмножество AnyVals:class Foo[@specialized(Int,Boolean) T]...

В Scala 2.8 введены аргументы по умолчанию и именованные аргументы, что сделало возможным добавление нового метода "copy", который Scala добавляет к классам case. Если вы определите это:

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

и вы хотите создать новый Foo, который похож на существующий Foo, только с другим значением "n", тогда вы можете просто сказать:

foo.copy(n = 3)

Расширяя язык. Я всегда хотел сделать что-то подобное в Java (не смог). Но в Scala я могу иметь:

  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
  }

а затем напишите:

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

и получить

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

Вы можете назначить параметр call-by-name (EDITED: он отличается от параметра lazy!) Для функции, и он не будет оцениваться до тех пор, пока не будет использован функцией (EDIT: фактически он будет переоцениваться каждый раз, когда он используемый). Смотрите этот FAQ для деталей

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

Ты можешь использовать locally ввести локальный блок, не вызывая проблем с выводом точки с запятой.

Использование:

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 определяется в "Predef.scala" как:

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

Будучи встроенным, он не накладывает никаких дополнительных накладных расходов.

Ранняя инициализация:

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)

Выход:

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

Мы создаем экземпляр анонимного внутреннего класса, инициализируя value поле в блоке, перед with AbstractT2 пункт. Это гарантирует, что value инициализируется перед телом AbstractT2 выполняется, как показано при запуске сценария.

Вы можете создавать структурные типы с помощью ключевого слова 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
  }
}

синтаксис заполнителя для анонимных функций

Из спецификации языка Scala:

SimpleExpr1 ::= '_'

Выражение (из синтаксической категории Expr) может содержать символы подчеркивания _ в местах, где идентификаторы являются законными. Такое выражение представляет собой анонимную функцию, где последующие вхождения подчеркивания обозначают последовательные параметры.

Из изменений языка 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)

Используя это, вы можете сделать что-то вроде:

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

Неявные определения, особенно преобразования.

Например, предположим функцию, которая будет форматировать входную строку, чтобы соответствовать размеру, заменив середину на "...":

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
}

Вы можете использовать это с любой строкой и, конечно, использовать метод toString для преобразования чего угодно. Но вы также можете написать это так:

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
}

И затем, вы можете передать классы других типов, выполнив это:

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

Теперь вы можете вызвать эту функцию, передав двойную:

sizeBoundedString(12345.12345D, 8)

Последний аргумент является неявным и передается автоматически из-за неявного объявления de. Кроме того, "s" обрабатывается как String внутри sizeBoundedString, поскольку существует неявное преобразование из него в String.

Последствия этого типа лучше определены для необычных типов, чтобы избежать неожиданных преобразований. Вы также можете явно передать преобразование, и оно все равно будет неявно использоваться внутри sizeBoundedString:

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

Вы также можете иметь несколько неявных аргументов, но тогда вы должны либо передать все из них, либо не передавать ни один из них. Существует также сокращенный синтаксис для неявных преобразований:

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
}

Это используется точно так же.

Последствия могут иметь любое значение. Их можно использовать, например, для сокрытия информации библиотеки. Возьмите следующий пример, например:

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

В этом примере вызов "f" в объекте Y отправит журнал демону по умолчанию, а экземпляр X - демону Daemon X. Но вызов g для экземпляра X отправит журнал явно указанному DefaultDaemon.

Хотя этот простой пример можно переписать с перегрузкой и частным состоянием, для последствий не требуется частное состояние, и его можно привести в контекст с помощью импорта.

Неявные аргументы в замыканиях.

Аргумент функции может быть помечен как неявный, как и в методах. Внутри тела функции неявный параметр видим и имеет право на неявное разрешение:

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

Может быть, не слишком скрыто, но я думаю, что это полезно:

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

Это автоматически сгенерирует метод получения и установки для поля, которое соответствует соглашению бина.

Дальнейшее описание на developerworks

Создавайте бесконечные структуры данных с помощью Scala Streams: http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient

Типы результатов зависят от неявного разрешения. Это может дать вам форму многократной отправки:

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 Java инициализатора двойной скобки.

Scala позволяет создавать анонимный подкласс с телом класса (конструктором), содержащим операторы для инициализации экземпляра этого класса.

Этот шаблон очень полезен при создании основанных на компонентах пользовательских интерфейсов (например, Swing, Vaadin), поскольку он позволяет создавать компоненты пользовательского интерфейса и более кратко объявлять их свойства.

См. http://spot.colorado.edu/~reids/papers/how-scala-experience-improved-our-java-development-reid-2011.pdf для получения дополнительной информации.

Вот пример создания кнопки Vaadin:

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

Исключая участников из import заявления

Предположим, вы хотите использовать Logger который содержит println и printerr метод, но вы хотите использовать только один для сообщений об ошибках, и сохранить старый добрый Predef.println для стандартного вывода. Вы могли бы сделать это:

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

но если logger также содержит еще двенадцать методов, которые вы хотели бы импортировать и использовать, перечислять их становится неудобно. Вместо этого вы можете попробовать:

import logger.{println => donotuseprintlnt, _}

но это все еще "загрязняет" список импортируемых участников. Введите сверхмощный шаблон:

import logger.{println => _, _}

и это будет правильно делать ™.

require метод (определенный в Predef), которые позволяют определить дополнительные функциональные ограничения, которые будут проверяться во время выполнения. Представьте, что вы разрабатываете еще один клиент для Twitter, и вам нужно ограничить длину твита до 140 символов. Кроме того, вы не можете опубликовать пустой твит.

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

Теперь вызов post с недопустимым аргументом длины вызовет исключение:

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)

Вы можете написать несколько требований или даже добавить описание к каждому:

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

Теперь исключения многословны:

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

Еще один пример здесь.


бонус

Вы можете выполнить действие каждый раз, когда требование не выполняется:

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

Черты с abstract override методы - это особенность Scala, которая не так широко рекламируется, как многие другие. Предназначение методов с abstract override Модификатор должен делать некоторые операции и делегировать вызов super, Затем эти черты должны быть смешаны с конкретными реализациями их 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

Хотя мой пример на самом деле не намного больше, чем AOP для бедняков, я использовал эти Stackable Traits по своему вкусу для создания экземпляров интерпретатора Scala с предопределенным импортом, пользовательскими привязками и classpathes. Стекируемые черты позволили создать мою фабрику по образцу new InterpreterFactory with JsonLibs with LuceneLibs а затем иметь полезные импорт и переменные области для пользовательских сценариев.

Другие вопросы по тегам