Hiểu ngầm trong Scala


308

Tôi đang thực hiện theo hướng dẫn của Scala playframework và tôi đã bắt gặp đoạn mã này khiến tôi bối rối:

def newTask = Action { implicit request =>
taskForm.bindFromRequest.fold(
        errors => BadRequest(views.html.index(Task.all(), errors)),
        label => {
          Task.create(label)
          Redirect(routes.Application.tasks())
        } 
  )
}

Vì vậy, tôi quyết định điều tra và đi qua bài đăng này .

Tôi vẫn không hiểu.

Sự khác biệt giữa cái này là gì:

implicit def double2Int(d : Double) : Int = d.toInt

def double2IntNonImplicit(d : Double) : Int = d.toInt

ngoài thực tế rõ ràng họ có tên phương thức khác nhau.

Khi nào tôi nên sử dụng implicitvà tại sao?


Tìm thấy hướng dẫn này thực sự hữu ích: Hướng dẫn Scala - Tìm hiểu cách tạo chức năng tiềm ẩn
Adrian Moisa

Câu trả lời:


391

Tôi sẽ giải thích các trường hợp sử dụng chính của ẩn ý bên dưới, nhưng để biết thêm chi tiết, hãy xem chương Lập trình liên quan trong Scala .

Thông số ngầm định

Danh sách tham số cuối cùng trên một phương thức có thể được đánh dấu implicit, có nghĩa là các giá trị sẽ được lấy từ ngữ cảnh mà chúng được gọi. Nếu không có giá trị ngầm định của đúng loại trong phạm vi, nó sẽ không biên dịch. Vì giá trị ngầm định phải giải quyết thành một giá trị duy nhất và để tránh xung đột, nên tốt nhất là làm cho loại cụ thể theo mục đích của nó, ví dụ: không yêu cầu các phương thức của bạn để tìm ẩn Int!

thí dụ:

  // probably in a library
class Prefixer(val prefix: String)
def addPrefix(s: String)(implicit p: Prefixer) = p.prefix + s

  // then probably in your application
implicit val myImplicitPrefixer = new Prefixer("***")
addPrefix("abc")  // returns "***abc"

Chuyển đổi ngầm định

Khi trình biên dịch tìm thấy một biểu thức sai loại cho ngữ cảnh, nó sẽ tìm một Functiongiá trị ngầm định của một loại sẽ cho phép nó đánh máy. Vì vậy, nếu một Alà bắt buộc và nó tìm thấy một B, nó sẽ tìm một giá trị ngầm định của loại B => Atrong phạm vi (nó cũng kiểm tra một số vị trí khác như trong các đối tượng BAđồng hành, nếu chúng tồn tại). Vì defs có thể được "mở rộng eta" thành Functioncác đối tượng, nên implicit def xyz(arg: B): Acũng sẽ làm như vậy.

Vì vậy, sự khác biệt giữa các phương thức của bạn là một phương thức được đánh dấu implicitsẽ được trình biên dịch chèn vào cho bạn khi Doubletìm thấy một cái nhưng Intbắt buộc phải có.

implicit def doubleToInt(d: Double) = d.toInt
val x: Int = 42.0

sẽ làm việc giống như

def doubleToInt(d: Double) = d.toInt
val x: Int = doubleToInt(42.0)

Trong lần thứ hai, chúng tôi đã chèn chuyển đổi thủ công; trong lần đầu tiên trình biên dịch đã làm tương tự tự động. Việc chuyển đổi là bắt buộc vì chú thích loại ở phía bên trái.


Về đoạn trích đầu tiên của bạn từ Play:

Các hành động được giải thích trên trang này từ tài liệu Play (xem thêm tài liệu API ). Bạn đang sử dụng

apply(block: (Request[AnyContent])Result): Action[AnyContent]

trên Actionđối tượng (là bạn đồng hành với đặc điểm cùng tên).

Vì vậy, chúng ta cần cung cấp một Hàm làm đối số, có thể được viết dưới dạng một chữ theo mẫu

request => ...

Trong một hàm theo nghĩa đen, phần trước phần =>khai báo là giá trị và có thể được đánh dấu implicitnếu bạn muốn, giống như trong bất kỳ valkhai báo nào khác . Ở đây, request không cần phải đánh dấu implicitđể đánh dấu kiểm này, nhưng bằng cách đó, nó sẽ có sẵn như là một giá trị ngầm định cho bất kỳ phương thức nào có thể cần nó trong hàm (và tất nhiên, nó cũng có thể được sử dụng rõ ràng) . Trong trường hợp cụ thể này, điều này đã được thực hiện vì bindFromRequestphương thức trên lớp Form yêu cầu một Requestđối số ngầm .


