Các tính năng ẩn của Scala


149

Các tính năng ẩn của Scala mà mọi nhà phát triển Scala nên biết là gì?

Xin vui lòng một tính năng ẩn cho mỗi câu trả lời.


6
Heh, câu hỏi này hữu ích cho các liên kết đến các bài đăng tính năng ẩn khác như cho chính câu hỏi. Chúc mừng!
JohnMetta

1
@mettadore chỉ cần nhìn vào các liên kết liên quan ở phía bên phải.
Daniel C. Sobral

2
@JohnMetta: Hoặc sử dụng thẻ .

Câu trả lời:


85

Được rồi, tôi phải thêm một cái nữa. Mọi Regexđối tượng trong Scala đều có trình trích xuất (xem câu trả lời từ oxbox_lakes ở trên) cho phép bạn truy cập vào các nhóm khớp. Vì vậy, bạn có thể làm một cái gì đó như:

// 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"

Dòng thứ hai có vẻ khó hiểu nếu bạn không quen sử dụng so khớp mẫu và trích xuất. Bất cứ khi nào bạn xác định một valhoặc var, những gì xuất hiện sau từ khóa không chỉ đơn giản là một định danh mà là một mẫu. Đó là lý do tại sao điều này hoạt động:

val (a, b, c) = (1, 3.14159, "Hello, world")

Biểu thức bàn tay phải tạo ra một Tuple3[Int, Double, String]mẫu có thể khớp với mẫu (a, b, c).

Hầu hết thời gian các mẫu của bạn sử dụng các trình trích xuất là thành viên của các đối tượng singleton. Ví dụ: nếu bạn viết một mẫu như

Some(value)

sau đó bạn đang ngầm gọi trình trích xuất Some.unapply.

Nhưng bạn cũng có thể sử dụng các thể hiện lớp trong các mẫu và đó là những gì đang xảy ra ở đây. Val regex là một ví dụ Regexvà khi bạn sử dụng nó trong một mẫu, bạn đang gọi ngầm regex.unapplySeq( unapplyso với unapplySeqphạm vi của câu trả lời này), trích xuất các nhóm khớp thành một Seq[String], các yếu tố được gán để các biến số năm, tháng và ngày.


1
Thx đã đăng bài này! FYI nó đã được đề cập trong chương "Trích xuất bằng các biểu thức chính quy" trong cuốn sách "Lập trình trong Scala" trên trang 503 trong phiên bản đầu tiên và trên trang 611 trong phiên bản thứ hai.
đất paul

51

Định nghĩa kiểu cấu trúc - tức là một kiểu được mô tả bởi phương thức nào nó hỗ trợ. Ví dụ:

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

Lưu ý rằng loại tham số closeablekhông được xác định khác với closephương thức


1
Các kiểu cấu trúc thậm chí không được đề cập trong "Lập trình trong Scala". Chúng chậm hơn một chút so với các kỹ thuật khác để chuyển các loại mặc dù chúng sử dụng phản xạ để gọi đúng phương thức. (Hy vọng họ sẽ tìm ra cách để tăng tốc điều đó.)
Ken Bloom

1
Và cũng có thể tạo bí danh cho chúng, những gì hoạt động như giao diện được gán bên ngoài (rất chậm): gõ Closizable = {def close (): Unit}
Alexey

45

Kiểu đa hình xây dựng kiểu (hay còn gọi là kiểu cao hơn)

Nếu không có tính năng này, bạn có thể, ví dụ, thể hiện ý tưởng ánh xạ một chức năng qua một danh sách để trả về một danh sách khác hoặc ánh xạ một chức năng qua một cây để trả về một cây khác. Nhưng bạn không thể bày tỏ ý tưởng này nói chung mà không có loại cao hơn.

Với các loại cao hơn, bạn có thể nắm bắt ý tưởng về bất kỳ loại nào được tham số hóa với loại khác. Một constructor loại có một tham số được gọi là loại (*->*). Ví dụ , List. Một hàm tạo kiểu trả về hàm tạo kiểu khác được gọi là loại (*->*->*). Ví dụ , Function1. Nhưng trong Scala, chúng ta có các loại cao hơn , vì vậy chúng ta có thể có các hàm tạo kiểu được tham số hóa với các hàm tạo kiểu khác. Vì vậy, họ thuộc loại như ((*->*)->*).

