Memahami tersirat dalam Scala


308

Saya sedang mencari cara melalui tutorial Scala playframework dan saya menemukan potongan kode ini yang membuat saya bingung:

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

Jadi saya memutuskan untuk menyelidiki dan menemukan posting ini .

Saya masih belum mengerti.

Apa perbedaan antara ini:

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

dan

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

selain fakta yang jelas mereka memiliki nama metode yang berbeda.

Kapan saya harus menggunakan implicitdan mengapa?


Jawaban:


391

Saya akan menjelaskan kasus penggunaan utama dari implisit di bawah ini, tetapi untuk lebih jelasnya lihat bab Pemrograman yang relevan di Scala .

Parameter implisit

Daftar parameter akhir pada suatu metode dapat ditandai implicit, yang berarti nilai-nilai akan diambil dari konteks di mana mereka dipanggil. Jika tidak ada nilai implisit dari tipe yang tepat dalam cakupan, itu tidak akan dikompilasi. Karena nilai implisit harus diselesaikan ke nilai tunggal dan untuk menghindari bentrokan, itu ide yang baik untuk membuat tipe spesifik untuk tujuannya, misalnya tidak memerlukan metode Anda untuk menemukan implisit Int!

contoh:

  // 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"

Konversi tersirat

Ketika kompiler menemukan ekspresi tipe yang salah untuk konteks, ia akan mencari Functionnilai implisit dari tipe yang akan memungkinkannya untuk mengetik centang. Jadi jika suatu Adiperlukan dan ia menemukan a B, ia akan mencari nilai implisit dari tipe B => Adalam lingkup (ia juga memeriksa beberapa tempat lain seperti dalam Bdan Aobjek pendamping, jika ada). Karena defs dapat "eta-diperluas" menjadi Functionobjek, sebuah implicit def xyz(arg: B): Aakan melakukannya juga.

Jadi perbedaan antara metode Anda adalah bahwa yang ditandai implicitakan dimasukkan untuk Anda oleh kompiler ketika Doubleditemukan tetapi Intdiperlukan.

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

akan bekerja sama dengan

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

Pada detik kami telah memasukkan konversi secara manual; pada awalnya kompiler melakukan hal yang sama secara otomatis. Konversi diperlukan karena anotasi jenis di sisi kiri.


Mengenai cuplikan pertama Anda dari Play:

Tindakan dijelaskan pada halaman ini dari dokumentasi Play (lihat juga API docs ). Anda menggunakan

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

pada Actionobjek (yang merupakan pendamping untuk sifat dengan nama yang sama).

Jadi kita perlu menyediakan Fungsi sebagai argumen, yang dapat ditulis sebagai bentuk literal

request => ...

Dalam fungsi literal, bagian sebelum =>adalah deklarasi nilai, dan dapat ditandai implicitjika Anda mau, sama seperti valdeklarasi lainnya . Di sini, request tidak harus ditandai implicituntuk ini untuk mengetikkan cek, tetapi dengan melakukan itu akan tersedia sebagai nilai implisit untuk setiap metode yang mungkin membutuhkannya dalam fungsi (dan tentu saja, itu dapat digunakan secara eksplisit juga) . Dalam kasus khusus ini, ini telah dilakukan karena bindFromRequestmetode pada kelas Formulir memerlukan Requestargumen implisit .


12
Terima kasih atas tanggapannya. Tautan untuk bab 21 benar-benar mengagumkan. Menghargai itu.
Clive

14
Hanya untuk menambahkan ini, video berikut memberikan penjelasan yang sangat baik dari implisit ditambah beberapa fitur lain dari scala youtube.com/watch?v=IobLWVuD-CQ
Shakti

Lompat ke 24:25 dalam video di atas (bagi mereka yang tidak ingin mendengarkan selama 55 menit)
papigee

36

PERINGATAN: mengandung sarkasme dengan bijaksana! YMMV ...