12
Cảm ơn bạn đã phản hồi. Liên kết cho chương 21 thực sự tuyệt vời. Cảm kích điều đó.
Clive

14
Chỉ cần thêm điều này, video sau đây đưa ra lời giải thích tuyệt vời về ẩn ý cộng với một số tính năng khác của scala youtube.com/watch?v=IobLWVuD-CQ
Shakti

Chuyển đến 24:25 trong video trên (đối với những người không muốn nghe trong 55 phút)
papigee

36

CẢNH BÁO: chứa đựng sự mỉa mai một cách thận trọng! YMMV ...

Câu trả lời của Luigi là đầy đủ và chính xác. Điều này chỉ là để mở rộng nó một chút với một ví dụ về cách bạn có thể lạm dụng quá mức các ẩn ý , vì nó xảy ra khá thường xuyên trong các dự án Scala. Trên thực tế rất thường xuyên, bạn thậm chí có thể tìm thấy nó trong một trong những hướng dẫn "Thực hành tốt nhất" .

object HelloWorld {
  case class Text(content: String)
  case class Prefix(text: String)

  implicit def String2Text(content: String)(implicit prefix: Prefix) = {
    Text(prefix.text + " " + content)
  }

  def printText(text: Text): Unit = {
    println(text.content)
  }

  def main(args: Array[String]): Unit = {
    printText("World!")
  }

  // Best to hide this line somewhere below a pile of completely unrelated code.
  // Better yet, import its package from another distant place.
  implicit val prefixLOL = Prefix("Hello")
}

1
Haha. Khiếu hài hước.
Det

1
Tôi đánh giá cao sự hài hước. Điều này là một trong những lý do khiến tôi ngừng cố gắng học Scala từ nhiều năm trước và giờ chỉ quay lại với nó. Tôi không bao giờ chắc chắn một số (nhiều) ý nghĩa đến từ mã mà tôi đang xem xét.
melston

7

Tại sao và khi nào bạn nên đánh dấu requesttham số là implicit:

Một số phương thức mà bạn sẽ sử dụng trong phần thân của hành động của mình có một danh sách tham số ngầm định , ví dụ như Form.scala định nghĩa một phương thức:

def bindFromRequest()(implicit request: play.api.mvc.Request[_]): Form[T] = { ... }

Bạn không nhất thiết phải chú ý điều này vì bạn chỉ cần gọi myForm.bindFromRequest()Bạn không phải cung cấp các đối số ngầm rõ ràng. Không, bạn rời khỏi trình biên dịch để tìm kiếm bất kỳ đối tượng ứng cử viên hợp lệ nào để vượt qua mỗi khi nó bắt gặp một cuộc gọi phương thức yêu cầu một thể hiện của yêu cầu. Vì bạn làm có một yêu cầu có sẵn, tất cả các bạn cần làm là để đánh dấu nó như là implicit.

Bạn rõ ràng đánh dấu nó là có sẵn để sử dụng ngầm .

Bạn gợi ý trình biên dịch rằng "OK" để sử dụng đối tượng yêu cầu được gửi bởi khung Play (chúng tôi đã đặt tên "request" nhưng có thể chỉ sử dụng "r" hoặc "req") bất cứ khi nào được yêu cầu, "trên sly" .

myForm.bindFromRequest()

nhìn thấy nó? nó không có ở đó, nhưng nó có!

Nó chỉ xảy ra mà không cần bạn phải đặt thủ công ở mọi nơi cần thiết (nhưng bạn có thể vượt qua nó một cách rõ ràng, nếu bạn muốn, bất kể nó có được đánh dấu implicithay không):

myForm.bindFromRequest()(request)

Nếu không đánh dấu nó là ẩn, bạn sẽ phải làm như trên. Đánh dấu nó là ngầm định bạn không phải.

Khi nào bạn nên đánh dấu yêu cầu là implicit? Bạn chỉ thực sự cần nếu bạn đang sử dụng các phương thức khai báo danh sách tham số ngầm mong đợi một thể hiện của Yêu cầu . Nhưng để đơn giản, bạn có thể tập thói quen đánh dấu yêu cầu implicit luôn . Bằng cách đó bạn chỉ có thể viết mã terse đẹp.