Ví dụ:

trait Functor[F[_]] {
  def fmap[A, B](f: A => B, fa: F[A]): F[B]
}

Bây giờ, nếu bạn có một Functor[List], bạn có thể ánh xạ qua danh sách. Nếu bạn có một Functor[Tree], bạn có thể lập bản đồ trên cây. Nhưng quan trọng hơn, nếu bạn có Functor[A] bất kỳ loại A nào(*->*) , bạn có thể ánh xạ một hàm qua A.


39

Trình trích xuất cho phép bạn thay thế if-elseif-elsemã kiểu lộn xộn bằng các mẫu. Tôi biết rằng những thứ này không được giấu chính xác nhưng tôi đã sử dụng Scala trong một vài tháng mà không thực sự hiểu sức mạnh của chúng. Ví dụ (dài) tôi có thể thay thế:

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

Với điều này, rõ ràng hơn nhiều theo ý kiến ​​của tôi

implicit val ps: ProductService = ...
val p = code match {
  case SyntheticCodes.Cash(c) => c
  case SyntheticCodes.Forward(f) => f
  case _ => ps.lookupProductByRic(code)
}

Tôi phải làm một chút công việc trong nền ...

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

Nhưng các công việc đáng giá vì thực tế là nó tách một phần logic kinh doanh thành một nơi hợp lý. Tôi có thể thực hiện Product.getCodecác phương pháp của mình như sau ..

class CashProduct {
  def getCode = SyntheticCodes.Cash(this)
}

class ForwardProduct {
  def getCode = SyntheticCodes.Forward(this)     
}

Đây không giống như một công tắc? có lẽ điều này có thể được tái cấu trúc nhiều hơn.
Geo

14
Các mô hình giống như các công tắc tích điện turbo: mạnh mẽ và rõ ràng hơn nhiều
oxbow_lakes

1
Đẹp, nhưng tôi không muốn bạn phải sử dụng ngầm vì phạm vi của nó vượt xa hơn so với trận đấu {}. Bạn cũng có thể chỉ cần thêm một phương thức vào ProductService tìm kiếm Sản phẩm theo mã. Dù sao, bạn sẽ bọc đoạn mã được cấu trúc lại của mình theo một phương thức để có thể sử dụng nó ở mọi nơi.
Martin Konicek

35

Các biểu hiện là một cách để có được thông tin loại trong thời gian chạy, như thể Scala có các loại thống nhất.


8
Tôi nghĩ rằng tốt hơn là giải thích câu trả lời trong câu trả lời hơn là đề cập đến một liên kết. Nhân tiện, hi agai oxbow! :-)
Daniel C. Sobral

Đây là một tính năng thực sự bị ẩn ... ngay cả trong các tài liệu API. Rất hữu ích mặc dù.
André Laszlo

35

Trong scala 2.8, bạn có thể có các phương thức đệ quy đuôi bằng cách sử dụng gói scala.util.control.TailCalls (trên thực tế, đó là trampolining).

Một ví dụ:

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

Các lớp trường hợp tự động trộn vào đặc điểm của Sản phẩm, cung cấp quyền truy cập được lập chỉ mục, được lập chỉ mục vào các trường mà không có bất kỳ phản ánh nào:

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)

Tính năng này cũng cung cấp một cách đơn giản hóa để thay đổi đầu ra của toStringphương thức:

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

Nó không được ẩn chính xác, nhưng chắc chắn là một tính năng được quảng cáo: scalac -Xprint .

Như một minh họa về việc sử dụng, hãy xem xét các nguồn sau:

class A { "xx".r }

Biên dịch cái này với 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
  }
}

Lưu ý scala.this.Predef.augmentString("xx").r, đó là một ứng dụng của implicit def augmentStringhiện tại trong Predef.scala.

scalac -Xprint: <phase> sẽ in cây cú pháp sau một số pha biên dịch. Để xem các pha có sẵn, hãy sử dụng các giai đoạn -Xshow-scalac .

Đây là một cách tuyệt vời để tìm hiểu những gì đang diễn ra đằng sau hậu trường.

Thử với

case class X(a:Int,b:String)

sử dụng giai đoạn typer để thực sự cảm thấy nó hữu ích như thế nào.


30

Bạn có thể xác định cấu trúc điều khiển của riêng bạn. Nó thực sự chỉ là các chức năng và đối tượng và một số đường cú pháp, nhưng chúng trông và hành xử như thật.

Ví dụ: đoạn mã sau định nghĩa 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
  }
}

Bây giờ bạn có thể làm như sau:

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


Tôi sẽ tò mò nếu ai đó biết một cách để xác định các khối if-then-other với các tùy chọn khác kiểm tra kiểu như các khối tiêu chuẩn.
Philippe

@Phipippe : zif[A : Zero](cond: => Boolean)(t: => A): A = if(cond) t else mzero. Yêu cầu Scalaz.
missingfaktor

26

@switch chú thích trong Scala 2.8:

Một chú thích được áp dụng cho một biểu thức khớp. Nếu có, trình biên dịch sẽ xác minh rằng trận đấu đã được biên dịch thành một bảng biểu hoặc tra cứu và đưa ra lỗi nếu thay vào đó nó biên dịch thành một chuỗi các biểu thức điều kiện.

Thí dụ:

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

Nếu điều này thực sự bị che giấu, nhưng tôi thấy nó khá hay.

Máy đánh chữ có 2 tham số loại có thể được viết bằng ký hiệu 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
Đẹp! Tôi có thể tưởng tượng rằng đôi khi hữu ích trong việc cải thiện khả năng đọc. Ví dụ var foo2barConverter: Foo ConvertTo Barsẽ làm cho thứ tự của các tham số loại tự hiển nhiên.
Esko Luontola

4
Đôi khi tôi làm điều này trong mã sử dụng PartialFunction ở một mức độ nào đó: type ~> [A, B] = PartialFunction [A, B]
raichoo

24

Scala 2.8 đã giới thiệu các đối số mặc định và được đặt tên, điều này có thể bổ sung một phương thức "sao chép" mới mà Scala thêm vào các lớp tình huống. Nếu bạn xác định điều này:

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

và bạn muốn tạo một Foo mới giống như một Foo hiện có, chỉ với một giá trị "n" khác, thì bạn chỉ có thể nói:

foo.copy(n = 3)

3
CẢNH BÁO: phương thức sao chép sẽ không bị ghi đè nếu bạn kế thừa một lớp trường hợp từ lớp khác. Vì vậy, bạn phải ghi đè bằng tay
Alexey

Liên quan: Cách sạch hơn để cập nhật cấu trúc lồng nhau stackoverflow.com/q/3900307/203968
oluies

5
trường hợp lớp không còn (Scala 2.8) được phép kế thừa từ lớp trường hợp. Cảm ơn chúa của Scala vì đã từ chối sự thừa kế không lành mạnh này.
olle kullberg

24

trong scala 2.8, bạn có thể thêm @specialized vào các lớp / phương thức chung của mình. Điều này sẽ tạo các phiên bản đặc biệt của lớp cho các loại nguyên thủy (mở rộng AnyVal) và tiết kiệm chi phí cho việc đóng hộp / bỏ hộp không cần thiết: class Foo[@specialized T]...

Bạn có thể chọn một tập hợp con của AnyVals: class Foo[@specialized(Int,Boolean) T]...


1
Có một lời giải thích dài hơn mà bạn có thể chỉ cho tôi? Tôi muốn tìm hiểu thêm.
Paweł Prażak

23

Mở rộng ngôn ngữ. Tôi luôn muốn làm một cái gì đó như thế này trong Java (không thể). Nhưng ở Scala tôi có thể có:

  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
  }

và sau đó viết:

val numbers = List(12, 42, 3, 11, 6, 3, 77, 44)
val sorted = timed {   // "timed" is a new "keyword"!
  numbers.sortWith(_<_)
}
println(sorted)

và lấy

Executed in: 6.410311 millisec
List(3, 3, 6, 11, 12, 42, 44, 77)

23

Bạn có thể chỉ định một tham số gọi theo tên (EDITED: đây là một tham số lười biếng!) Cho một hàm và nó sẽ không được đánh giá cho đến khi được sử dụng bởi hàm (EDIT: trên thực tế, nó sẽ được đánh giá lại mỗi khi nó được đã sử dụng). Xem faq này để biết chi tiết

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
*/

Tôi nghĩ "x: => Bar" có nghĩa là x là một hàm không có tham số và trả về một Bar. Vì vậy, "thanh mới (22)" chỉ là một hàm ẩn danh và được đánh giá là một hàm giống như bất kỳ hàm nào khác.
Alex Black

1
"x: () => Bar" xác định hàm xa không có tham số và trả về một thanh. x: => Bar định nghĩa x là cuộc gọi theo tên. Hãy xem scala.sygneca.com/faqs/ Lỗi để biết thêm chi tiết
agilefall

3
Những gì bạn hiển thị là các tham số gọi theo tên. Các tham số lười biếng chưa được triển khai: đènvn.epfl.ch/trac/scala/ticket/240
ArtemGr

Tôi nghĩ rằng bạn có thể sử dụng nó như một param lười biếng nếu bạn làm một cái gì đó giống như lazy val xx: Bar = xtrong phương pháp của bạn và từ lúc đó bạn chỉ sử dụng xx.
Cristian Vrabie

20

Bạn có thể sử dụng locallyđể giới thiệu một khối cục bộ mà không gây ra các vấn đề suy luận dấu chấm phẩy.

Sử dụng:

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 được định nghĩa trong "Predef.scala" là:

@inline def locally[T](x: T): T = x

Là nội tuyến, nó không áp đặt bất kỳ chi phí bổ sung.


3
Điều này được giải thích tốt hơn tại stackoverflow.com/questions/3237727/
Ấn

17

Khởi tạo sớm:

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)

Đầu ra:

In AbstractT2:  
AbstractT2: value = 10, inverse = 0.1  
c2c.value = 10, inverse = 0.1

Chúng tôi khởi tạo một lớp bên trong ẩn danh, khởi tạo valuetrường trong khối, trước with AbstractT2mệnh đề. Điều này đảm bảo valueđược khởi tạo trước khi phần thân của AbstractT2được thực thi, như được hiển thị khi bạn chạy tập lệnh.


1
Cấu trúc được gọi là "khởi tạo sớm."
Randall Schulz

17

Bạn có thể soạn các loại cấu trúc với từ khóa 'với'

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

cú pháp giữ chỗ cho các hàm ẩn danh

Từ Đặc tả ngôn ngữ Scala:

SimpleExpr1 ::= '_'

Một biểu thức (thuộc danh mục cú pháp Expr) có thể chứa các ký hiệu gạch dưới được nhúng _tại các vị trí nơi định danh là hợp pháp. Một biểu thức như vậy biểu thị một hàm ẩn danh trong đó các lần xuất hiện tiếp theo của dấu gạch dưới biểu thị các tham số liên tiếp.

Từ thay đổi ngôn ngữ 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)

Sử dụng cái này bạn có thể làm một cái gì đó như:

def filesEnding(query: String) =
  filesMatching(_.endsWith(query))

2
Điều này nên được gọi là "cú pháp giữ chỗ cho các hàm ẩn danh". Tiềm ẩn có một ý nghĩa riêng biệt trong Scala và nó không liên quan đến điều này.
retronym

Liên kết có mối quan hệ không rõ ràng với câu trả lời. "ngầm" không phải là thuật ngữ chính xác cho việc này. Như trên nó phải là "giữ chỗ."
Alain O'Dea

2
Nó không thực sự "ẩn", tôi đã thấy cách sử dụng này trong gần như tất cả các hướng dẫn về Scala mà tôi đã đọc ... :-) Nhưng tôi đánh giá cao định nghĩa chính thức mà tôi chưa thấy.
PhiLho

@PhiLho có lẽ nó ít được biết đến vào năm 2009. Tôi không biết.
Eugene Yokota

Tôi đã bỏ lỡ ngày ban đầu, vì chỉ ngày chỉnh sửa cuối cùng được hiển thị. Và tốt, không phải tất cả các tính năng được giải thích trong chủ đề này là "ẩn". Chủ đề mát mẻ và câu trả lời tốt nào.
PhiLho

16

Định nghĩa ngầm, đặc biệt là chuyển đổi.

Ví dụ: giả sử một hàm sẽ định dạng chuỗi đầu vào phù hợp với kích thước, bằng cách thay thế giữa của nó bằng "...":

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
}

Bạn có thể sử dụng điều đó với bất kỳ Chuỗi nào và, tất nhiên, sử dụng phương thức toString để chuyển đổi mọi thứ. Nhưng bạn cũng có thể viết nó như thế này:

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
}

Và sau đó, bạn có thể vượt qua các lớp loại khác bằng cách thực hiện điều này:

implicit def double2String(d: Double) = d.toString

Bây giờ bạn có thể gọi hàm đó đi qua một đôi:

sizeBoundedString(12345.12345D, 8)

Đối số cuối cùng là ẩn và được truyền tự động vì khai báo ẩn. Hơn nữa, "s" đang được xử lý như một Chuỗi bên trong sizeBoundedString vì có một chuyển đổi ngầm định từ nó sang Chuỗi.

Ý nghĩa của loại này được xác định tốt hơn cho các loại không phổ biến để tránh chuyển đổi không mong muốn. Bạn cũng có thể vượt qua một chuyển đổi một cách rõ ràng và nó vẫn sẽ được sử dụng hoàn toàn bên trong sizeBoundedString:

sizeBoundedString(1234567890L, 8)((l : Long) => l.toString)

Bạn cũng có thể có nhiều đối số ngầm, nhưng sau đó bạn phải vượt qua tất cả chúng, hoặc không vượt qua bất kỳ đối số nào. Ngoài ra còn có một cú pháp phím tắt cho chuyển đổi ngầm:

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
}

Điều này được sử dụng chính xác theo cùng một cách.

Implicits có thể có bất kỳ giá trị. Chúng có thể được sử dụng, ví dụ, để ẩn thông tin thư viện. Lấy ví dụ sau đây, ví dụ:

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

Trong ví dụ này, việc gọi "f" trong một đối tượng Y sẽ gửi nhật ký đến daemon mặc định và trên một thể hiện của X đến daemon X daemon. Nhưng việc gọi g trên một thể hiện của X sẽ gửi nhật ký đến DefaultDaemon được cung cấp rõ ràng.

Trong khi ví dụ đơn giản này có thể được viết lại với tình trạng quá tải và trạng thái riêng tư, các ẩn ý không yêu cầu trạng thái riêng tư và có thể được đưa vào ngữ cảnh với nhập khẩu.


13

Có thể không quá ẩn, nhưng tôi nghĩ điều này hữu ích:

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

Điều này sẽ tự động tạo ra một getter và setter cho trường khớp với quy ước bean.

Mô tả thêm tại developerworks


6
Và bạn có thể tạo lối tắt cho nó nếu bạn sử dụng nó nhiều, ví dụ: nhập scala.reflect. {BeanProperty => BP}
Alexey

13

Lập luận ngầm trong đóng cửa.

Một đối số hàm có thể được đánh dấu là ẩn giống như với các phương thức. Trong phạm vi phần thân của hàm, tham số ẩn được hiển thị và đủ điều kiện để phân giải ngầm:

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

Các loại kết quả phụ thuộc vào độ phân giải ngầm. Điều này có thể cung cấp cho bạn một hình thức của nhiều công văn:

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

Đó có thể là trường hợp, nhưng phiên trên là sai lệch. Định nghĩa về việc foosử dụng một acái phải có trong môi trường trước khi thực hiện các lệnh này. Tôi giả sử bạn có nghĩa là z.perform(x).
Daniel C. Sobral

4

Scala tương đương với trình khởi tạo cú đúp Java.

Scala cho phép bạn tạo một lớp con ẩn danh với phần thân của lớp (hàm tạo) có chứa các câu lệnh để khởi tạo thể hiện của lớp đó.

Mẫu này rất hữu ích khi xây dựng giao diện người dùng dựa trên thành phần (ví dụ: Swing, Vaadin) vì nó cho phép tạo các thành phần UI và khai báo các thuộc tính của chúng chính xác hơn.

Xem http://spot.colorado.edu/~reids/ con / how-scala-experience-improved-our-java-developer-re-2011.pdf để biết thêm thông tin.

Dưới đây là một ví dụ về việc tạo nút Vaadin:

val button = new Button("Click me"){
 setWidth("20px")
 setDescription("Click on this")
 setIcon(new ThemeResource("icons/ok.png"))
}

3

Không bao gồm các thành viên từ importbáo cáo

Giả sử bạn muốn sử dụng một phương thức Loggerchứa a printlnvà một printerrphương thức, nhưng bạn chỉ muốn sử dụng một phương thức cho các thông báo lỗi và giữ cũ tốt Predef.printlncho đầu ra tiêu chuẩn. Bạn có thể làm điều này:

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

nhưng nếu loggercũng chứa mười hai phương thức khác mà bạn muốn nhập và sử dụng, việc liệt kê chúng trở nên bất tiện. Thay vào đó, bạn có thể thử:

import logger.{println => donotuseprintlnt, _}

nhưng điều này vẫn "gây ô nhiễm" danh sách các thành viên nhập khẩu. Nhập ký tự đại diện mạnh mẽ über:

import logger.{println => _, _}

và điều đó sẽ làm đúng việc ™.


2

requirephương thức (được xác định trong Predef) cho phép bạn xác định các ràng buộc chức năng bổ sung sẽ được kiểm tra trong thời gian chạy. Hãy tưởng tượng rằng bạn đang phát triển một ứng dụng twitter khác và bạn cần giới hạn độ dài tweet lên tới 140 ký hiệu. Hơn nữa, bạn không thể đăng tweet trống.

def post(tweet: String) = {
  require(tweet.length < 140 && tweet.length > 0) 
  println(tweet)
 }

Bây giờ gọi bài với đối số độ dài không phù hợp sẽ gây ra ngoại lệ:

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)

Bạn có thể viết nhiều yêu cầu hoặc thậm chí thêm mô tả cho từng yêu cầu:

def post(tweet: String) = {
  require(tweet.length > 0, "too short message")
  require(tweet.length < 140, "too long message")
  println(tweet)
}

Bây giờ các ngoại lệ là dài dòng:

scala> post("")
java.lang.IllegalArgumentException: requirement failed: too short message
    at scala.Predef$.require(Predef.scala:157)
    at .post(<console>:8)

Một ví dụ nữa là ở đây .


Tặng kem

Bạn có thể thực hiện một hành động mỗi khi yêu cầu không thành công:

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
requirekhông phải là một từ dành riêng. Đó là nhưng một phương pháp được định nghĩa trong Predef.
missingfaktor

1

Đặc điểm với abstract overridecác phương pháp là một tính năng trong Scala không được quảng cáo rộng rãi như nhiều người khác. Mục đích của các phương thức với công cụ abstract overridesửa đổi là thực hiện một số thao tác và ủy thác cuộc gọi đến super. Sau đó, những đặc điểm này phải được trộn lẫn với việc thực hiện cụ thể các abstract overridephương pháp của họ .

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

Mặc dù ví dụ của tôi thực sự không khác gì một người nghèo AOP, tôi đã sử dụng các Đặc điểm có thể xếp chồng này theo ý thích của mình để xây dựng các phiên bản trình thông dịch Scala với nhập khẩu được xác định trước, các ràng buộc tùy chỉnh và đường dẫn lớp. Các đặc điểm có thể xếp chồng đã cho phép tạo nhà máy của tôi dọc theo các dòng new InterpreterFactory with JsonLibs with LuceneLibsvà sau đó có các biến nhập phạm vi và phạm vi hữu ích cho các tập lệnh của người dùng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.