Jawaban Luigi lengkap dan benar. Yang ini hanya sedikit memperluasnya dengan contoh bagaimana Anda dapat menggunakan terlalu banyak implisit , seperti yang sering terjadi dalam proyek Scala. Sebenarnya sangat sering, Anda bahkan dapat menemukannya di salah satu panduan "Praktik Terbaik" .

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
Ha ha. Punya humor yang baik.
Det

1
Saya menghargai humornya. Hal semacam ini adalah salah satu alasan saya berhenti mencoba mempelajari Scala bertahun-tahun yang lalu dan baru sekarang kembali ke sana. Saya tidak pernah yakin dari mana (banyak) dari implisit itu berasal dari kode yang saya lihat.
melston

7

Mengapa dan kapan Anda harus menandai requestparameter sebagai implicit:

Beberapa metode yang akan Anda gunakan dalam tubuh tindakan Anda memiliki daftar parameter implisit seperti, misalnya, Form.scala mendefinisikan metode:

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

Anda tidak perlu melihat ini karena Anda hanya akan menelepon myForm.bindFromRequest()Anda tidak harus memberikan argumen implisit secara eksplisit. Tidak, Anda meninggalkan kompiler untuk mencari objek kandidat yang valid untuk lulus setiap kali menemukan panggilan metode yang membutuhkan instance dari permintaan. Karena Anda lakukan memiliki permintaan yang tersedia, semua yang perlu Anda lakukan adalah untuk menandainya sebagai implicit.

Anda secara eksplisit menandainya sebagai tersedia untuk penggunaan implisit .

Anda memberi tahu kompiler bahwa "OK" untuk menggunakan objek permintaan yang dikirim oleh kerangka kerja Play (bahwa kami memberikan nama "permintaan" tetapi bisa saja menggunakan "r" atau "req") di mana saja diperlukan, "diam-diam" .

myForm.bindFromRequest()

lihat itu? itu tidak ada, tapi itu adalah ada!

Itu hanya terjadi tanpa Anda harus memasukkannya secara manual di setiap tempat yang dibutuhkan (tetapi Anda dapat meneruskannya secara eksplisit, jika Anda mau, tidak peduli apakah itu ditandai implicitatau tidak):

myForm.bindFromRequest()(request)

Tanpa menandainya sebagai implisit, Anda harus melakukan hal di atas. Menandainya sebagai implisit Anda tidak harus melakukannya.

Kapan Anda harus menandai permintaan sebagai implicit? Anda hanya perlu jika Anda menggunakan metode yang menyatakan daftar parameter implisit yang mengharapkan instance dari Permintaan . Tetapi untuk membuatnya sederhana, Anda bisa membiasakan diri untuk implicit selalu menandai permintaan . Dengan begitu Anda bisa menulis kode singkat yang indah.


2
"Dengan begitu kamu bisa menulis kode singkat yang indah." Atau, seperti yang ditunjukkan oleh @DanielDinnyes, kode yang dikaburkan dengan indah. Bisa sangat menyulitkan untuk melacak dari mana sebuah implisit berasal dan mereka benar-benar dapat membuat kode lebih sulit untuk dibaca dan dipelihara jika Anda tidak hati-hati.
melston

7

Dalam scala implisit berfungsi sebagai :

Konverter

Nilai parameter injektor

Ada 3 jenis penggunaan Implisit

  1. Konversi jenis tersirat : Ini mengkonversi tugas memproduksi kesalahan menjadi tipe yang dimaksud

    val x: String = "1"

    val y: Int = x

String bukan sub tipe Int , jadi kesalahan terjadi pada baris 2. Untuk menyelesaikan kesalahan, kompiler akan mencari metode seperti itu dalam lingkup yang memiliki kata kunci implisit dan menggunakan String sebagai argumen dan mengembalikan sebuah Int .

begitu

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. Konversi penerima secara implisit : Kami umumnya dengan properti objek panggilan penerima, misalnya. metode atau variabel. Jadi, untuk memanggil properti apa pun oleh penerima, properti harus menjadi anggota kelas / objek penerima itu.

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

    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