2
"Bằng cách đó bạn chỉ có thể viết mã terse đẹp." Hoặc, như @DanielDinnyes chỉ ra, mã bị xáo trộn đẹp mắt. Nó có thể là một nỗi đau thực sự để theo dõi nơi một ẩn ý đến từ đâu và họ thực sự có thể làm cho mã khó đọc và duy trì hơn nếu bạn không cẩn thận.
melston

7

Trong scala ngầm hoạt động như :

Bộ chuyển đổi

Thông số giá trị kim phun

Có 3 loại sử dụng ngầm

  1. Chuyển đổi kiểu ngầm định : Nó chuyển đổi việc tạo ra lỗi gán thành loại dự định

    val x: Chuỗi = "1"

    val y: Int = x

Chuỗi không phải là kiểu con của Int , do đó, lỗi xảy ra trong dòng 2. Để giải quyết lỗi, trình biên dịch sẽ tìm một phương thức như vậy trong phạm vi có từ khóa ẩn và lấy String làm đối số và trả về một Int .

vì thế

implicit def z(a:String):Int = 2

val x :String = "1"

val y:Int = x // compiler will use z here like val y:Int=z(x)

println(y) // result 2  & no error!
  1. Chuyển đổi người nhận ngầm định : Chúng tôi thường bằng các thuộc tính của đối tượng cuộc gọi người nhận, vd. phương pháp hoặc biến. Vì vậy, để gọi bất kỳ thuộc tính nào bởi một người nhận, thuộc tính đó phải là thành viên của lớp / đối tượng của người nhận đó.

    class Mahadi{
    
    val haveCar:String ="BMW"
    
    }

    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

Ở đây mahadi.haveTv sẽ tạo ra một lỗi. Bởi vì trình biên dịch scala đầu tiên sẽ tìm kiếm haveTv tài sản để mahadi nhận. Nó sẽ không tìm thấy. Thứ hai, nó sẽ tìm kiếm một phương thức trong phạm vi có từ khóa ẩn lấy đối tượng Mahadi làm đối số và trả về đối tượng Johnny . Nhưng nó không có ở đây. Vì vậy, nó sẽ tạo ra lỗi . Nhưng sau đây là được.

class Mahadi{

val haveCar:String ="BMW"

}

class Johnny{

val haveTv:String = "Sony"

}

val mahadi = new Mahadi

implicit def z(a:Mahadi):Johnny = new Johnny

mahadi.haveTv // compiler will use z here like new Johnny().haveTv

println(mahadi.haveTv)// result Sony & no error
  1. Tiêm tham số ngầm định : Nếu chúng ta gọi một phương thức và không truyền giá trị tham số của nó, nó sẽ gây ra lỗi. Trình biên dịch scala hoạt động như thế này - đầu tiên sẽ cố gắng truyền giá trị, nhưng nó sẽ không nhận được giá trị trực tiếp cho tham số.

    def x(a:Int)= a
    
    x // ERROR happening

Thứ hai nếu tham số có bất kỳ từ khoá ngầm nó sẽ tìm kiếm bất kỳ val trong phạm vi mà có cùng loại có giá trị. Nếu không nhận được nó sẽ gây ra lỗi.

def x(implicit a:Int)= a

x // error happening here

Để slove trình biên dịch vấn đề này sẽ tìm một val ẩnloại Int vì tham số atừ khóa ẩn .

def x(implicit a:Int)=a

implicit val z:Int =10

x // compiler will use implicit like this x(z)
println(x) // will result 10 & no error.

Một vi dụ khac:

def l(implicit b:Int)

def x(implicit a:Int)= l(a)

chúng ta cũng có thể viết nó như-

def x(implicit a:Int)= l

Bởi vì l có một tham số ngầm định và trong phạm vi của thân phương thức x , nên có một biến cục bộ ẩn ( tham số là biến cục bộ ) a là tham số của x , vì vậy trong phần thân của phương thức x, giá trị đối số ẩn của chữ ký phương thức là nộp bởi các biến x phương pháp của địa phương tiềm ẩn (parameter) angầm .

Vì thế

 def x(implicit a:Int)= l

sẽ có trong trình biên dịch như thế này

def x(implicit a:Int)= l(a)

