Скрытые черты Scala
Каковы скрытые функции Scala, о которых должен знать каждый разработчик Scala?
Одна скрытая функция для каждого ответа.
Ответы
Ответ 1
Хорошо, мне пришлось добавить еще один. Каждый объект 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.unapplySeq
(unapply
versus unapplySeq
выходит за рамки этого ответа), который извлекает группы соответствия в Seq[String]
, элементы которого назначаются для переменных год, месяц и день.
Ответ 2
Определения структурного типа, т.е. тип, описываемый тем, какие методы он поддерживает. Например:
object Closer {
def using(closeable: { def close(): Unit }, f: => Unit) {
try {
f
} finally { closeable.close }
}
}
Обратите внимание, что тип параметра closeable
не определен иначе, чем метод close
Ответ 3
Без этой функции вы можете, например, выразить идею сопоставления функции над списком, чтобы вернуть другой список, или сопоставить функцию над деревом, чтобы вернуть другое дерево. Но вы не можете выразить эту идею вообще без более высоких видов.
С более высокими видами вы можете запечатлеть идею любого типа, параметризованного другим типом. Конструктор типа, который принимает один параметр, называется добрым (*->*)
. Например, 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 вида (*->*)
, вы можете сопоставить функцию над A
.
Ответ 4
Экстракторы, которые позволяют вам заменить беспорядочный код стиля 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)
}
Ответ 5
Manifests, которые являются способом получения информации о типе во время выполнения, как будто Scala имеет типы reified.
Ответ 6
В 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)
Ответ 7
Классы классов автоматически смешивают свойство продукта, предоставляя нетипизированный, индексированный доступ к полям без какого-либо отражения:
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))
Ответ 8
Он не совсем скрыт, но, безусловно, под рекламируемой функцией: scalac -Xprint.
В качестве иллюстрации рассмотрим следующий источник:
class A { "xx".r }
Компиляция с помощью scalac -Xprint: строковые выходы:
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)
используя цифровую фазу, чтобы действительно почувствовать, насколько она полезна.
Ответ 9
Вы можете определить свои собственные структуры управления. Это действительно просто функции и объекты и некоторый синтаксический сахар, но они выглядят и ведут себя как настоящая вещь.
Например, следующий код определяет 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)
Ответ 10
@switch
аннотация в Scala 2.8:
Аннотации, применяемые к совпадению выражение. Если присутствует, компилятор проверит, что матч был скомпилирован в столовый переключатель или lookupswitch и выдавать ошибку, если она вместо этого компилируется в ряд условные выражения.
Пример:
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 {
Ответ 11
Не знаю, если это действительно скрыто, но я считаю это довольно приятным.
Typeconstructors, которые принимают 2 типа параметров, могут быть записаны в нотации 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
}
}
Ответ 12
Scala 2.8 представлены аргументы по умолчанию и именованные аргументы, что позволило добавить новый метод "копирования", который Scala добавляет к классам case. Если вы определяете это:
case class Foo(a: Int, b: Int, c: Int, ... z:Int)
и вы хотите создать новый Foo, который, как и существующий Foo, только с другим значением "n", вы можете просто сказать:
foo.copy(n = 3)
Ответ 13
в scala 2.8 вы можете добавить @specialized к вашим общим классам/методам. Это создаст специальные версии класса для примитивных типов (расширение AnyVal) и сохранит стоимость ненужного бокса/распаковки:
class Foo[@specialized T]...
Вы можете выбрать подмножество AnyVals:
class Foo[@specialized(Int,Boolean) T]...
Ответ 14
Вы можете назначить параметр вызова по имени (EDITED: это отличается от ленивого параметра!) функции, и он не будет оцениваться до тех пор, пока не будет использоваться функцией (EDIT: на самом деле, он будет переоцениваться каждый время используется). Подробнее см. этот вопрос
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
*/
Ответ 15
Расширение языка. Я всегда хотел сделать что-то подобное на 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)
Ответ 16
Вы можете использовать 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
Будучи встроенным, он не налагает никаких дополнительных накладных расходов.
Ответ 17
синтаксис заполнителя для анонимных функций
Из 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))
Ответ 18
Ранняя инициализация:
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
Мы создаем анонимный внутренний class, инициализируя поле value
в блоке, перед предложением with
AbstractT2
. Это гарантирует что value
инициализируется до тело AbstractT2
выполняется, так как отображается при запуске script.
Ответ 19
Вы можете создавать структурные типы с ключевым словом "с"
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
}
}
Ответ 20
Неявные определения, особенно преобразования.
Например, предположим, что функция, которая будет форматировать входную строку в соответствии с размером, заменив ее середину на "...":
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
}
Вы можете использовать это с любой String и, конечно же, использовать метод 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
Теперь вы можете вызвать эту функцию, передав double:
sizeBoundedString(12345.12345D, 8)
Последний аргумент неявный и передается автоматически из-за неявного объявления. Кроме того, "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
}
Это используется точно так же.
Implicits может иметь любое значение. Их можно использовать, например, для скрыть библиотечную информацию. Возьмем следующий пример, например:
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.
Хотя этот простой пример может быть переписан с перегрузкой и приватным состоянием, implicits не требуют частного состояния и могут быть перенесены в контекст с импортом.
Ответ 21
Возможно, не слишком скрытый, но я думаю, что это полезно:
@scala.reflect.BeanProperty
var firstName:String = _
Это автоматически сгенерирует геттер и сеттер для поля, соответствующего bean.
Дальнейшее описание developerworks
Ответ 22
Неявные аргументы в закрытии.
Аргумент функции может быть помечен как неявный, как с помощью методов. В рамках тела функции неявный параметр виден и имеет право на неявное разрешение:
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")
})
}
Ответ 23
Постройте бесконечные структуры данных с помощью Scala Stream
:
http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient
Ответ 24
Типы результатов зависят от неявного разрешения. Это может дать вам несколько вариантов отправки:
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] = [email protected]
scala> implicit val intToDouble = new PerformFunc[Int,Double] {
def perform(a : Int) = 1.0
}
intToDouble: java.lang.Object with PerformFunc[Int,Double] = [email protected]
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
Ответ 25
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"))
}
Ответ 26
Исключение членов из операторов import
Предположим, вы хотите использовать Logger
, который содержит метод println
и printerr
, но вы хотите использовать его только для сообщений об ошибках и сохранить старый добрый Predef.println
для стандартного вывода. Вы можете сделать это:
val logger = new Logger(...)
import logger.printerr
но если Logger
также содержит еще двенадцать методов, которые вы хотите импортировать и использовать, неудобно их перечислить. Вместо этого вы можете попробовать:
import logger.{println => donotuseprintlnt, _}
но это все еще "загрязняет" список импортированных членов. Введите подстановочный знак über-powerful:
import logger.{println => _, _}
и это сделает правильную вещь & trade;.
Ответ 27
require
(определенный в Predef
), который позволяет вам определить дополнительные ограничения функций, которые будут проверяться во время выполнения. Представьте, что вы разрабатываете еще один клиент Twitter, и вам нужно ограничить длину твита до 140 символов. Кроме того, вы не можете публиковать пустые твиты.
def post(tweet: String) = {
require(tweet.length < 140 && tweet.length > 0)
println(tweet)
}
Теперь вызов post с аргументом несоответствующей длины вызовет исключение:
scala> post("that ok")
that 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)
Еще один пример здесь.
Bonus
Вы можете выполнить действие каждый раз, когда выполняются требования:
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
Ответ 28
Черты с 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. Stackable Traits позволил создать мой factory по строкам new InterpreterFactory with JsonLibs with LuceneLibs
, а затем использовать полезные импорт и переменные областей для сценариев пользователей.