Di sini mahadi.haveTv akan menghasilkan kesalahan. Karena scala compiler pertama akan mencari haveTv properti untuk Mahadi penerima. Itu tidak akan menemukan. Kedua akan mencari metode dalam lingkup yang memiliki kata kunci implisit yang mengambil objek Mahadi sebagai argumen dan mengembalikan objek Johnny . Tetapi tidak ada di sini. Jadi itu akan membuat kesalahan . Tapi berikut ini tidak apa-apa.

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. Injeksi parameter secara implisit : Jika kita memanggil metode dan tidak memberikan nilai parameternya, itu akan menyebabkan kesalahan. Scala compiler berfungsi seperti ini - pertama akan mencoba memberikan nilai, tetapi tidak akan mendapatkan nilai langsung untuk parameter.

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

Kedua, jika parameter memiliki kata kunci implisit, ia akan mencari val dalam lingkup yang memiliki jenis nilai yang sama. Jika tidak mendapatkannya akan menyebabkan kesalahan.

def x(implicit a:Int)= a

x // error happening here

Untuk mencari masalah ini kompiler akan mencari val implisit yang memiliki tipe Int karena parameter a memiliki kata kunci implisit .

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.

Contoh lain:

def l(implicit b:Int)

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

kita juga bisa menulis seperti-

def x(implicit a:Int)= l

Karena l memiliki parameter implisit dan dalam lingkup tubuh metode x , ada variabel lokal implisit ( parameter adalah variabel lokal ) a yang merupakan parameter x , jadi dalam tubuh metode x nilai argumen implisit metode-tanda tangan l adalah diajukan oleh variabel (parameter) lokal implisit metode x secara aimplisit .

Begitu

 def x(implicit a:Int)= l

akan berada di kompiler seperti ini

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

Contoh lain:

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

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

x{
x => c
}

itu akan menyebabkan kesalahan, karena c dalam x {x => c} perlu secara eksplisit melewati nilai dalam argumen atau val implisit dalam lingkup .

Jadi kita dapat membuat parameter fungsi literal tersirat secara eksplisit ketika kita memanggil metode x

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

Ini telah digunakan dalam metode aksi 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())  

}

jika Anda tidak menyebutkan parameter permintaan sebagai implisit secara eksplisit maka Anda harus telah ditulis-

def index = Action{
request =>

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

}

4

Juga, dalam kasus di atas harus ada only onefungsi implisit yang tipenya double => Int. Jika tidak, kompiler menjadi bingung dan tidak dapat dikompilasi dengan benar.

//this won't compile

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

0

Contoh yang sangat mendasar dari Implicits in scala.

Parameter implisit :

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

Catatan: Di sini multipliersecara implisit akan diteruskan ke fungsi multiply. Parameter yang hilang untuk pemanggilan fungsi dilihat berdasarkan jenis dalam lingkup saat ini yang berarti bahwa kode tidak akan dikompilasi jika tidak ada variabel implisit dari tipe Int dalam ruang lingkup.

Konversi tersirat :

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

Catatan: Ketika kita memanggil multiplyfungsi yang melewati nilai ganda, kompiler akan mencoba menemukan fungsi implisit konversi dalam lingkup saat ini, yang dikonversi Intke Double(Sebagai fungsi multiplymenerima Intparameter). Jika tidak ada convertfungsi implisit maka kompiler tidak akan mengkompilasi kode.


0

Saya memiliki pertanyaan yang sama persis dengan yang Anda miliki dan saya pikir saya harus membagikan bagaimana saya mulai memahaminya dengan beberapa contoh yang sangat sederhana (perhatikan bahwa itu hanya mencakup kasus penggunaan umum).

Ada dua kasus penggunaan umum dalam penggunaan Scala implicit.

  • Menggunakannya pada variabel
  • Menggunakannya pada suatu fungsi

Contohnya adalah sebagai berikut

Menggunakannya pada variabel . Seperti yang Anda lihat, jika implicitkata kunci digunakan dalam daftar parameter terakhir, maka variabel terdekat akan digunakan.

// 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

Menggunakannya pada suatu fungsi . Seperti yang Anda lihat, jika implicitdigunakan pada fungsi, maka metode konversi tipe terdekat akan digunakan.

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!

Semoga ini bisa membantu.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.