1. Implicit conversion
Cách thức hoạt động:
- Đầu tiên compiler sẽ compile code, nhưng sẽ gặp lỗi type errors.
- Trước khi kết thúc việc compile, compiler sẽ tìm các implicit conversion phù hợp để repair lỗi đó.
- Nếu fix được thì nó sẽ tiếp tục việc compile còn không sẽ throw error.
2. Các Rule
a. Making rule
Các variable, function, object phải được khai báo với từ khóa implicit
thì compiler mới có thể sử dụng được.
implicit def intToString(x: Int) = x.toString
b. Scope rule
Một implicit conversion được sử dụng phải trong scope và nó phải là implicit duy nhất, hoặc được liên kết với source or target type của conversion.
Muốn sử dụng implicit có 2 cách:
- Import implicit mà bạn muốn dùng.
import Preamble._
- Viết các implicit trong companion Object. vì các companion sẽ liên kết với các class, case class của nó nên compiler sẽ tự động sử dụng chúng khi cần.
object Dollar {
implicit def dollarToEuro(x: Dollar): Euro = ...
}
class Dollar { ... }
c. One at a time rule
Trong một thời điểm chỉ có một implicit được áp dụng. Compiler sẽ không bao giờ áp dụng thêm những implicit conversion khác khi đã có một implicit được sử dụng. Vì khi đó sẽ làm tăng compile time khi gặp code lỗi. Đồng thời nó cũng có thể dẫn đến việc hiểu nhầm giữa code của developer với cái mà compiler tạo ra.
convert1(convert2(x)) + y
d. Explicits first rule
Bất cứ khi code đang hoạt động tốt, implicit sẽ không bao giờ được sử dụng. (Nó chỉ được sử dụng khi compiler phát hiện lỗi).
3. Khi nào implicit được áp dụng
Có 3 cách mà implicit được sử dụng:
- Conversions to an expected type
- Conversions of the receiver of a selection
- Implicit parameters
a. Conversions to an expected type
Bất cứ khi nào compiler thấy một kiểu X nhưng lại cần một kiểu Y. Nó sẽ tìm một implicit method chuyển X sang Y.
Lấy ví dụ để dễ hiểu:
Nếu một số Double cần chuyển đổi thành số Int. Compiler sẽ tìm những implicit trong scope có thể chuyển Double thành Int để apply để tránh khỏi lỗi.
val age: Int = 26.5
Solution:
implicit def doubleToInt(x: Double): Int = x.toInt
val age: Int = 26.5
Với ví dụ trên việc implicit conversion từ Double sang Int sẽ gây rất khó hiểu (Vì các số bị làm tròn). Nhưng nếu implicit conversion từ Int sang Double thì sẽ hợp lý hơn rất nhiều. Vì thế khi dùng implicit thì nên chuyển đổi từ một kiểu nhỏ sang một kiểu lớn hơn, bao quát hơn, tránh làm ngược lại.
Đoạn code trên nên được sửa lại thành:
implicit def int2double(x: Int): Double = x.toDouble
b. Conversions of the receiver of a selection
Khi một object
gọi một method mà nó không có, thì compiler sẽ chuyển đổi object
đó thành object
khác mà chứa method được gọi.
Nó được sử dụng chính trong 2 trường hợp:
- Tương tác với những types mới (Interoperating with new types). Dễ hiểu thì là: Khi viết
obj.doIt
mà obj
lại không hề có doIt
. Lúc này compiler sẽ tìm kiếm các implicit conversion có thể biến đổi từ obj
sang 1 kiểu nào đó mà có chứa doIt
.
class Rational(n: Int, d: Int) {
def + (that: Rational): Rational = ...
def + (that: Int): Rational = ...
}
val oneHalf = new Rational(1, 2)
Khi viết như sau thì hoàn toàn hợp lệ:
oneHalf + 1 // res = 3/2
Nhưng nếu viết như sau sẽ throw Exception:
1 + oneHalf
Do 1
không có method +
nào nhận giá trị có kiểu Rational
làm parameter cả. Để giải quyết vấn đề này có thể viết thêm như sau:
implicit def intToRational(x: Int) = new Rational(x, 1)
- Mô phỏng systax mới (Simulating new syntax).
Bạn có thể quen thuộc khi khai báo Map như sau:
Map(1 -> "one", 2 -> "two", 3 -> "three")
Ta có thể thấy 1
là số Int thì tại sao lại có method ->
. Vì muốn tạo ra systax mới (method ->
) cho một kiểu bất kỳ thì library đã tạo ra một implicit def ->
để convert các type sang ArrowAssoc
Lúc này compiler sẽ apply implicit def ->
vào trong số 1
.
package scala
object Predef {
class ArrowAssoc[A](x: A) {
def -> [B](y: B): Tuple2[A, B] = Tuple2(x, y)
}
implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] =
new ArrowAssoc(x)
...
}
Trong Scala cũng sử dụng rất nhiều các class có tên kiểu RichSomething
ví dụ như RichInt, RichBoolean… Thì khả năng nó sẽ thêm các systax mới để phục vụ cho kiểu Something
c. Implicit parameters
Được áp dụng cho các parameters được parse vào method mà được khai báo là implicit Nó được dùng chủ yếu để cung cấp thêm thông tin về type của một parameter được thêm vào trước đó trong method.
Chú ý: Chúng ta nên tạo ra các type mới cho các implicit parameter để có thể giúp người đọc hiểu hơn về giá trị implicit truyền vào có ý nghĩa gì. Như ví dụ bên dưới, thay vì để promt
hoặc
drink
có type là String thì ta nên để type của chúng PreferredPrompt
và PreferredDrink
.
class PreferredPrompt(val preference: String)
class PreferredDrink(val preference: String)
object Greeter {
implicit val prompt = new PreferredPrompt("Yes, master> ")
implicit val drink = new PreferredDrink("tea")
def greet(name: String)(implicit prompt: PreferredPrompt, drink: PreferredDrink) {
println("Welcome, "+ name +". The system is ready.")
print("But while you work, ")
println("why not enjoy a cup of "+ drink.preference +"?")
println(prompt.preference)
}
}
Greeter.greet("Joe")
4. Trong một scope nhưng có nhiều conversion có thể áp dụng
- Với scala 2.7 trở về trước, khi gặp trường hợp này compiler sẽ throw ra lỗi
ambiguous
. Vì nó không biết chọn cái nào để aply. (Nó cũng tương tự overloading. khi có 2 method giống hệt nhau thì compiler sẽ báo lỗi.) - Với scala 2.8 trở đi thì compiler sẽ chọn ra một implicit mà nó rõ ràng hơn để sử dụng
Tuy nhiên dù thế nào đi nữa, chúng ta nên chỉ định rõ ràng implicit nào được sử dụng để tránh confuse.
5. Tổng kết.
Implicit rất mạnh mẽ và được sử dụng rất rộng rãi. Gần như trong tất cả libraries, 3rd đều sử dụng nó. Nó có các lợi ích sau:
- Có thể giúp bạn thêm các fields, method cho các class mà bạn muốn.
- Giúp code ngắn gọn hơn
- Reuse code tốt hơn.
Bên cạnh nó nếu lạm dụng thì code sẽ trở lên khó hiểu, và khó debug. Vì thế NẾU KHÔNG CÒN LỰA CHỌN NÀO KHÁC THÌ HÃY SỬ DỤNG IMPLICIT
Post Views: 2,402
c. Implicit parametersc.Phần c Implicit pẩmeters đọc không được rõ nghĩa lắm. Ý anh là sao nhỉ