<: <, <% <, Và =: = có nghĩa là gì trong Scala 2.8 và chúng được ghi lại ở đâu?


201

Tôi có thể thấy trong các tài liệu API cho Predef rằng chúng là các lớp con của một loại hàm chung (Từ) => Tới, nhưng đó là tất cả những gì nó nói. Ừm, cái gì? Có thể có tài liệu ở đâu đó, nhưng các công cụ tìm kiếm không xử lý "tên" như "<: <" rất tốt, vì vậy tôi không thể tìm thấy nó.

Câu hỏi tiếp theo: khi nào tôi nên sử dụng các biểu tượng / lớp thú vị này và tại sao?


6
Đây là một câu hỏi liên quan có thể trả lời câu hỏi của bạn ít nhất một phần: stackoverflow.com/questions/2603003/operator-in-scala
Yardena

13
Symbolhound.com là người bạn tìm kiếm mã của bạn :)
ron

typeclassEs của Haskell có thực hiện công việc của các nhà khai thác này không? Ví dụ : compare :: Ord a => a -> a -> Ordering? Tôi đang cố gắng hiểu khái niệm Scala này đối với phần đối tác Haskell của nó.
Kevin Meredith

Câu trả lời:


217

Chúng được gọi là các ràng buộc kiểu tổng quát . Chúng cho phép bạn, từ bên trong một lớp hoặc đặc tính tham số loại, tiếp tục ràng buộc một trong các tham số loại của nó. Đây là một ví dụ:

case class Foo[A](a:A) { // 'A' can be substituted with any type
    // getStringLength can only be used if this is a Foo[String]
    def getStringLength(implicit evidence: A =:= String) = a.length
}

Đối số ngầm evidenceđược cung cấp bởi trình biên dịch, iff AString. Bạn có thể nghĩ về nó như một bằng chứng - đó AStringlý lẽ không quan trọng, chỉ biết rằng nó tồn tại. [chỉnh sửa: tốt, về mặt kỹ thuật nó thực sự quan trọng bởi vì nó đại diện cho một chuyển đổi ngầm từ Asang String, đó là những gì cho phép bạn gọi a.lengthvà không có trình biên dịch la mắng bạn]

Bây giờ tôi có thể sử dụng nó như vậy:

scala> Foo("blah").getStringLength
res6: Int = 4

Nhưng nếu tôi đã thử sử dụng nó với một Foothứ gì đó không phải là String:

scala> Foo(123).getStringLength
<console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]

Bạn có thể đọc lỗi đó là "không thể tìm thấy bằng chứng cho thấy Int == String" ... đúng như vậy! getStringLengthđang áp đặt các hạn chế hơn nữa đối với loại Ahơn những gì Foonói chung yêu cầu; cụ thể là, bạn chỉ có thể gọi getStringLengthtrên a Foo[String]. Ràng buộc này được thi hành vào thời gian biên dịch, thật tuyệt!

<:<<%<hoạt động tương tự, nhưng với các biến thể nhẹ:

  • A =:= B có nghĩa là A phải chính xác B
  • A <:< Bcó nghĩa là A phải là một kiểu con của B (tương tự như ràng buộc kiểu đơn giản<: )
  • A <%< Bcó nghĩa là A phải có thể xem được như B, có thể thông qua chuyển đổi ngầm định (tương tự như ràng buộc kiểu đơn giản <%)

Đoạn trích này của @retronym là một lời giải thích tốt về cách thức loại điều này từng được thực hiện và cách các ràng buộc kiểu tổng quát làm cho nó dễ dàng hơn bây giờ.

ĐỊA CHỈ

Để trả lời câu hỏi tiếp theo của bạn, phải thừa nhận ví dụ tôi đưa ra khá giả tạo và rõ ràng không hữu ích. Nhưng hãy tưởng tượng sử dụng nó để định nghĩa một cái gì đó giống như một List.sumIntsphương thức, trong đó thêm một danh sách các số nguyên. Bạn không muốn cho phép phương thức này được gọi trên bất kỳ cái cũ nào List, chỉ là a List[Int]. Tuy nhiên, hàm tạo Listkiểu không thể bị hạn chế; bạn vẫn muốn có thể có danh sách các chuỗi, foos, bar và whatnots. Vì vậy, bằng cách đặt một ràng buộc kiểu tổng quát trên sumInts, bạn có thể đảm bảo rằng chỉ phương thức đó có một ràng buộc bổ sung mà nó chỉ có thể được sử dụng trên a List[Int]. Về cơ bản, bạn đang viết mã trường hợp đặc biệt cho một số loại danh sách nhất định.


