Понимание неявного в Scala

308

Я пробирался через учебник по игровой структуре 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и почему?

Clive
источник
Этот учебник
оказался

Ответы:

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требуется и он находит 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
Просто чтобы добавить это, следующее видео дает отличное объяснение последствий, а также некоторые другие функции scala youtube.com/watch?v=IobLWVuD-CQ
Шакти
Перейдите к 24:25 в видео выше (для тех, кто не хочет слушать в течение 55 минут)
papigee
36

ВНИМАНИЕ: содержит сарказм разумно! YMMV ...

Ответ Луиджи полный и правильный. Это только для того, чтобы немного расширить его на примере того, как вы можете великолепно злоупотреблять последствиями , как это часто случается в проектах Scala. На самом деле так часто, вы, вероятно, даже можете найти его в одном из руководств "Best Practice" .

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
Я ценю юмор. Подобные вещи - одна из причин, по которой я перестал пытаться изучать Scala много лет назад, и только сейчас возвращаюсь к этому. Я никогда не был уверен, откуда взялись некоторые (многие) последствия в коде, который я просматривал.
Мелстон
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

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 выдаст ошибку. Поскольку компилятор Scala будет первым искать haveTv имущества mahadi приемника. Это не найдет. Во-вторых, он будет искать метод в области видимости, имеющий неявное ключевое слово, которое принимает объект 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

Во- вторых , если параметр имеет какое - либо ключевое слово неявный он будет искать любой Валу в рамках , которые имеют одинаковый тип значения. Если не получить, это приведет к ошибке.

def x(implicit a:Int)= a

x // error happening here

Чтобы решить эту проблему, компилятор будет искать неявный val, имеющий тип 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

Поскольку у l есть неявный параметр и в области видимости тела метода x есть неявная локальная переменная ( параметры являются локальными переменными ) a, которая является параметром x , поэтому в теле метода x значение неявного аргумента сигнатуры метода 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))  

}
MHJ
источник
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
rileyss
источник
0

Очень простой пример использования Scala.

Неявные параметры :

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!

Надеюсь, это поможет.

RobotCharlie
источник