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

149

Каковы скрытые функции Scala, о которых должен знать каждый разработчик Scala?

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

  • 6
    Хех, этот вопрос так же полезен для ссылок на другие сообщения о скрытых функциях, как и для самого вопроса. Ура!
  • 1
    @mettadore просто посмотрите на соответствующие ссылки на правой стороне.
Показать ещё 1 комментарий
Теги:
hidden-features

28 ответов

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.unapplySeq (unapply versus unapplySeq выходит за рамки этого ответа), который извлекает группы соответствия в Seq[String], элементы которого назначаются для переменных год, месяц и день.

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

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

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

Полиморфизм типа-конструктора (a.k.a. более высокие типы)

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

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

38

Экстракторы, которые позволяют вам заменить беспорядочный код стиля 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)     
}
  • 0
    это не похоже на переключатель? может быть, это может быть переработано больше.
  • 14
    Шаблоны похожи на переключатели с турбонаддувом: гораздо более мощные и четкие
Показать ещё 1 комментарий
35

Классы классов автоматически смешивают свойство продукта, предоставляя нетипизированный, индексированный доступ к полям без какого-либо отражения:

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

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

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

  • 8
    Я думаю, что лучше объяснить ответ в ответе, чем ссылаться на ссылку. Кстати, привет агаи oxbow! :-)
  • 0
    Это действительно скрытая функция ... даже в документации по API. Очень полезно, хотя.
32

Он не совсем скрыт, но, безусловно, под рекламируемой функцией: 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)

используя цифровую фазу, чтобы действительно почувствовать, насколько она полезна.

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) 
  • 0
    Еще несколько примеров здесь: programmers.stackexchange.com/questions/13072/…
  • 0
    Мне было бы любопытно, если бы кто-нибудь знал способ определения блоков if-then-else с необязательными else, которые проверяют тип, как стандартные.
Показать ещё 1 комментарий
27

Не знаю, если это действительно скрыто, но я считаю это довольно приятным.

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
  }
}
  • 1
    Ницца! Я могу себе представить, что иногда полезно улучшить читаемость. Например, var foo2barConverter: Foo ConvertTo Bar сделает порядок параметров типа очевидным.
  • 4
    Иногда я делаю это в коде, который до некоторой степени использует PartialFunction: type ~> [A, B] = PartialFunction [A, B]
26

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

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

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

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

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

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

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

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

foo.copy(n = 3)
  • 3
    ВНИМАНИЕ: метод copy не будет переопределен, если вы унаследуете один класс case от другого. Таким образом, вы должны переопределить его вручную
  • 0
    Связанный: Более чистый способ обновления вложенных структур stackoverflow.com/q/3900307/203968
Показать ещё 1 комментарий
24

Вы можете назначить параметр вызова по имени (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
*/
  • 0
    Я думал, что «x: => Bar» означает, что x - это функция, которая не принимает параметров и возвращает Bar. Итак, «new bar (22)» - это просто анонимная функция, которая оценивается как функция, как и любая другая функция.
  • 1
    «x: () => Bar» определяет функцию xa, которая не принимает параметров и возвращает Bar. x: => Bar определяет x как вызов по имени. Посмотрите на scala.sygneca.com/faqs/… для получения более подробной информации
Показать ещё 3 комментария
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

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

17

Вы можете создавать структурные типы с ключевым словом "с"

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

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

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.

  • 1
    Конструкция называется «ранняя инициализация».
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, и это не связано с этим.
  • 0
    Ссылка имеет неочевидное отношение к ответу. «неявный» не является правильным термином для этого. Как и выше, он должен быть «заполнителем».
Показать ещё 3 комментария
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
}

Вы можете использовать это с любой 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 не требуют частного состояния и могут быть перенесены в контекст с импортом.

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

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

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

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

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

  • 6
    И вы можете сделать ярлык для него, если вы часто его используете, например: import scala.reflect. {BeanProperty => BP}
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
  • 0
    Это может быть так, но приведенная выше сессия вводит в заблуждение. Определение foo использует a который должен присутствовать в среде до выполнения этих команд. Я полагаю, вы имели в виду z.perform(x) .
12

Постройте бесконечные структуры данных с помощью Scala Stream: http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient

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

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

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

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

import logger.{println => donotuseprintlnt, _}

но это все еще "загрязняет" список импортированных членов. Введите подстановочный знак über-powerful:

import logger.{println => _, _}

и это сделает правильную вещь & trade;.

  • 0
    (Спасибо Адриану за то, что указал на это.)
2

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
  • 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 с предопределенными импортами, пользовательскими привязками и classpathes. Stackable Traits позволил создать мой factory по строкам new InterpreterFactory with JsonLibs with LuceneLibs, а затем использовать полезные импорт и переменные областей для сценариев пользователей.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню