Tất cả các toán tử tượng trưng của Scala có nghĩa là gì?


402

Cú pháp Scala có rất nhiều biểu tượng. Vì các loại tên này rất khó tìm thấy bằng cách sử dụng các công cụ tìm kiếm, nên một danh sách toàn diện về chúng sẽ hữu ích.

Tất cả các biểu tượng trong Scala là gì, và mỗi trong số chúng làm gì?

Đặc biệt, tôi muốn biết về ->, ||=, ++=, <=, _._, ::, và :+=.


4
và chỉ mục của Staircase phiên bản 1, tại >> artima.com/pin1ed/book-index.html#indexanchor
Gene T

2
Liên quan: ký tự toán tử và ký tự chữ và số: stackoverflow.com/questions/7656937/
triệt

1
ngoài ra, nếu có "toán tử" (chủ yếu là các phương thức, với một vài tên lớp được sử dụng là infix) mà bạn không thể tìm thấy trong sổ mở rộng hoặc sách cầu thang, ví dụ: "!!", các nguồn có khả năng là scaladocs cho akka, scalaz và sbt
Gene T

ví dụ về tên lớp được sử dụng infix (bằng tiếng Đức) >> raichoo.blogspot.com/2010/06/spass-mit-scala-infixtypen.html
Gene T

liên quan đến vấn đề lọc bởi các công cụ tìm kiếm, Symbolhound.com cũng là một lựa chọn tốt
Patrick Refondini

Câu trả lời:


526

Tôi chia các toán tử, cho mục đích giảng dạy, thành bốn loại :

  • Từ khóa / ký hiệu dành riêng
  • Phương thức nhập tự động
  • Phương pháp phổ biến
  • Đường tổng hợp / thành phần

Sau đó, thật may mắn là hầu hết các danh mục được thể hiện trong câu hỏi:

->    // Automatically imported method
||=   // Syntactic sugar
++=   // Syntactic sugar/composition or common method
<=    // Common method
_._   // Typo, though it's probably based on Keyword/composition
::    // Common method
:+=   // Common method

Ý nghĩa chính xác của hầu hết các phương thức này phụ thuộc vào lớp định nghĩa chúng. Ví dụ: <=trên Intcó nghĩa là "nhỏ hơn hoặc bằng" . Cái đầu tiên ->, tôi sẽ đưa ra ví dụ dưới đây. ::có lẽ là phương thức được định nghĩa trên List(mặc dù nó có thể là đối tượng cùng tên) và :+=có lẽ là phương thức được định nghĩa trên các Bufferlớp khác nhau .

Vì vậy, hãy xem chúng.

Từ khóa / ký hiệu dành riêng

Có một số biểu tượng trong Scala là đặc biệt. Hai trong số chúng được coi là từ khóa thích hợp, trong khi những từ khác chỉ là "dành riêng". Họ đang:

// Keywords
<-  // Used on for-comprehensions, to separate pattern from generator
=>  // Used for function types, function literals and import renaming

