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


149

О каких скрытых особенностях Scala должен знать каждый разработчик Scala?

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


6
Хех, этот вопрос так же полезен для ссылок на другие сообщения о скрытых функциях, как и для самого вопроса. Ура!
JohnMetta

1
@mettadore просто посмотрите на соответствующие ссылки на правой стороне.
Даниэль С. Собрал

2
@JohnMetta: или используйте тег .

Ответы:


85

Хорошо, я должен был добавить еще один. Каждый 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], элементы которого назначаются для того, чтобы переменные год, месяц и день.


1
Спасибо за размещение этого! К вашему сведению, это упомянуто в главе «Извлечение с помощью регулярных выражений» в книге «Программирование в Scala» на стр. 503 в первом издании и на стр. 611 во втором издании.
землянин Павел

51

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

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

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


1
Структурные типы даже не упоминаются в "Программирование в Scala". Они немного медленнее, чем другие методы передачи типов, поскольку используют рефлексию для вызова правильных методов. (Надеюсь, они найдут способ ускорить это.)
Кен Блум,

1
И есть также возможность сделать для них псевдоним, который работает как внешний интерфейс (очень медленный): type Closeable = {def close (): Unit}
Алексей

45

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

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

С более высокими типами вы можете поймать идею любого типа, который параметризован с другим типом. Конструктор типа, который принимает один параметр, называется добрым (*->*). Например, 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.


39

Экстракторы, которые позволяют заменить грязный 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)     
}

это не похоже на переключатель? может быть, это может быть переработано больше.
Geo

14
Шаблоны похожи на переключатели с турбонаддувом: гораздо более мощные и четкие
oxbow_lakes

1
Хорошо, но мне не нравится, что вы должны использовать неявный, потому что его область действия выходит за пределы соответствия {}. Вы также можете просто добавить метод к ProductService, который ищет Product по коду. В любом случае вы должны обернуть свой рефакторированный фрагмент в метод, чтобы иметь возможность использовать его везде.
Мартин Коничек

35

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


8
Я думаю, что лучше объяснить ответ в ответе, чем ссылаться на ссылку. Кстати, привет агаи oxbow! :-)
Даниэль С. Собрал

Это действительно скрытая функция ... даже в документации по API. Очень полезно, хотя.
Андре Ласло

35

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

35

Классы 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)) 

32

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


30

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

Например, следующий код определяет 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) 

Еще несколько примеров здесь: programmers.stackexchange.com/questions/13072/…
missingfaktor

Мне было бы любопытно, если бы кто-нибудь знал способ определения блоков if-then-else с необязательными else, которые проверяют тип, как стандартные.
Филипп

@Philippe: zif[A : Zero](cond: => Boolean)(t: => A): A = if(cond) t else mzero. Требуется Скалаз.
Пропавший фактор 15.11.11

26

@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 {

26

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

Типовые конструкторы, которые принимают 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
  }
}

1
Ницца! Я могу себе представить, что иногда полезно улучшить читаемость. Например var foo2barConverter: Foo ConvertTo Bar, порядок параметров типа будет очевиден.
Эско Луонтола

4
Иногда я делаю это в коде, который в некоторой степени использует PartialFunction: type ~> [A, B] = PartialFunction [A, B]
raichoo

24

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

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

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

foo.copy(n = 3)

3
ВНИМАНИЕ: метод copy не будет переопределен, если вы унаследуете один класс case от другого. Таким образом, вы должны переопределить это вручную
Алексей

Связанный:
Более чистый

5
Класс case больше не поддерживается (Scala 2.8) для наследования от класса case. Спасибо, владыка Скала, за то, что осуждаешь это нечестивое наследство.
Олле Куллберг

24

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

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


1
Есть более длинное объяснение, на которое вы могли бы указать мне? Я хотел бы узнать больше.
Павел Прагак

23

Расширяя язык. Я всегда хотел сделать что-то подобное в 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)

23

Вы можете назначить параметр 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
*/

Я думал, что «x: => Bar» означает, что x - это функция, которая не принимает параметров и возвращает Bar. Итак, «new bar (22)» является просто анонимной функцией и оценивается как функция, как и любая другая функция.
Алекс Блэк

1
«x: () => Bar» определяет функцию xa, которая не принимает параметров и возвращает Bar. x: => Bar определяет x как вызов по имени. Взгляните на scala.sygneca.com/faqs/… для получения более подробной информации
agilefall

3
То, что вы показываете, это параметры вызова по имени. Ленивые параметры еще не реализованы: lampvn.epfl.ch/trac/scala/ticket/240
ArtemGr

Я думаю, что вы можете использовать его как ленивый параметр, если вы делаете что-то подобное lazy val xx: Bar = xв вашем методе и с этого момента вы используете только xx.
Кристиан Враби

20

Вы можете использовать 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

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


3
Это объясняется лучше на stackoverflow.com/questions/3237727/…
Эско Луонтола

17

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

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, как показано при запуске сценария.


1
Конструкция называется «ранняя инициализация».
Рэндалл Шульц

17

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

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

2
Это должно называться «синтаксис заполнителя для анонимных функций». У неявного есть особое значение в Scala, и это не связано с этим.
ретроним

Ссылка имеет неочевидное отношение к ответу. «неявный» не является правильным термином для этого. Как и выше, он должен быть «заполнителем».
Ален О'Ди

2
Это на самом деле не «скрыто», я видел это использование почти во всех уроках по Scala, которые я прочитал ... :-) Но я ценю формальное определение, которое я еще не видел.
Фил

@PhiLho, может быть, это было менее известно в 2009 году. Я не знаю.
Евгений Йокота

Я пропустил исходную дату, так как отображается только последняя дата редактирования. И хорошо, не все функции, описанные в этой теме, являются «скрытыми». Классная тема и хороший ответ в любом случае.
Фил Фил

16

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

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

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.

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


13

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

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

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

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


6
И вы можете сделать ярлык для него, если вы часто его используете, например: import scala.reflect. {BeanProperty => BP}
Алексей

13

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

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

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


12

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

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).
Даниэль С. Собрал

4

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

3

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

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

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

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

import logger.{println => donotuseprintlnt, _}

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

import logger.{println => _, _}

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


2

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

1
requireне зарезервированное слово Это всего лишь метод, определенный в Predef.
фактор

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 с предопределенным импортом, пользовательскими привязками и классами. В стекируемых Чертах характера позволил создать свой завод по линиям , new InterpreterFactory with JsonLibs with LuceneLibsа затем имеют полезный импорт и сфера varibles для сценариев пользователей.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.