การทำความเข้าใจโดยนัยใน Scala


308

ฉันกำลังทำของฉันผ่านการกวดวิชา playframework Scala และฉันเจอรหัสนี้ซึ่งทำให้ฉันงงงวย:

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

ดังนั้นฉันตัดสินใจที่จะตรวจสอบและเจอโพสต์นี้

ฉันยังไม่เข้าใจ

อะไรคือความแตกต่างระหว่างสิ่งนี้:

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

และ

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

นอกจากความจริงที่เห็นได้ชัดพวกเขามีชื่อวิธีการที่แตกต่างกัน

ฉันควรใช้เมื่อใดimplicitและเพราะเหตุใด


คำตอบ:


391

ฉันจะอธิบายกรณีการใช้งานหลักของ implicits ด้านล่าง แต่สำหรับรายละเอียดเพิ่มเติมโปรดดูที่บทที่เกี่ยวข้องของการเขียนโปรแกรมใน Scala

พารามิเตอร์โดยนัย

สามารถทำเครื่องหมายรายการพารามิเตอร์สุดท้ายในวิธีการimplicitซึ่งหมายความว่าค่าจะถูกนำมาจากบริบทที่พวกเขาถูกเรียก หากไม่มีค่าโดยนัยของชนิดที่ถูกต้องในขอบเขตมันจะไม่คอมไพล์ เนื่องจากค่าโดยนัยจะต้องแก้ไขเป็นค่าเดียวและเพื่อหลีกเลี่ยงการปะทะกันจึงเป็นความคิดที่ดีที่จะสร้างประเภทเฉพาะตามวัตถุประสงค์เช่นไม่ต้องการวิธีการของคุณในการค้นหาสิ่งที่เป็นนัยInt!

ตัวอย่าง:

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

การแปลงโดยนัย

