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

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

Ответ 1

Ниже я объясню основные варианты использования последствий, но более подробно см. Соответствующую главу "Программирование в 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 значение типа, что позволит ему typecheck. Поэтому, если требуется A и он находит B, он ищет неявное значение типа B => A в области видимости (он также проверяет некоторые другие места, например, в объектах-компаньонах B и A, если они существуют). Поскольку def может быть "расширенно" до объектов 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.

Ответ 2

ПРЕДУПРЕЖДЕНИЕ: содержит сарказм разумно! 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")
}

Ответ 3

Почему и когда вы должны пометить параметр request как implicit:

Некоторые методы, которые вы будете использовать в теле вашего действия, имеют список неявных параметров, например, Form.scala определяет метод:

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

Вы не обязательно замечаете это, так как вы просто вызываете myForm.bindFromRequest(). Вам не нужно явно указывать неявные аргументы. Нет, вы оставляете компилятор для поиска любого действительного объекта-кандидата для передачи при каждом вызове метода, который требует экземпляр запроса. Поскольку у вас есть запрос, все, что вам нужно сделать, это отметить его как implicit.

Вы явно помечены как доступные для неявного использования.

Вы намекаете компилятору, что он "ОК" использует объект запроса, отправленный платформой Play (мы дали имя "запрос", но могли бы использовать только "r" или "req" ) везде, где это необходимо, "on хитрый".

myForm.bindFromRequest()

видеть это? он не там, но он есть!

Это происходит просто, если вам не нужно вставлять его вручную в нужное место (но вы можете передать его явно, если хотите, независимо от того, помечено ли оно implicit или нет):

myForm.bindFromRequest()(request)

Не помещая его как неявное, вы должны выполнить описанное выше. Помечая это как неявное, вам не нужно.

Когда следует пометить запрос как implicit? Вам действительно нужно, только если вы используете методы, объявляющие список неявных параметров, ожидающий экземпляр запроса. Но чтобы это было просто, вы могли просто привыкнуть отмечать запрос implicit всегда. Таким образом, вы можете просто написать красивый краткий код.

Ответ 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

Ответ 5

В 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 приемника. Это не найдет. Во-вторых, он будет искать метод в области видимости, имеющий неявное ключевое слово, которое принимает объект Махади в качестве аргумента и возвращает объект Джонни. Но это не имеет здесь. Так что это создаст ошибку. Но следующее хорошо.

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)

мы также можем написать это like-

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

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

Это был использован в действии метод Play-Framework

def index = Action{
implicit request =>

Ok(views.html.formpage)

}

Ответ 6

Лучший ответ здесь

Это лучший ответ, который я когда-либо читал. Это было описано пошагово. Полагаю, что после прочтения этого ответа никаких проблем с неявным не останется :)