3
Chà, ok, nhưng cũng có những phương pháp cùng tên Manifestmà bạn không đề cập đến.
Daniel C. Sobral

3
Các phương pháp trên Manifest<:<>:>chỉ ... kể từ khi OP đề cập chính xác 3 loại hạn chế loại tổng quát, tôi giả sử đó là những gì ông đã quan tâm.
Tom Crockett

12
@IttayD: nó khá thông minh ... class =:=[From, To] extends From => To, điều đó có nghĩa là giá trị ngầm định của loại From =:= Tothực sự là một chuyển đổi ngầm định từ Fromsang To. Vì vậy, bằng cách chấp nhận một tham số ngầm định của loại A =:= Stringbạn đang nói Acó thể được chuyển đổi hoàn toàn thành String. Nếu bạn thay đổi thứ tự và biến đối số ngầm thành kiểu String =:= A, nó sẽ không hoạt động, bởi vì đây sẽ là một chuyển đổi ngầm định từ Stringsang A.
Tom Crockett

25
Những biểu tượng ba ký tự có tên? Vấn đề của tôi với súp biểu tượng của Scala là họ khó nói về lời nói và thực tế không thể sử dụng Google hoặc bất kỳ công cụ tìm kiếm nào khác để tìm các cuộc thảo luận và ví dụ về việc sử dụng chúng.
Gigatron

4
@Andrea Không, điều này sẽ chỉ hoạt động nếu các loại chính xác bằng nhau. Lưu ý rằng tôi đã nói rằng có một giá trị ngầm định của loại From =:= Totrong phạm vi ngụ ý rằng bạn có một chuyển đổi ngầm định From => To, nhưng hàm ý không chạy ngược; có một chuyển đổi ngầm A => Bkhông không có nghĩa bạn có một thể hiện của A =:= B. =:=là một lớp trừu tượng được niêm phong được định nghĩa trong scala.Predefvà chỉ có một thể hiện phơi bày công khai, ẩn, và có kiểu A =:= A. Vì vậy, bạn được đảm bảo rằng một giá trị ngầm định của loại A =:= Bchứng kiến ​​thực tế đó ABbằng nhau.
Tom Crockett

55

Không phải là một câu trả lời hoàn chỉnh (những người khác đã trả lời điều này), tôi chỉ muốn lưu ý những điều sau, có thể giúp hiểu cú pháp tốt hơn: Cách bạn thường sử dụng các "toán tử" này, ví dụ như trong ví dụ của pelotom:

def getStringLength(implicit evidence: A =:= String)

sử dụng cú pháp thay thế của Scala cho các toán tử kiểu .

Vì vậy, A =:= Stringgiống như =:=[A, String](và =:=chỉ là một lớp hoặc đặc điểm với một cái tên trông lạ mắt). Lưu ý rằng cú pháp này cũng hoạt động với các lớp "thông thường", ví dụ bạn có thể viết:

val a: Tuple2[Int, String] = (1, "one")

như thế này:

val a: Int Tuple2 String = (1, "one")

Nó tương tự như hai cú pháp cho các cuộc gọi phương thức, "bình thường" với .()cú pháp toán tử.


2
cần upvote vì makes use of Scala's alternative infix syntax for type operators.hoàn toàn thiếu lời giải thích này mà không có gì toàn bộ điều này không có ý nghĩa
Ovidiu Dolha

39

Đọc các câu trả lời khác để hiểu những cấu trúc này là gì. Đây là khi bạn nên sử dụng chúng. Bạn sử dụng chúng khi bạn cần chỉ giới hạn một phương thức cho các loại cụ thể.

Đây là một ví dụ. Giả sử bạn muốn xác định một cặp đồng nhất, như thế này:

class Pair[T](val first: T, val second: T)

Bây giờ bạn muốn thêm một phương thức smaller, như thế này:

