О каких скрытых особенностях Scala должен знать каждый разработчик Scala?
Одна скрытая функция в ответе, пожалуйста.
О каких скрытых особенностях Scala должен знать каждый разработчик Scala?
Одна скрытая функция в ответе, пожалуйста.
Ответы:
Хорошо, я должен был добавить еще один. Каждый 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
что выходит за рамки этого ответа), который выделяет группы соответствия в a 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 имела усовершенствованные типы.
В 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)
Классы 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: <phase> напечатает синтаксическое дерево после некоторой фазы компилятора. Чтобы увидеть доступные фазы, используйте 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)
zif[A : Zero](cond: => Boolean)(t: => A): A = if(cond) t else mzero
. Требуется Скалаз.
@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 {
Не знаю, если это действительно скрыто, но я нахожу это довольно хорошим.
Типовые конструкторы, которые принимают 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
}
}
var foo2barConverter: Foo ConvertTo Bar
, порядок параметров типа будет очевиден.
В Scala 2.8 введены аргументы по умолчанию и именованные аргументы, что сделало возможным добавление нового метода «copy», который Scala добавляет к классам case. Если вы определите это:
case class Foo(a: Int, b: Int, c: Int, ... z:Int)
и вы хотите создать новый Foo, который похож на существующий Foo, только с другим значением «n», тогда вы можете просто сказать:
foo.copy(n = 3)
в Scala 2.8 вы можете добавить @specialized к вашим родовым классам / методам. Это создаст специальные версии класса для примитивных типов (расширяющих AnyVal) и сэкономит затраты на ненужный бокс / распаковку:
class Foo[@specialized T]...
Вы можете выбрать подмножество AnyVals:
class Foo[@specialized(Int,Boolean) T]...
Расширяя язык. Я всегда хотел сделать что-то подобное в 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
*/
lazy val xx: Bar = x
в вашем методе и с этого момента вы используете только xx
.
Вы можете использовать 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.
Хотя этот простой пример можно переписать с перегрузкой и частным состоянием, для последствий не требуется частное состояние, и его можно привести в контекст с помощью импорта.
Может быть, не слишком скрыто, но я думаю, что это полезно:
@scala.reflect.BeanProperty
var firstName:String = _
Это автоматически сгенерирует метод получения и установки для поля, которое соответствует соглашению бина.
Дальнейшее описание на developerworks
Неявные аргументы в замыканиях.
Аргумент функции может быть помечен как неявный, как и в методах. В пределах объема тела функции неявный параметр видим и имеет право на неявное разрешение:
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 Stream
:
http://www.codecommit.com/blog/scala/infinite-lists-for-the-finite-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
foo
использует, a
который должен присутствовать в среде до выполнения этих команд. Я полагаю, вы имели в виду z.perform(x)
.
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
заявленийПредположим, вы хотите использовать a, Logger
который содержит a println
и printerr
метод, но вы хотите использовать только один для сообщений об ошибках и оставить старый добрый Predef.println
для стандартного вывода. Вы могли бы сделать это:
val logger = new Logger(...)
import logger.printerr
но если logger
также содержит еще двенадцать методов, которые вы хотели бы импортировать и использовать, перечислять их становится неудобно. Вместо этого вы можете попробовать:
import logger.{println => donotuseprintlnt, _}
но это все еще "загрязняет" список импортируемых участников. Введите сверхмощный шаблон:
import logger.{println => _, _}
и это будет правильно делать ™.
require
метод (определенный в Predef
), который позволяет вам определять дополнительные функциональные ограничения, которые будут проверяться во время выполнения. Представьте, что вы разрабатываете еще один клиент для Твиттера, и вам нужно ограничить длину твита до 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
require
не зарезервированное слово Это всего лишь метод, определенный в Predef
.
Черты с 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 с предопределенным импортом, пользовательскими привязками и классами. В стекируемых Чертах характера позволил создать свой завод по линиям , new InterpreterFactory with JsonLibs with LuceneLibs
а затем имеют полезный импорт и сфера varibles для сценариев пользователей.