Một vi dụ khac:

def c(implicit k:Int):String = k.toString

def x(a:Int => String):String =a

x{
x => c
}

nó sẽ gây ra lỗi, bởi vì c trong x {x => c} cần truyền giá trị rõ ràng trong đối số hoặc giá trị ẩn trong phạm vi .

Vì vậy, chúng ta có thể làm cho tham số của hàm theo nghĩa rõ ràng ẩn khi chúng ta gọi phương thức x

x{
implicit x => c // the compiler will set the parameter of c like this c(x)
}

Điều này đã được sử dụng trong phương thức hành động của Play-Framework

in view folder of app the template is declared like
@()(implicit requestHreader:RequestHeader)

in controller action is like

def index = Action{
implicit request =>

Ok(views.html.formpage())  

}

nếu bạn không đề cập đến tham số yêu cầu như ngầm định thì bạn phải viết

def index = Action{
request =>

Ok(views.html.formpage()(request))  

}

4

Ngoài ra, trong trường hợp trên, nên có only onehàm ẩn có loại double => Int. Mặt khác, trình biên dịch bị lẫn lộn và sẽ không biên dịch đúng.

//this won't compile

implicit def doubleToInt(d: Double) = d.toInt
implicit def doubleToIntSecond(d: Double) = d.toInt
val x: Int = 42.0

0

Một ví dụ rất cơ bản về Implicits trong scala.

Thông số ngầm định :

val value = 10
implicit val multiplier = 3
def multiply(implicit by: Int) = value * by
val result = multiply // implicit parameter wiil be passed here
println(result) // It will print 30 as a result

Lưu ý: Ở đây multipliersẽ được truyền vào hàm multiply. Thiếu tham số cho lệnh gọi hàm được tra cứu theo loại trong phạm vi hiện tại có nghĩa là mã sẽ không biên dịch nếu không có biến ẩn của loại Int trong phạm vi.

Chuyển đổi ngầm định :

implicit def convert(a: Double): Int = a.toInt
val res = multiply(2.0) // Type conversions with implicit functions
println(res)  // It will print 20 as a result

Lưu ý: Khi chúng ta gọi multiplyhàm truyền một giá trị kép, trình biên dịch sẽ cố gắng tìm hàm ẩn chuyển đổi trong phạm vi hiện tại, chuyển đổi Intthành Double( Tham số multiplychấp nhận hàm Int). Nếu không có hàm ẩn convertthì trình biên dịch sẽ không biên dịch mã.


0

Tôi đã có cùng một câu hỏi như bạn đã có và tôi nghĩ tôi nên chia sẻ cách tôi bắt đầu hiểu nó bằng một vài ví dụ thực sự đơn giản (lưu ý rằng nó chỉ bao gồm các trường hợp sử dụng phổ biến).

Có hai trường hợp sử dụng phổ biến trong Scala sử dụng implicit.

  • Sử dụng nó trên một biến
  • Sử dụng nó trên một chức năng

Ví dụ như sau

Sử dụng nó trên một biến . Như bạn có thể thấy, nếu implicittừ khóa được sử dụng trong danh sách tham số cuối cùng, thì biến gần nhất sẽ được sử dụng.

// Here I define a class and initiated an instance of this class
case class Person(val name: String)
val charles: Person = Person("Charles")

// Here I define a function
def greeting(words: String)(implicit person: Person) = person match {
  case Person(name: String) if name != "" => s"$name, $words"
    case _ => "$words"
}

greeting("Good morning") // Charles, Good moring

val charles: Person = Person("")
greeting("Good morning") // Good moring

Sử dụng nó trên một chức năng . Như bạn có thể thấy, nếu implicitđược sử dụng trên hàm, thì phương thức chuyển đổi loại gần nhất sẽ được sử dụng.

val num = 10 // num: Int (of course)

// Here I define a implicit function
implicit def intToString(num: Int) = s"$num -- I am a String now!"

val num = 10 // num: Int (of course). Nothing happens yet.. Compiler believes you want 10 to be an Int

// Util...
val num: String = 10 // Compiler trust you first, and it thinks you have `implicitly` told it that you had a way to covert the type from Int to String, which the function `intToString` can do!
// So num is now actually "10 -- I am a String now!"
// console will print this -> val num: String = 10 -- I am a String now!

Hy vọng điều này có thể giúp đỡ.

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.