// Reserved
( )        // Delimit expressions and parameters
[ ]        // Delimit type parameters
{ }        // Delimit blocks
.          // Method call and path separator
// /* */   // Comments
#          // Used in type notations
:          // Type ascription or context bounds
<: >: <%   // Upper, lower and view bounds
<? <!      // Start token for various XML elements
" """      // Strings
'          // Indicate symbols and characters
@          // Annotations and variable binding on pattern matching
`          // Denote constant or enable arbitrary identifiers
,          // Parameter separator
;          // Statement separator
_*         // vararg expansion
_          // Many different meanings

Đây là tất cả các phần của ngôn ngữ , và, như vậy, có thể được tìm thấy trong bất kỳ văn bản nào mô tả chính xác ngôn ngữ, chẳng hạn như chính Đặc tả Scala (PDF).

Cái cuối cùng, dấu gạch dưới, xứng đáng được mô tả đặc biệt, bởi vì nó được sử dụng rộng rãi và có rất nhiều ý nghĩa khác nhau. Đây là một mẫu:

import scala._    // Wild card -- all of Scala is imported
import scala.{ Predef => _, _ } // Exception, everything except Predef
def f[M[_]]       // Higher kinded type parameter
def f(m: M[_])    // Existential type
_ + _             // Anonymous function placeholder parameter
m _               // Eta expansion of method into method value
m(_)              // Partial function application
_ => 5            // Discarded parameter
case _ =>         // Wild card pattern -- matches anything
f(xs: _*)         // Sequence xs is passed as multiple parameters to f(ys: T*)
case Seq(xs @ _*) // Identifier xs is bound to the whole matched sequence

Tôi có lẽ đã quên một số ý nghĩa khác, mặc dù.

Phương thức nhập tự động

Vì vậy, nếu bạn không tìm thấy biểu tượng bạn đang tìm kiếm trong danh sách trên, thì đó phải là một phương thức hoặc một phần của một biểu tượng. Nhưng, thông thường, bạn sẽ thấy một số biểu tượng và tài liệu cho lớp sẽ không có phương pháp đó. Khi điều này xảy ra, hoặc bạn đang xem một thành phần của một hoặc nhiều phương thức với một phương thức khác hoặc phương thức đã được nhập vào phạm vi hoặc có sẵn thông qua một chuyển đổi ẩn được nhập.

Chúng vẫn có thể được tìm thấy trên ScalaDoc : bạn chỉ cần biết nơi để tìm chúng. Hoặc, không thành công, hãy nhìn vào chỉ số (hiện đã bị hỏng vào ngày 2.9.1, nhưng có sẵn vào ban đêm).

Mỗi mã Scala có ba lần nhập tự động:

// Not necessarily in this order
import _root_.java.lang._      // _root_ denotes an absolute path
import _root_.scala._
import _root_.scala.Predef._

Hai cái đầu tiên chỉ làm cho các lớp và các đối tượng singleton có sẵn. Cái thứ ba chứa tất cả các chuyển đổi ngầm định và các phương thức được nhập, vì Predefchính nó là một đối tượng.

Nhìn vào bên trong Predefnhanh chóng hiển thị một số biểu tượng:

class <:<
class =:=
object <%<
object =:=

Bất kỳ biểu tượng nào khác sẽ được cung cấp thông qua một chuyển đổi ngầm . Chỉ cần nhìn vào các phương thức được gắn thẻ implicitnhận, như tham số, một đối tượng của kiểu đang nhận phương thức. Ví dụ:

"a" -> 1  // Look for an implicit from String, AnyRef, Any or type parameter

Trong trường hợp trên, ->được định nghĩa trong lớp ArrowAssocthông qua phương thức any2ArrowAssoclấy một đối tượng kiểu A, trong đó Atham số kiểu không giới hạn cho cùng một phương thức.

Phương pháp phổ biến

Vì vậy, nhiều biểu tượng chỉ đơn giản là các phương thức trên một lớp. Ví dụ, nếu bạn làm

List(1, 2) ++ List(3, 4)

Bạn sẽ tìm thấy phương pháp ++ngay trên ScalaDoc cho Danh sách . Tuy nhiên, có một quy ước mà bạn phải biết khi tìm kiếm các phương thức. Các phương thức kết thúc bằng dấu hai chấm ( :) liên kết với bên phải thay vì bên trái. Nói cách khác, trong khi cuộc gọi phương thức trên tương đương với:

List(1, 2).++(List(3, 4))

Nếu tôi có, thay vào đó 1 :: List(2, 3), nó sẽ tương đương với:

List(2, 3).::(1)

Vì vậy, bạn cần nhìn vào loại được tìm thấy ở bên phải khi tìm kiếm các phương thức kết thúc bằng dấu hai chấm. Xem xét, ví dụ:

1 +: List(2, 3) :+ 4

Phương thức đầu tiên ( +:) liên kết với bên phải và được tìm thấy trên List. Phương thức thứ hai ( :+) chỉ là một phương thức bình thường và liên kết với bên trái - một lần nữa, trên List.

Đường tổng hợp / thành phần

Vì vậy, đây là một vài loại đường cú pháp có thể ẩn một phương thức:

class Example(arr: Array[Int] = Array.fill(5)(0)) {
  def apply(n: Int) = arr(n)
  def update(n: Int, v: Int) = arr(n) = v
  def a = arr(0); def a_=(v: Int) = arr(0) = v
  def b = arr(1); def b_=(v: Int) = arr(1) = v
  def c = arr(2); def c_=(v: Int) = arr(2) = v
  def d = arr(3); def d_=(v: Int) = arr(3) = v
  def e = arr(4); def e_=(v: Int) = arr(4) = v
  def +(v: Int) = new Example(arr map (_ + v))
  def unapply(n: Int) = if (arr.indices contains n) Some(arr(n)) else None
}

val Ex = new Example // or var for the last example
println(Ex(0))  // calls apply(0)
Ex(0) = 2       // calls update(0, 2)
Ex.b = 3        // calls b_=(3)
// This requires Ex to be a "val"
val Ex(c) = 2   // calls unapply(2) and assigns result to c
// This requires Ex to be a "var"
Ex += 1         // substituted for Ex = Ex + 1

Cách cuối cùng là thú vị, bởi vì bất kỳ phương thức biểu tượng nào cũng có thể được kết hợp để tạo thành một phương thức giống như bài tập theo cách đó.

Và, tất nhiên, có nhiều kết hợp khác nhau có thể xuất hiện trong mã:

(_+_) // An expression, or parameter, that is an anonymous function with
      // two parameters, used exactly where the underscores appear, and
      // which calls the "+" method on the first parameter passing the
      // second parameter as argument.

1
Ý bạn là val c = ex(2)thay vì val ex(c) = 2?
Mike Ở lại

3
@MikeStay Không, ý tôi là vậy val ex(c) = 2.
Daniel C. Sobral

Ồ, đó là sử dụng cú pháp khớp mẫu. Cảm ơn.
Mike Ở lại

=> cũng xác nhận trạng thái 'gọi theo tên' khi được sử dụng giữa: và nhập như trong y: => Int '
Stephen W. Wright

1
Có lẽ người ta cũng nên đề cập đến: / và: \ thực sự không trực quan. Vì vậy, map. FoldLeft (initVal) giống như (initVal: / map) -: \ là FoldRight thay thế.
Ông MT

24

Một điểm khác biệt (tốt, IMO) giữa Scala và các ngôn ngữ khác là nó cho phép bạn đặt tên cho các phương thức của mình với hầu hết mọi ký tự.

Những gì bạn liệt kê không phải là "chấm câu" mà là các phương thức đơn giản và đơn giản, và do đó hành vi của chúng thay đổi từ đối tượng này sang đối tượng khác (mặc dù có một số quy ước).

Ví dụ: kiểm tra tài liệu Scaladoc cho Danh sách và bạn sẽ thấy một số phương pháp bạn đã đề cập ở đây.

Một số điều cần lưu ý:

  • Hầu hết các lần A operator+equal Bkết hợp dịch sang A = A operator B, như trong ||=hoặc ++=ví dụ.

  • Các phương thức kết thúc :là kết hợp đúng, điều này có nghĩa A :: Blà thực sự B.::(A).

Bạn sẽ tìm thấy hầu hết các câu trả lời bằng cách duyệt tài liệu Scala. Giữ một tài liệu tham khảo ở đây sẽ nhân đôi nỗ lực, và nó sẽ bị tụt lại phía sau một cách nhanh chóng :)


21

Bạn có thể nhóm những người đầu tiên theo một số tiêu chí. Trong bài viết này tôi sẽ chỉ giải thích các ký tự gạch dưới và mũi tên phải.

_._chứa một khoảng thời gian Một khoảng thời gian trong Scala luôn chỉ ra một cuộc gọi phương thức . Vì vậy, bên trái của khoảng thời gian bạn có người nhận và bên phải của nó là thông báo (tên phương thức). Bây giờ _là một biểu tượng đặc biệt trong Scala. Có một số bài viết về nó, ví dụ blog này nhập tất cả các trường hợp sử dụng. Đây là một hàm rút gọn hàm ẩn danh , đó là một lối tắt cho một hàm lấy một đối số và gọi phương thức _trên nó. Bây giờ _không phải là một phương thức hợp lệ, vì vậy chắc chắn bạn đã thấy _._1hoặc một cái gì đó tương tự, đó là, gọi phương thức _._1trên đối số hàm. _1để _22là phương pháp tuples đó trích xuất một yếu tố cụ thể của một tuple. Thí dụ:

val tup = ("Hallo", 33)
tup._1 // extracts "Hallo"
tup._2 // extracts 33

Bây giờ hãy giả sử một trường hợp sử dụng cho các phím tắt ứng dụng chức năng. Đưa ra một bản đồ ánh xạ các số nguyên thành chuỗi:

val coll = Map(1 -> "Eins", 2 -> "Zwei", 3 -> "Drei")

Wooop, đã có một dấu chấm câu lạ. Dấu gạch nối và lớn hơn ký tự, giống như một mũi tên bên phải , là một toán tử tạo ra một Tuple2. Vì vậy, không có sự khác biệt trong kết quả của việc viết (1, "Eins")hoặc 1 -> "Eins", chỉ có điều sau đó dễ đọc hơn, đặc biệt là trong một danh sách các bộ dữ liệu như ví dụ bản đồ. Điều ->này là không có phép thuật, giống như một số toán tử khác, có sẵn bởi vì bạn có tất cả các chuyển đổi ngầm định trong đối tượng scala.Predeftrong phạm vi. Việc chuyển đổi diễn ra ở đây là

implicit def any2ArrowAssoc [A] (x: A): ArrowAssoc[A] 

Trường hợp ArrowAssoc->phương pháp tạo ra Tuple2. Như vậy 1 -> "Eins"là thực tế cuộc gọi Predef.any2ArrowAssoc(1).->("Eins"). Đồng ý. Bây giờ trở lại câu hỏi ban đầu với ký tự gạch dưới:

// lets create a sequence from the map by returning the
// values in reverse.
coll.map(_._2.reverse) // yields List(sniE, iewZ, ierD)

Dấu gạch dưới ở đây rút ngắn mã tương đương sau:

coll.map(tup => tup._2.reverse)

Lưu ý rằng mapphương thức của Bản đồ chuyển qua bộ khóa và giá trị cho đối số hàm. Vì chúng tôi chỉ quan tâm đến các giá trị (chuỗi), chúng tôi trích xuất chúng bằng _2phương thức trên bộ dữ liệu.


+1 Tôi gặp khó khăn khi cố gắng hiểu ->phương pháp nhưng câu của bạn "Vì vậy, không có sự khác biệt nào trong kết quả của việc viết (1, "Eins")hoặc 1 -> "Eins"" đã giúp tôi hiểu cú pháp và cách sử dụng.
Jesse Webb

fyi liên kết mục nhập blog của bạn đã chết
still_learning

15

Ngoài các câu trả lời xuất sắc của Daniel và 0__, tôi phải nói rằng Scala hiểu các tương tự Unicode cho một số ký hiệu, vì vậy thay vì

for (n <- 1 to 10) n % 2 match {
  case 0 => println("even")
  case 1 => println("odd")
}

người ta có thể viết

for (n ← 1 to 10) n % 2 match {
  case 0 ⇒ println("even")
  case 1 ⇒ println("odd")
}

10

Liên quan đến ::có một mục Stackoverflow khác bao gồm các ::trường hợp. Nói tóm lại, nó được sử dụng để xây dựng Listsbởi ' consing ' một yếu tố đầu và một danh sách đuôi. Nó vừa là một lớp đại diện cho một danh sách vừa có và có thể được sử dụng như một trình trích xuất, nhưng phổ biến nhất là một phương thức trong danh sách. Như Pablo Fernandez chỉ ra, vì nó kết thúc bằng dấu hai chấm, nên nó là liên kết bên phải , nghĩa là người nhận cuộc gọi phương thức ở bên phải và đối số ở bên trái của toán tử. Bằng cách đó bạn thanh lịch có thể bày tỏ sự consing như prepending một yếu tố đầu mới cho một danh sách hiện có:

val x = 2 :: 3 :: Nil  // same result as List(2, 3)
val y = 1 :: x         // yields List(1, 2, 3)

Điều này tương đương với

val x = Nil.::(3).::(2) // successively prepend 3 and 2 to an empty list
val y = x.::(1)         // then prepend 1

Việc sử dụng làm đối tượng trích xuất như sau:

def extract(l: List[Int]) = l match {
   case Nil          => "empty"
   case head :: Nil  => "exactly one element (" + head + ")"
   case head :: tail => "more than one element"
}

extract(Nil)          // yields "empty"
extract(List(1))      // yields "exactly one element (1)"
extract(List(2, 3))   // yields "more than one element"

Điều này trông giống như một toán tử ở đây, nhưng nó thực sự chỉ là một cách viết khác (dễ đọc hơn)

def extract2(l: List[Int]) = l match {
   case Nil            => "empty"
   case ::(head, Nil)  => "exactly one element (" + head + ")"
   case ::(head, tail) => "more than one element"
}

Bạn có thể đọc thêm về trích xuất trong bài này .


9

<=giống như bạn sẽ "đọc" nó: 'nhỏ hơn hoặc bằng'. Vì vậy, nó là một toán tử toán học, trong danh sách <(nhỏ hơn?), >(Lớn hơn?), ==(Bằng?), !=(Không bằng?), <=(Nhỏ hơn hoặc bằng?), Và >=(lớn hơn hoặc bằng nhau?).

Không được nhầm lẫn với =>loại mũi tên hai tay phải , được sử dụng để tách danh sách đối số khỏi phần thân của hàm và để tách điều kiện kiểm tra trong khớp mẫu (một casekhối) khỏi phần thân được thực hiện khi khớp xảy ra . Bạn có thể xem ví dụ về điều này trong hai câu trả lời trước của tôi. Đầu tiên, chức năng sử dụng:

coll.map(tup => tup._2.reverse)

mà đã được viết tắt là các loại được bỏ qua. Các chức năng sau sẽ là

// function arguments         function body
(tup: Tuple2[Int, String]) => tup._2.reverse

và sử dụng khớp mẫu:

def extract2(l: List[Int]) = l match {
   // if l matches Nil    return "empty"
   case Nil            => "empty"
   // etc.
   case ::(head, Nil)  => "exactly one element (" + head + ")"
   // etc.
   case ::(head, tail) => "more than one element"
}

4
Tránh sự nhầm lẫn này là lý do tại sao tôi quyết định bắt đầu sử dụng các ký tự unicode cho mũi tên kép bên phải (\ U21D2), mũi tên "bản đồ" bên phải (\ U2192) và mũi tên "trong" bên trái (\ U2190). Scala ủng hộ điều này nhưng tôi đã có một chút hoài nghi cho đến khi tôi thử nó một lúc. Chỉ cần tìm cách liên kết các điểm mã này với tổ hợp phím thuận tiện trên hệ thống của bạn. Nó thực sự dễ dàng trên OS X.
Connor Doyle

5

Tôi coi một IDE hiện đại là rất quan trọng để hiểu các dự án scala lớn. Vì các toán tử này cũng là các phương thức, nên trong ý tưởng intellij tôi chỉ cần điều khiển nhấp chuột hoặc điều khiển-b vào các định nghĩa.

Bạn có thể điều khiển nhấp chuột phải vào toán tử khuyết điểm (: :) và kết thúc tại javalaoc scala với nội dung "Thêm một yếu tố vào đầu danh sách này." Trong các toán tử do người dùng định nghĩa, điều này càng trở nên quan trọng hơn, vì chúng có thể được định nghĩa theo các ẩn ý khó tìm ... IDE của bạn biết nơi ẩn được định nghĩa.


4

Chỉ cần thêm vào các câu trả lời tuyệt vời khác. Scala cung cấp hai toán tử biểu tượng thường bị chỉ trích, toán tử /:( foldLeft) và :\( foldRight), đầu tiên là liên kết đúng. Vì vậy, ba tuyên bố sau là tương đương:

( 1 to 100 ).foldLeft( 0, _+_ )
( 1 to 100 )./:( 0 )( _+_ )
( 0 /: ( 1 to 100 ) )( _+_ )

Cả ba đều như vậy:

( 1 to 100 ).foldRight( 0, _+_ )
( 1 to 100 ).:\( 0 )( _+_ )
( ( 1 to 100 ) :\ 0 )( _+_ )

2

Scala thừa hưởng hầu hết các toán tử số học của Java . Điều này bao gồm bitwise - hoặc |(ký tự ống đơn), bitwise - và &, độc quyền bitwise - hoặc ^, cũng như logic (boolean) hoặc ||(hai ký tự ống) và logic - và &&. Thật thú vị, bạn có thể sử dụng các toán tử ký tự đơn trên boolean, vì vậy các toán tử logic của java là hoàn toàn dư thừa:

true && true   // valid
true & true    // valid as well

3 & 4          // bitwise-and (011 & 100 yields 000)
3 && 4         // not valid

Như được chỉ ra trong một bài đăng khác, các cuộc gọi kết thúc bằng dấu bằng =, được giải quyết (nếu một phương thức có tên đó không tồn tại!) Bằng cách gán lại:

var x = 3
x += 1         // `+=` is not a method in `int`, Scala makes it `x = x + 1`

Điều này 'kiểm tra hai lần' giúp có thể dễ dàng trao đổi một biến đổi cho một bộ sưu tập bất biến:

val m = collection.mutable.Set("Hallo")   // `m` a val, but holds mutable coll
var i = collection.immutable.Set("Hallo") // `i` is a var, but holds immutable coll

m += "Welt" // destructive call m.+=("Welt")
i += "Welt" // re-assignment i = i + "Welt" (creates a new immutable Set)

4
PS Có một sự khác biệt giữa việc sử dụng đơn so với các nhà khai thác nhân vật đúp vào boolean-cựu là háo hức (tất cả các thuật ngữ này được đánh giá) thì chấm dứt sau sớm nếu boolean kết quả được biết: true | { println( "Icke" ); true }⇒ in! true || { println( "Icke" ); true }⇒ nào không in!
0__
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.