Скрытые возможности 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:
Это отличный способ узнать, что происходит за кулисами.
Попробуй с
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
) может содержать символы подчеркивания_
в местах, где идентификаторы являются законными. Такое выражение представляет собой анонимную функцию, где последующие вхождения подчеркивания обозначают последовательные параметры.
_ + 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 Stream
s: 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
а затем иметь полезные импорт и переменные области для пользовательских сценариев.