def smaller = if (first < second) first else second

Điều đó chỉ hoạt động nếu Tđược ra lệnh. Bạn có thể hạn chế toàn bộ lớp:

class Pair[T <: Ordered[T]](val first: T, val second: T)

Nhưng điều đó có vẻ xấu hổ - có thể có sử dụng cho lớp khi Tkhông được ra lệnh. Với một ràng buộc kiểu, bạn vẫn có thể xác định smallerphương thức:

def smaller(implicit ev: T <:< Ordered[T]) = if (first < second) first else second

Đó là ok để nhanh chóng, chẳng hạn, một Pair[File], miễn là bạn đừng gọi smaller vào nó.

Trong trường hợp Option, những người triển khai muốn có một orNullphương thức, mặc dù nó không có ý nghĩa gì Option[Int]. Bằng cách sử dụng một ràng buộc kiểu, tất cả đều tốt. Bạn có thể sử dụng orNulltrên một Option[String], và bạn có thể tạo Option[Int]và sử dụng nó, miễn là bạn không gọi orNullnó. Nếu bạn cố gắng Some(42).orNull, bạn sẽ nhận được thông điệp quyến rũ

 error: Cannot prove that Null <:< Int

2
Tôi nhận ra rằng đây là nhiều năm sau câu trả lời này, nhưng tôi đang tìm kiếm các trường hợp sử dụng <:<và tôi nghĩ rằng Orderedví dụ này không còn hấp dẫn nữa vì bây giờ bạn thà sử dụng kiểu chữ Orderinghơn là Orderedđặc điểm. Một cái gì đó như : def smaller(implicit ord: Ordering[T]) = if (ord.lt(first, second)) first else second.
ebruchez

1
@ebruchez: trường hợp sử dụng là để mã hóa các loại kết hợp trong scala chưa sửa đổi, xem milessabin.com/blog/2011/06/09/scala-union-types-curry-howard

17

Nó phụ thuộc vào nơi chúng đang được sử dụng. Thông thường, khi được sử dụng trong khi khai báo các loại tham số ngầm định, chúng là các lớp. Chúng có thể là đối tượng quá trong những trường hợp hiếm hoi. Cuối cùng, chúng có thể là toán tử trên Manifestcác đối tượng. Chúng được định nghĩa bên trong scala.Predeftrong hai trường hợp đầu tiên, mặc dù không được ghi chép rõ ràng.

Chúng có nghĩa là để cung cấp một cách để kiểm tra mối quan hệ giữa các lớp, giống như <:<%làm, trong các tình huống khi cái sau không thể được sử dụng.

Đối với câu hỏi "khi nào tôi nên sử dụng chúng?", Câu trả lời là bạn không nên, trừ khi bạn biết bạn nên làm thế. :-) EDIT : Ok, ok, đây là một số ví dụ từ thư viện. Vào Either, bạn có:

/**
  * Joins an <code>Either</code> through <code>Right</code>.
  */
 def joinRight[A1 >: A, B1 >: B, C](implicit ev: B1 <:< Either[A1, C]): Either[A1, C] = this match {
   case Left(a)  => Left(a)
   case Right(b) => b
 }

 /**
  * Joins an <code>Either</code> through <code>Left</code>.
  */
 def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1]): Either[C, B1] = this match {
   case Left(a)  => a
   case Right(b) => Right(b)
 }

Vào Option, bạn có:

def orNull[A1 >: A](implicit ev: Null <:< A1): A1 = this getOrElse null

Bạn sẽ tìm thấy một số ví dụ khác trên các bộ sưu tập.


:-)một trong những người khác? Và tôi đồng ý rằng câu trả lời của bạn cho "Khi nào tôi nên sử dụng chúng?" áp dụng cho rất nhiều thứ.
Mike Miller

"Chúng có nghĩa là cung cấp một cách để kiểm tra mối quan hệ giữa các lớp" <- quá chung chung là hữu ích
Jeff

3
"Đối với câu hỏi" khi nào tôi nên sử dụng chúng? ", Câu trả lời là bạn không nên, trừ khi bạn biết bạn nên làm." <- Đó là lý do tại sao tôi hỏi. Tôi muốn có thể tự quyết định điều đó.
Jeff
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.