เมื่อคอมไพเลอร์พบการแสดงออกของประเภทที่ไม่ถูกต้องสำหรับบริบทมันจะมองหาFunctionค่าโดยนัยของประเภทที่จะช่วยให้มันพิมพ์ ดังนั้นหากAจำเป็นต้องใช้และพบว่าBมันจะหาค่าปริยายของประเภทB => Aในขอบเขต (มันยังตรวจสอบสถานที่อื่น ๆ เช่นในวัตถุBและAสหายหากมี เนื่องจากdefs สามารถ "eta-expand" ลงในFunctionวัตถุได้จึงimplicit def xyz(arg: B): Aจะทำเช่นกัน

ดังนั้นความแตกต่างระหว่างเมธอดของคุณคือimplicitคอมไพเลอร์ที่ถูกทำเครื่องหมายไว้จะถูกแทรกสำหรับคุณเมื่อDoubleพบ แต่Intจำเป็นต้องมี

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

จะทำงานเหมือนกัน

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

ในวินาทีที่เราได้แทรกการแปลงด้วยตนเอง; ในคอมไพเลอร์ตัวแรกทำแบบเดียวกันโดยอัตโนมัติ ต้องการการแปลงเนื่องจากการเพิ่มความคิดเห็นแบบทางด้านซ้ายมือ


เกี่ยวกับข้อมูลโค้ดแรกของคุณจาก Play:

คำอธิบายการกระทำในหน้านี้จากเอกสาร Play (ดูเอกสาร API ) คุณกำลังใช้

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

บนActionวัตถุ (ซึ่งเป็นคู่หูกับลักษณะที่มีชื่อเดียวกัน)

ดังนั้นเราจำเป็นต้องจัดหา Function เป็นอาร์กิวเมนต์ซึ่งสามารถเขียนเป็นตัวอักษรในรูปแบบ

request => ...

ในฟังก์ชันตามตัวอักษรส่วนก่อนหน้า=>คือการประกาศค่าและสามารถทำเครื่องหมายได้implicitหากคุณต้องการเช่นเดียวกับvalการประกาศอื่น ๆ ที่นี่request ไม่จำเป็นต้องทำเครื่องหมายimplicitเพื่อพิมพ์การตรวจสอบ แต่โดยการทำเช่นนั้นจะสามารถใช้เป็นค่าโดยนัยสำหรับวิธีการใด ๆ ที่อาจจำเป็นต้องใช้ภายในฟังก์ชัน (และแน่นอนว่ามันสามารถใช้อย่างชัดเจนเช่นกัน) . ในกรณีพิเศษนี้ทำได้เนื่องจากbindFromRequestวิธีการในคลาสFormต้องการRequestอาร์กิวเมนต์โดยนัย


12
ขอบคุณสำหรับคำตอบ ลิงค์สำหรับบทที่ 21 นั้นยอดเยี่ยมจริงๆ ขอบคุณมัน
ไคลฟ์

14
เพียงเพิ่มวิดีโอต่อไปนี้ให้คำอธิบายที่ยอดเยี่ยมเกี่ยวกับ implicits รวมถึงคุณสมบัติอื่น ๆ ของ scala youtube.com/watch?v=IobLWVuD-CQ
Shakti

ข้ามไปที่24:25ในวิดีโอด้านบน (สำหรับผู้ที่ไม่ต้องการฟังตลอด 55 นาที)
papigee

36

คำเตือน:มีการเสียดสีอย่างรอบคอบ! YMMV ...

คำตอบของ Luigiนั้นสมบูรณ์และถูกต้อง หนึ่งในนี้เป็นเพียงที่จะขยายมัน bit กับตัวอย่างของวิธีที่คุณสามารถเฉิดฉายมากเกินไปimplicitsขณะที่มันเกิดขึ้นค่อนข้างบ่อยในโครงการสกาล่า ที่จริงแล้วบ่อยครั้งคุณอาจพบได้ในคำแนะนำ"แนวทางปฏิบัติที่ดีที่สุด"

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
ฮ่าฮ่า อารมณ์ขันดี
เดชอุดม

1
ฉันซาบซึ้งอารมณ์ขัน สิ่งนี้เป็นหนึ่งในเหตุผลที่ฉันหยุดพยายามเรียนรู้สกาล่าเมื่อหลายปีก่อนและตอนนี้ฉันก็กลับมาที่นี่อีกครั้ง ฉันไม่เคยแน่ใจว่า implicits (มาก) บางส่วนมาจากรหัสที่ฉันดู
melston

7

ทำไมและเมื่อคุณควรทำเครื่องหมายrequestพารามิเตอร์เป็นimplicit:

วิธีการบางอย่างที่คุณจะใช้ในส่วนของการกระทำของคุณมีรายการพารามิเตอร์โดยนัยเช่น Form.scala กำหนดวิธีการ:

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

คุณไม่จำเป็นต้องสังเกตสิ่งนี้เพราะคุณเพียงแค่โทรหาmyForm.bindFromRequest()คุณไม่จำเป็นต้องให้ข้อโต้แย้งโดยนัยอย่างชัดเจน ไม่คุณออกจากคอมไพเลอร์เพื่อค้นหาออบเจ็กต์ที่ถูกต้องที่จะผ่านทุกครั้งที่พบการเรียกเมธอดที่ต้องการอินสแตนซ์ของการร้องขอ เนื่องจากคุณทำimplicitมีคำขอที่มีอยู่ทั้งหมดที่คุณต้องทำคือการทำเครื่องหมายว่า

คุณทำเครื่องหมายอย่างชัดเจนว่าพร้อมใช้งานโดยนัย

คุณแนะนำคอมไพเลอร์ว่า "ตกลง" เพื่อใช้วัตถุคำขอที่ส่งโดย Play Framework (ซึ่งเราให้ชื่อ "คำขอ" แต่สามารถใช้เพียงแค่ "r" หรือ "req") เมื่อใดก็ตามที่ต้องการ "กับเจ้าเล่ห์" .

myForm.bindFromRequest()

เห็นไหม มันไม่ได้อยู่ที่นั่น แต่อยู่ที่นั่น!

มันเกิดขึ้นโดยที่คุณไม่ต้องเสียบมันเข้าด้วยตนเองในทุกที่ที่ต้องการ (แต่คุณสามารถผ่านมันได้อย่างชัดเจนหากคุณต้องการไม่ว่าจะทำเครื่องหมายimplicitหรือไม่ก็ตาม):

myForm.bindFromRequest()(request)

คุณจะต้องทำตามข้างต้นโดยไม่ทำเครื่องหมายว่าเป็นนัย ทำเครื่องหมายว่าคุณไม่จำเป็นต้องทำ

เมื่อใดที่คุณควรทำเครื่องหมายคำขอเป็นimplicit? คุณจริงๆต้องถ้าคุณมีการใช้วิธีการที่ประกาศรายการพารามิเตอร์นัยคาดหวังว่าตัวอย่างของการขอใช้บริการที่ แต่การที่จะให้มันง่ายคุณก็อาจจะได้รับเป็นนิสัยของการทำเครื่องหมายการร้องขอเสมอimplicit ด้วยวิธีนี้คุณสามารถเขียนโค้ดตัวย่อที่สวยงามได้


2
"ด้วยวิธีนี้คุณสามารถเขียนโค้ดตัวย่อที่สวยงาม" หรือตามที่ @DanielDinnyes ชี้ให้เห็นรหัสที่ทำให้งงงวยอย่างสวยงาม มันอาจเป็นความเจ็บปวดที่แท้จริงในการติดตามว่ามีนัยมาจากไหนและพวกเขาสามารถทำให้อ่านและบำรุงรักษาโค้ดได้ยากขึ้นหากคุณไม่ระวัง
melston

7

ในงานสกาล่าโดยปริยายเป็น :

แปลง

หัวฉีดค่าพารามิเตอร์

การใช้งานโดยนัยมี 3 ประเภท

  1. การแปลงประเภทโดยนัย : แปลงข้อผิดพลาดที่ทำให้การกำหนดเป็นประเภทที่ต้องการ

    val x: String = "1"

    val y: Int = x

Stringไม่ได้เป็นชนิดย่อยของ Intดังนั้นข้อผิดพลาดที่เกิดขึ้นในสาย 2. ในการแก้ไขข้อผิดพลาดคอมไพเลอร์จะมองหาวิธีการดังกล่าวอยู่ในขอบเขตที่มีคำหลักโดยปริยายและใช้เวลาเป็นสตริงเป็นอาร์กิวเมนต์และส่งกลับInt

ดังนั้น

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. การแปลงตัวรับโดยปริยาย : โดยทั่วไปแล้วเราจะใช้คุณสมบัติของวัตถุที่รับสัญญาณเช่น วิธีการหรือตัวแปร ดังนั้นการเรียกคุณสมบัติใด ๆ โดยผู้รับคุณสมบัติต้องเป็นสมาชิกของคลาส / วัตถุของผู้รับนั้น

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

    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

ที่นี่mahadi.haveTvจะสร้างข้อผิดพลาด เพราะสกาล่าคอมไพเลอร์แรกจะมองหาhaveTvคุณสมบัติการMahadiรับ มันจะไม่พบ ประการที่สองก็จะมองหาวิธีการที่อยู่ในขอบเขตที่มีคำหลักโดยปริยายซึ่งใช้วัตถุ Mahadiเป็นอาร์กิวเมนต์และส่งกลับวัตถุจอห์นนี่ แต่มันไม่มีที่นี่ ดังนั้นมันจะสร้างข้อผิดพลาด แต่ต่อไปนี้ก็โอเค

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. การฉีดพารามิเตอร์โดยปริยาย : ถ้าเราเรียกใช้เมธอดและไม่ผ่านค่าพารามิเตอร์มันจะทำให้เกิดข้อผิดพลาด คอมไพเลอร์สกาล่าทำงานเช่นนี้ - ก่อนจะพยายามส่งผ่านค่า แต่จะไม่ได้รับค่าโดยตรงสำหรับพารามิเตอร์

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

ประการที่สองถ้าพารามิเตอร์มีคำหลักนัยใด ๆ ก็จะมองหาใด ๆValในขอบเขตที่มีชนิดเดียวกันของมูลค่า หากไม่ได้รับจะทำให้เกิดข้อผิดพลาด

def x(implicit a:Int)= a

x // error happening here

เพื่อ slove เรียบเรียงปัญหานี้จะมองหาVal นัยมีประเภทของ Intเพราะพารามิเตอร์มีคำหลักโดยปริยาย

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.

ตัวอย่างอื่น:

def l(implicit b:Int)

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

เรายังสามารถเขียนมันเหมือน -

def x(implicit a:Int)= l

เพราะลิตรมีพารามิเตอร์โดยปริยายและอยู่ในขอบเขตของร่างกายวิธีการของ Xจะมีตัวแปรท้องถิ่นโดยปริยาย ( พารามิเตอร์ตัวแปรท้องถิ่น ) ซึ่งเป็นพารามิเตอร์ของxดังนั้นในร่างกายของ x วิธีวิธีลายเซ็นค่าอาร์กิวเมนต์ l's นัยคือ ยื่นฟ้องโดยตัวแปรนัย x วิธีการของท้องถิ่น (พารามิเตอร์)โดยปริยาย a

ดังนั้น

 def x(implicit a:Int)= l

จะอยู่ในคอมไพเลอร์แบบนี้

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

ตัวอย่างอื่น:

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

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

x{
x => c
}

ก็จะทำให้เกิดข้อผิดพลาดเพราะในx {x => C}ความต้องการในการโต้เถียงหรือ Val นัยผ่านอย่างชัดเจนมูลค่าอยู่ในขอบเขต

ดังนั้นเราจึงสามารถทำให้ตัวอักษรของฟังก์ชันพารามิเตอร์อย่างชัดเจนโดยปริยายเมื่อเราเรียกวิธีการ x

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

สิ่งนี้ถูกใช้ในวิธีการทำงานของ 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())  

}

