Các loại hình ảnh
Ý nghĩa trong Scala đề cập đến một giá trị có thể được chuyển "tự động", có thể nói, hoặc chuyển đổi từ loại này sang loại khác được thực hiện tự động.
Chuyển đổi ngầm định
Nói rất ngắn gọn về loại sau, nếu người ta gọi một phương thức m
trên một đối tượng o
của một lớp C
và lớp đó không hỗ trợ phương thức m
, thì Scala sẽ tìm kiếm một chuyển đổi ngầm định từ C
một thứ có hỗ trợ m
. Một ví dụ đơn giản sẽ là phương pháp map
trên String
:
"abc".map(_.toInt)
String
không hỗ trợ phương pháp này map
, nhưng StringOps
không, và có một chuyển đổi ngầm từ String
để StringOps
sẵn (xem implicit def augmentString
trên Predef
).
Thông số tiềm ẩn
Các loại ẩn khác là tham số ngầm . Chúng được truyền cho các cuộc gọi phương thức như bất kỳ tham số nào khác, nhưng trình biên dịch cố gắng tự động điền chúng vào. Nếu không thể, nó sẽ phàn nàn. Người ta có thể vượt qua các tham số này một cách rõ ràng, đó là cách người ta sử dụng breakOut
, ví dụ (xem câu hỏi về breakOut
, vào một ngày bạn cảm thấy khó khăn cho một thách thức).
Trong trường hợp này, người ta phải khai báo sự cần thiết của một ẩn, chẳng hạn như foo
khai báo phương thức:
def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
Xem giới hạn
Có một tình huống trong đó một ẩn là cả chuyển đổi ngầm và tham số ngầm. Ví dụ:
def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)
getIndex("abc", 'a')
Phương thức getIndex
có thể nhận bất kỳ đối tượng nào, miễn là có một chuyển đổi ngầm định có sẵn từ lớp của nó sang Seq[T]
. Vì lý do đó, tôi có thể vượt qua một String
đến getIndex
, và nó sẽ làm việc.
Đằng sau hậu trường, trình biên dịch thay đổi seq.IndexOf(value)
thành conv(seq).indexOf(value)
.
Điều này hữu ích đến nỗi có đường cú pháp để viết chúng. Sử dụng đường cú pháp này, getIndex
có thể được định nghĩa như thế này:
def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)
Đường cú pháp này được mô tả như là một khung nhìn bị ràng buộc , gần giống với giới hạn trên ( CC <: Seq[Int]
) hoặc giới hạn dưới ( T >: Null
).
Giới hạn bối cảnh
Một mẫu phổ biến khác trong các tham số ngầm là mẫu lớp . Mẫu này cho phép cung cấp các giao diện chung cho các lớp không khai báo chúng. Nó vừa có thể phục vụ như một mẫu cầu nối - đạt được sự phân tách các mối quan tâm - vừa là một mẫu bộ điều hợp.
Các Integral
lớp học mà bạn đề cập là một ví dụ điển hình của kiểu dữ liệu mẫu lớp. Một ví dụ khác về thư viện tiêu chuẩn của Scala là Ordering
. Có một thư viện sử dụng rất nhiều mẫu này, được gọi là Scalaz.
Đây là một ví dụ về việc sử dụng nó:
def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Ngoài ra còn có cú pháp cú pháp cho nó, được gọi là bối cảnh ràng buộc , được làm cho ít hữu ích hơn bởi sự cần thiết phải đề cập đến ẩn. Một chuyển đổi thẳng của phương thức đó trông như thế này:
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Giới hạn bối cảnh hữu ích hơn khi bạn chỉ cần chuyển chúng sang các phương thức khác sử dụng chúng. Ví dụ, phương pháp sorted
trên Seq
cần một ẩn Ordering
. Để tạo một phương thức reverseSort
, người ta có thể viết:
def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse
Bởi vì Ordering[T]
đã được truyền vào ngầm reverseSort
, sau đó nó có thể chuyển nó hoàn toàn sang sorted
.
Implicits đến từ đâu?
Khi trình biên dịch thấy sự cần thiết của một ẩn, vì bạn đang gọi một phương thức không tồn tại trên lớp của đối tượng hoặc bởi vì bạn đang gọi một phương thức yêu cầu một tham số ẩn, nó sẽ tìm kiếm một ẩn sẽ phù hợp với nhu cầu .
Tìm kiếm này tuân theo các quy tắc nhất định xác định những ẩn ý nào có thể nhìn thấy và không. Bảng dưới đây cho thấy trình biên dịch sẽ tìm kiếm ẩn ý được lấy từ một bài thuyết trình tuyệt vời về ẩn ý của Josh Suereth, mà tôi chân thành giới thiệu cho bất kỳ ai muốn cải thiện kiến thức Scala của họ. Nó đã được bổ sung kể từ đó với thông tin phản hồi và cập nhật.
Các ẩn ý có sẵn dưới số 1 dưới đây được ưu tiên so với các ẩn dưới số 2. Khác với điều đó, nếu có một số đối số đủ điều kiện khớp với loại tham số ẩn, một đối số cụ thể nhất sẽ được chọn bằng cách sử dụng quy tắc phân giải quá tải tĩnh (xem Scala Đặc điểm kỹ thuật §6.26.3). Thông tin chi tiết hơn có thể được tìm thấy trong một câu hỏi tôi liên kết đến cuối câu trả lời này.
- Cái nhìn đầu tiên trong phạm vi hiện tại
- Ý nghĩa được xác định trong phạm vi hiện tại
- Nhập khẩu rõ ràng
- nhập khẩu ký tự đại diện
Cùng phạm vi trong các tệp khác
- Bây giờ hãy nhìn vào các loại liên quan trong
- Đối tượng đồng hành của một loại
- Phạm vi ngầm định của loại đối số (2.9.1)
- Phạm vi ngầm định của các đối số kiểu (2.8.0)
- Các đối tượng bên ngoài cho các loại lồng nhau
- Kích thước khác
Hãy cho một số ví dụ cho họ:
Ý nghĩa được xác định trong phạm vi hiện tại
implicit val n: Int = 5
def add(x: Int)(implicit y: Int) = x + y
add(5) // takes n from the current scope
Nhập khẩu rõ ràng
import scala.collection.JavaConversions.mapAsScalaMap
def env = System.getenv() // Java map
val term = env("TERM") // implicit conversion from Java Map to Scala Map
Nhập khẩu ký tự đại diện
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Cùng phạm vi trong các tệp khác
Chỉnh sửa : Có vẻ như điều này không có một ưu tiên khác. Nếu bạn có một số ví dụ chứng minh sự phân biệt ưu tiên, vui lòng đưa ra nhận xét. Nếu không, đừng dựa vào cái này.
Đây giống như ví dụ đầu tiên, nhưng giả sử định nghĩa ngầm định nằm trong một tệp khác với cách sử dụng. Xem thêm cách các đối tượng gói có thể được sử dụng để mang lại ẩn ý.
Đối tượng đồng hành của một loại
Có hai đối tượng lưu ý ở đây. Đầu tiên, đối tượng đồng hành của loại "nguồn" được xem xét. Chẳng hạn, bên trong đối tượng Option
có một chuyển đổi ngầm định Iterable
, vì vậy người ta có thể gọi Iterable
các phương thức trên Option
hoặc chuyển Option
đến một cái gì đó mong đợi một Iterable
. Ví dụ:
for {
x <- List(1, 2, 3)
y <- Some('x')
} yield (x, y)
Biểu thức đó được dịch bởi trình biên dịch sang
List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))
Tuy nhiên, List.flatMap
mong đợi một TraversableOnce
, đó Option
là không. Trình biên dịch sau đó trông bên trong Option
của bạn đồng hành đối tượng và tìm thấy việc chuyển đổi sang Iterable
, mà là một TraversableOnce
, làm cho biểu hiện này chính xác.
Thứ hai, đối tượng đồng hành của loại dự kiến:
List(1, 2, 3).sorted
Phương pháp sorted
này có một ẩn Ordering
. Trong trường hợp này, nó nhìn vào bên trong đối tượng Ordering
, đồng hành với lớp Ordering
và tìm thấy một ẩn Ordering[Int]
ở đó.
Lưu ý rằng các đối tượng đồng hành của các siêu lớp cũng được xem xét. Ví dụ:
class A(val n: Int)
object A {
implicit def str(a: A) = "A: %d" format a.n
}
class B(val x: Int, y: Int) extends A(y)
val b = new B(5, 2)
val s: String = b // s == "A: 2"
Đây là cách Scala tìm thấy ẩn ý Numeric[Int]
và Numeric[Long]
trong câu hỏi của bạn, nhân tiện, như chúng được tìm thấy bên trong Numeric
, không phải Integral
.
Phạm vi tiềm ẩn của loại đối số
Nếu bạn có một phương thức với một kiểu đối số A
, thì phạm vi ngầm định của kiểu A
cũng sẽ được xem xét. Theo "phạm vi ngầm", ý tôi là tất cả các quy tắc này sẽ được áp dụng đệ quy - ví dụ: đối tượng đồng hành của A
sẽ được tìm kiếm cho các ẩn ý, theo quy tắc trên.
Lưu ý rằng điều này không có nghĩa là phạm vi ngầm định A
sẽ được tìm kiếm để chuyển đổi tham số đó, mà là toàn bộ biểu thức. Ví dụ:
class A(val n: Int) {
def +(other: A) = new A(n + other.n)
}
object A {
implicit def fromInt(n: Int) = new A(n)
}
// This becomes possible:
1 + new A(1)
// because it is converted into this:
A.fromInt(1) + new A(1)
Điều này có sẵn kể từ Scala 2.9.1.
Phạm vi tiềm ẩn của các đối số loại
Điều này là cần thiết để làm cho mẫu lớp loại thực sự hoạt động. Ordering
Ví dụ, hãy xem xét : Nó đi kèm với một số ẩn ý trong đối tượng đồng hành của nó, nhưng bạn không thể thêm nội dung vào nó. Vì vậy, làm thế nào bạn có thể tạo một Ordering
lớp cho riêng bạn được tìm thấy tự động?
Hãy bắt đầu với việc thực hiện:
class A(val n: Int)
object A {
implicit val ord = new Ordering[A] {
def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(x.n, y.n)
}
}
Vì vậy, hãy xem xét những gì xảy ra khi bạn gọi
List(new A(5), new A(2)).sorted
Như chúng ta đã thấy, phương thức sorted
mong đợi một Ordering[A]
(thực sự, nó mong đợi một Ordering[B]
, ở đâu B >: A
). Không có bất kỳ thứ gì như vậy bên trong Ordering
, và không có loại "nguồn" nào để tìm. Rõ ràng, nó được tìm thấy nó bên trong A
, mà là một đối số kiểu của Ordering
.
Đây cũng là cách các phương thức thu thập khác nhau mong đợi CanBuildFrom
hoạt động: các ẩn ý được tìm thấy bên trong các đối tượng đồng hành với các tham số loại của CanBuildFrom
.
Lưu ý : Ordering
được định nghĩa là trait Ordering[T]
, trong đó T
là một tham số loại. Trước đây, tôi đã nói rằng Scala nhìn vào bên trong các tham số loại, điều này không có ý nghĩa nhiều. Các tiềm ẩn đã tìm kiếm ở trên là Ordering[A]
, nơi A
là một loại thực tế, không phải gõ tham số: nó là một đối số kiểu để Ordering
. Xem phần 7.2 của đặc tả Scala.
Điều này có sẵn kể từ Scala 2.8.0.
Đối tượng bên ngoài cho các loại lồng nhau
Tôi chưa thực sự thấy các ví dụ về điều này. Tôi sẽ biết ơn nếu ai đó có thể chia sẻ một. Nguyên tắc rất đơn giản:
class A(val n: Int) {
class B(val m: Int) { require(m < n) }
}
object A {
implicit def bToString(b: A#B) = "B: %d" format b.m
}
val a = new A(5)
val b = new a.B(3)
val s: String = b // s == "B: 3"
Kích thước khác
Tôi khá chắc chắn đây là một trò đùa, nhưng câu trả lời này có thể không cập nhật. Vì vậy, đừng coi câu hỏi này là trọng tài cuối cùng của những gì đang xảy ra, và nếu bạn nhận thấy nó đã lỗi thời, vui lòng thông báo cho tôi để tôi có thể khắc phục nó.
BIÊN TẬP
Các câu hỏi liên quan: