Які приховані особливості 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"
Другий рядок виглядає заплутаним, якщо ви не звикли використовувати відповідність шаблонів та витяжки. Щоразу, коли ви визначаєте a 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
проти того, 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(*->*)
, ви можете зіставити функцію 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 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 outputs :
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)
використовуючи фазу друку, щоб справді відчути, наскільки це корисно.
Ви можете визначити власні структури управління. Це дійсно просто функції та предмети, а також якийсь синтаксичний цукор, але вони виглядають і поводяться як справжня річ.
Наприклад, наступний код визначає 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
. Потрібен Scalaz.
@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 {
Данно, якщо це справді приховано, але я вважаю це досить приємним.
Конструктори типів, які приймають два типи параметрів, можуть записуватися у позначення інфіксу
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
, зробить порядок параметрів типу самоочевидним.
Скала 2.8 представила аргументи за замовчуванням та названі аргументи, що дозволило додати новий метод "копіювання", який Scala додає до класів справ. Якщо ви визначите це:
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)
Ви можете призначити для функції параметр виклику по імені (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
*/
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
, як показано під час запуску сценарію.
Можна створити структурні типи за допомогою ключового слова "з"
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)
Останній аргумент неявний і передається автоматично через неявну декларацію. Крім того, "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 = _
Це автоматично генерує геттер і сетер для поля, яке відповідає умовам боба.
Подальший опис у розробниках
Неявні аргументи у закриттях.
Аргумент функції може бути позначений як неявний так само, як і методи. У межах області функції неявний параметр є видимим і придатним для неявного вирішення:
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-fininity-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, _}
але це все ще "забруднює" список імпортованих членів. Введіть потужну підстановку über:
import logger.{println => _, _}
і це зробить правильно все ™.
require
метод (визначений в Predef
), що дозволяє визначати додаткові обмеження функції, які перевірялися б під час виконання. Уявіть, що ви розробляєте ще одного клієнта щебетання, і вам потрібно обмежити довжину твіту до 140 символів. Більше того, ви не можете публікувати порожній твіт.
def post(tweet: String) = {
require(tweet.length < 140 && tweet.length > 0)
println(tweet)
}
Тепер виклик повідомлення з аргументом невідповідної довжини спричинить виняток:
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
Незважаючи на те, що мій приклад насправді не набагато більше, ніж АОП з бідними людьми, я використовував ці складові риси сильно на свій смак, щоб створити екземпляри інтерпретатора Scala із заздалегідь визначеними імпортами, спеціальними прив'язками та classpathes. У стекові риси характеру дозволив створити свій завод по лініях , new InterpreterFactory with JsonLibs with LuceneLibs
а потім мають корисний імпорт і сфера varibles для сценаріїв користувачів.