Розуміння, неявне в Скалі


308

Я пробирався через навчальний посібник Scala playframework, і я натрапив на цей фрагмент коду, який мене спантеличив:

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

Я поясню основні випадки використання наслідків нижче, але більш детально дивіться відповідну главу програмування в 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 може бути "ета-розширено" на 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об’єкт (який є супутником однойменної риси).

Тому нам потрібно надати Функцію як аргумент, який може бути записаний у формі буквально у формі

request => ...

У функціональному буквальному сенсі частина перед =>символом є декларацією значення і може бути позначена, implicitякщо ви хочете, як і в будь-якому іншому valоголошенні. Тут request не потрібно позначати implicitце для перевірки набору, але, роблячи це, воно буде доступне як неявне значення для будь-яких методів, які можуть знадобитися йому в межах функції (і, звичайно, це можна використовувати і явно) . У цьому конкретному випадку це було зроблено, оскільки bindFromRequestметод класу Form вимагає неявного Requestаргументу.


12
Дякую за відповідь. Посилання для глави 21 справді приголомшливе. Оцініть це.
Клайв

14
Щоб додати це, наступне відео дає чудове пояснення наслідків плюс деякі інші особливості шкали youtube.com/watch?v=IobLWVuD-CQ
Shakti

Перейти до 24:25 у відео вище (для тих, хто не хоче слухати 55 хвилин)
papigee

36

ПОПЕРЕДЖЕННЯ: містить сарказм розсудливо! YMMV ...

Відповідь Луїджі повна і правильна. Це лише трохи розширити його прикладом того, як ви можете з великою мірою зловживати наслідками , як це часто трапляється в проектах Scala. Насправді так часто ви можете, мабуть, навіть знайти його в одному з посібників із «Кращих практик» .

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
Ха-ха. Гарне почуття гумору.
Det

1
Я ціную гумор. Така річ є однією з причин, коли я багато років тому переставав намагатися пізнати Скалу, і лише зараз повертаюся до неї. Я ніколи не був впевнений, звідки в коді, який я дивився, беруться деякі (багато) наслідки.
Мелстон

7

Чому і коли слід позначити requestпараметр як implicit:

Деякі методи, якими ви скористаєтесь в тілі вашої дії, мають неявний список параметрів , наприклад, Form.scala визначає метод:

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

Ви не обов'язково помічаєте це, як ви б тільки зателефонували. myForm.bindFromRequest()Не потрібно чітко вказувати неявні аргументи. Ні, ви залишаєте компілятор шукати будь-який дійсний кандидат-об'єкт, який потрібно передати щоразу, коли він наштовхується на виклик методу, який вимагає екземпляра запиту. Так як ви робите є запит доступні, все , що вам потрібно зробити , це помітити його як implicit.

Ви явно позначаєте його як доступне для неявного використання.

Ви натякаєте компілятору, що "добре" використовувати об'єкт запиту, надісланий в рамках Play-програми (що ми дали ім'я "запит", але могли використовувати лише "r" або "req"), де потрібно, "потворно" .

myForm.bindFromRequest()

бачити це? його там немає, але воно є !

Це просто відбувається без того, щоб вам було потрібно вручну розміщувати його в будь-якому місці, де це потрібно (але ви можете передати це явно, якщо ви цього хочете, незалежно від того, позначено це implicitчи ні):

myForm.bindFromRequest()(request)

Не позначаючи це неявним, вам доведеться зробити вищесказане. Позначаючи це як неявне, вам не потрібно.

Коли слід позначити запит як implicit? Вам дійсно потрібно лише в тому випадку, якщо ви використовуєте методи, які оголошують неявний список параметрів, очікуючи екземпляр Запити . Але щоб зробити це просто, ви могли просто ввійти в звичку маркувати запит implicit завжди . Таким чином ви можете просто написати гарний короткий код.


2
"Таким чином, ви можете просто написати гарний короткий код". Або, як вказує @DanielDinnyes, красиво заплутаний код. Визначити, звідки йде неявна інформація, може бути справжнім болем, і вони насправді можуть ускладнити читання та підтримку коду, якщо ви не будете уважні.
Мелстон

7

У Scala неявні роботи :

Перетворювач

Значення параметра інжектора

Існує 3 типи використання Implicit

  1. Неявне перетворення типу : воно перетворює помилку, що виробляє призначення, у призначений тип

    val x: String = "1"

    val y: Int = x

Рядок не є підтипом 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 видасть помилку. Тому що компілятор scala спочатку шукатиме властивість haveTv для махаді- приймача. Його не знайдуть. По-друге, він шукатиме метод в області застосування з неявним ключовим словом, яке приймає об'єкт Mahadi як аргумент і повертає об’єкт Johnny . Але його тут немає. Так це створить помилку . Але далі все гаразд.

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. Неналежне введення параметра : Якщо ми викликаємо метод і не передаємо його значення параметра, це призведе до помилки. Компілятор scala працює так - спочатку спробує передати значення, але воно не отримає прямого значення для параметра.

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

По-друге, якщо параметр має якесь неявне ключове слово, він буде шукати будь-який val в області, що має той же тип значення. Якщо не отримати, це призведе до помилки.

def x(implicit a:Int)= a

x // error happening here

Для слова цей компілятор проблем шукатиме неявний вал, що має тип Int, оскільки параметр a має неявне ключове слово .

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

Оскільки л має неявний параметр і в рамках тіла методу хрестиків , існує неявна локальні змінне ( параметри є локальними змінними ) , яка є параметром х , так що в тілі х методи методи підписи одиниці неявне значення аргументу знаходиться поданий в локальному змінному неявному методі хрестиків (параметр) неявно . 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
}

це призведе до помилки, оскільки c в x {x => c} потребує явного переходу значення в аргументі або неявного значення val в області застосування .

Таким чином , ми можемо зробити функцію літерал параметра явно неявно , коли ми називаємо метод х

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

Дуже базовий приклад неявних причин в масштабі.

Неявні параметри :

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!

Сподіваюсь, це може допомогти.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.