ถ้าคุณไม่พูดถึงพารามิเตอร์คำขอโดยปริยายอย่างชัดเจนคุณจะต้องเขียน -

def index = Action{
request =>

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

}

4

นอกจากนี้ในกรณีดังกล่าวข้างต้นควรจะมีฟังก์ชั่นที่มีนัยประเภทคือonly one double => Intมิฉะนั้นคอมไพเลอร์จะสับสนและจะไม่รวบรวมอย่างถูกต้อง

//this won't compile

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

0

ตัวอย่างพื้นฐานของ Implicits ในสกาล่า

พารามิเตอร์โดยนัย :

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

หมายเหตุ:ที่นี่จะถูกส่งผ่านโดยปริยายเข้าสู่ฟังก์ชั่นmultiplier multiplyพารามิเตอร์ที่ขาดไปยังการเรียกใช้ฟังก์ชันถูกค้นหาตามประเภทในขอบเขตปัจจุบันหมายความว่ารหัสจะไม่รวบรวมหากไม่มีตัวแปรโดยนัยของประเภท Int ในขอบเขต

การแปลงโดยนัย :

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

หมายเหตุ:เมื่อเราเรียกmultiplyใช้ฟังก์ชันที่ผ่านค่าสองเท่าคอมไพเลอร์จะพยายามค้นหาฟังก์ชันการแปลงโดยนัยในขอบเขตปัจจุบันซึ่งแปลงIntเป็นDouble(เป็นพารามิเตอร์multiplyรับฟังก์ชั่นInt) หากไม่มีconvertฟังก์ชั่นโดยปริยายคอมไพเลอร์จะไม่รวบรวมรหัส


0

ฉันมีคำถามเดียวกันกับที่คุณมีและฉันคิดว่าฉันควรแบ่งปันวิธีที่ฉันเริ่มเข้าใจโดยตัวอย่างง่ายๆจริง ๆ (โปรดทราบว่าครอบคลุมเฉพาะกรณีการใช้งานทั่วไป)

มีสองกรณีการใช้งานทั่วไปใน Scala implicitใช้เป็น

  • ใช้มันกับตัวแปร
  • ใช้มันในฟังก์ชั่น

ตัวอย่างมีดังนี้

ใช้มันในตัวแปร อย่างที่คุณเห็นถ้าimplicitคำสำคัญถูกใช้ในรายการพารามิเตอร์สุดท้ายตัวแปรที่ใกล้เคียงที่สุดจะถูกใช้

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

ใช้มันในฟังก์ชั่น อย่างที่คุณเห็นหากimplicitมีการใช้งานฟังก์ชั่นวิธีการแปลงประเภทที่ใกล้เคียงที่สุดจะถูกนำมาใช้

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!

หวังว่านี่จะช่วยได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.