Zrozumienie ukrytego w Scali

308

Przechodziłem przez samouczek Scala playframework i natrafiłem na ten fragment kodu, który mnie zastanawiał:

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

Postanowiłem więc to zbadać i natknąłem się na ten post .

Nadal nie rozumiem.

Jaka jest różnica między tym:

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

i

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

poza oczywistym faktem, że mają różne nazwy metod.

Kiedy powinienem używać impliciti dlaczego?

Clive
źródło
Ten samouczek okazał się bardzo przydatny: Samouczek Scala - Dowiedz się, jak utworzyć funkcję niejawną
Adrian Moisa

Odpowiedzi:

391

Poniżej wyjaśnię główne przypadki zastosowania implicitów, ale w celu uzyskania bardziej szczegółowych informacji patrz odpowiedni rozdział Programowanie w Scali .

Domniemane parametry

Końcową listę parametrów w metodzie można zaznaczyć implicit, co oznacza, że ​​wartości zostaną pobrane z kontekstu, w którym są wywoływane. Jeśli nie ma niejawnej wartości odpowiedniego typu w zakresie, nie zostanie skompilowana. Ponieważ wartość niejawna musi zostać rozpoznana jako jedna wartość i aby uniknąć kolizji, dobrym pomysłem jest uczynienie typu specyficznym dla jego celu, np. Nie wymagaj od swoich metod szukania wartości niejawnej Int!

przykład:

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

Niejawne konwersje

Gdy kompilator znajdzie wyrażenie niewłaściwego typu dla kontekstu, będzie szukał niejawnej Functionwartości typu, która pozwoli mu sprawdzić typ. Jeśli więc Ajest wymagane i znajdzie a B, to będzie szukało wartości domyślnej typu B => Aw zakresie (sprawdza także inne miejsca, takie jak w obiektach Bi Aobiektach towarzyszących, jeśli takie istnieją). Ponieważ defs można „rozszerzyć eta” na Functionobiekty, animplicit def xyz(arg: B): A zrobi.

Różnica między twoimi metodami polega na tym, że ta zaznaczona implicitzostanie wstawiona przez kompilator, gdy Doublezostanie znaleziony a, ale Intjest wymagany.

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

będzie działać tak samo jak

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

W drugiej wstawiliśmy konwersję ręcznie; w pierwszym kompilator zrobił to samo automatycznie. Konwersja jest wymagana ze względu na adnotację typu po lewej stronie.


Jeśli chodzi o Twój pierwszy fragment z Play:

Działania zostały wyjaśnione na tej stronie w dokumentacji Play (zobacz także dokumenty API ). Ty używasz

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

na Actionobiekcie (który jest towarzyszem cechy o tej samej nazwie).

Musimy więc podać funkcję jako argument, który można zapisać w postaci literału

request => ...

W literale funkcyjnym część przed =>znakiem jest deklaracją wartości i może być oznaczona, implicitjeśli chcesz, tak jak w każdej innej valdeklaracji. W tym przypadku request nie trzeba zaznaczać implicittej opcji w celu sprawdzenia typu, ale dzięki temu będzie ona dostępna jako wartość domyślna dla wszelkich metod, które mogą jej potrzebować w ramach funkcji (i oczywiście można jej również jawnie użyć) . W tym konkretnym przypadku zostało to zrobione, ponieważ bindFromRequestmetoda klasy Form wymaga niejawnego Requestargumentu.

Luigi Plinge
źródło
12
Dziękuję za odpowiedź. Link do rozdziału 21 jest naprawdę niesamowity. Doceniam to.
Clive,
14
Aby dodać to, poniższy film daje doskonałe wyjaśnienie implikacji oraz kilka innych funkcji scala youtube.com/watch?v=IobLWVuD-CQ
Shakti
Przejdź do 24:25 w powyższym filmie (dla tych, którzy nie chcą słuchać przez 55 minut)
papigee
36

UWAGA: zawiera sarkazm rozsądnie! YMMV ...

Odpowiedź Luigiego jest kompletna i poprawna. Ten jest tylko trochę rozszerzony o przykład, jak możesz chwalebnie nadużywać implicytów , jak to często zdarza się w projektach Scala. W rzeczywistości tak często można go nawet znaleźć w jednym z przewodników „najlepszych praktyk” .

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")
}
Daniel Dinnyes
źródło
1
Ha ha. Dobre poczucie humoru.
Det
1
Doceniam humor. Ten rodzaj rzeczy jest jednym z powodów, dla których przestałem próbować uczyć się Scali wiele lat temu i dopiero teraz do niej wracam. Nigdy nie byłem pewien, skąd biorą się niektóre (wiele) implikacji w kodzie, który oglądałem.
melston
7

Dlaczego i kiedy należy oznaczyć requestparametr jako implicit:

Niektóre metody, których użyjesz w treści działania, mają niejawną listę parametrów , na przykład Form.scala definiuje metodę:

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

Niekoniecznie zauważasz to, jak byś po prostu zadzwonił. myForm.bindFromRequest()Nie musisz jawnie podawać ukrytych argumentów. Nie, pozostawiasz kompilatorowi, aby szukał dowolnego poprawnego obiektu kandydującego do przekazania za każdym razem, gdy natrafi na wywołanie metody wymagające instancji żądania. Ponieważ zrobić mieć żądanie dostępne, wszystko co musisz zrobić, to zaznaczyć go jako implicit.

Użytkownik wyraźnie oznaczy go jako dostępny do niejawnego użytku.

Podpowiedziałeś kompilatorowi, że „OK” może używać obiektu żądania przesłanego przez środowisko Play (że nadaliśmy nazwę „żądanie”, ale mogliśmy użyć tylko „r” lub „req”) wszędzie tam, gdzie było to wymagane, „podstępnie” .

myForm.bindFromRequest()

Zobacz to? nie ma go, ale jest tam !

To właśnie dzieje się bez twojej konieczności włożenia go ręcznie w każdym miejscu jest to potrzebne (ale może przekazać je wyraźnie, jeśli sobie tego życzenia, bez względu na to czy jest to zaznaczone implicitlub nie):

myForm.bindFromRequest()(request)

Bez oznakowania go jako niejawny, byś musiał zrobić powyższych. Oznaczając to jako dorozumiane, nie musisz.

Kiedy należy oznaczyć wniosek jako implicit? Naprawdę potrzebujesz, jeśli korzystasz z metod, które deklarują niejawną listę parametrów oczekującą wystąpienia żądania . Ale dla uproszczenia możesz po prostu nabrać zwyczaju zaznaczania żądania implicit zawsze . W ten sposób możesz po prostu napisać piękny zwięzły kod.

Peter Perháč
źródło
2
„W ten sposób możesz po prostu napisać piękny zwięzły kod”. Lub, jak wskazuje @DanielDinnyes, pięknie zaciemniony kod. Śledzenie, skąd bierze się domniemanie, może być prawdziwym problemem, a może utrudniać odczytanie i utrzymanie kodu, jeśli nie będziesz ostrożny.
melston
7

W scala niejawne działa jako :

Przetwornik

Wtryskiwacz wartości parametru

Istnieją 3 rodzaje zastosowania Implicit

  1. Konwersja typu niejawnie : Konwertuje błąd powodujący przypisanie błędu na zamierzony typ

    val x: String = "1"

    val y: Int = x

String nie jest podtypem Int , więc błąd występuje w wierszu 2. Aby rozwiązać błąd, kompilator wyszuka taką metodę w zakresie, który zawiera niejawne słowo kluczowe i przyjmuje String jako argument i zwraca Int .

więc

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. Niejawna konwersja odbiornika : generalnie na podstawie właściwości obiektu wywołania odbiorcy, np. metody lub zmienne. Tak więc, aby wywołać dowolną właściwość przez odbiorcę, właściwość musi być członkiem klasy / obiektu tego odbiorcy.

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

    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

Tutaj mahadi.haveTv spowoduje błąd. Ponieważ kompilator Scala najpierw poszuka właściwości haveTv w odbiorniku Mahadi . Nie znajdzie. Po drugie, będzie szukał metody o zasięgu z niejawnym słowem kluczowym, która przyjmuje obiekt Mahadi jako argument i zwraca obiekt Johnny . Ale tu nie ma. To spowoduje błąd . Ale następujące jest w porządku.

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. Niejawne wprowadzanie parametrów : jeśli wywołamy metodę i nie przekażemy jej wartości parametru, spowoduje to błąd. Kompilator scala działa w ten sposób - najpierw spróbuje przekazać wartość, ale nie otrzyma bezpośredniej wartości parametru.

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

Po drugie, jeśli parametr ma żadnych niejawny słowa kluczowego będzie wyglądać za każdym val w zakresie , które mają ten sam typ wartości. Jeśli nie dostaniesz spowoduje błąd.

def x(implicit a:Int)= a

x // error happening here

Aby spowolnić ten problem, kompilator szuka niejawnej wartości typu Int, ponieważ parametr a ma niejawne słowo kluczowe .

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.

Inny przykład:

def l(implicit b:Int)

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

możemy też napisać jak

def x(implicit a:Int)= l

Ponieważ l ma niejawny parametr oraz w zakresie treści metody x , istnieje niejawna zmienna lokalna ( parametry to zmienne lokalne ) a, która jest parametrem x , więc w treści metody x wartość domyślnego argumentu l podpisu metody wynosi złożony przez lokalną zmienną niejawny x metody badaniem (parametrów) aw sposób dorozumiany .

Więc

 def x(implicit a:Int)= l

będzie w takim kompilatorze

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

Inny przykład:

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

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

x{
x => c
}

może to być przyczyną błędów, ponieważ C w X = {x> c} potrzebuje wyraźnie wartości pominięciem argument lub utajonego Val zakres .

Możemy więc uczynić parametr literału funkcji jawnie niejawnym, gdy wywołujemy metodę x

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

Zostało to wykorzystane w metodzie akcji 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())  

}

jeśli nie wymieniasz jawnie parametru żądania jako jawnego, to musisz mieć napisane-

def index = Action{
request =>

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

}
MHJ
źródło
4

Ponadto w powyższym przypadku powinna istnieć only onefunkcja niejawna, której typ jest double => Int. W przeciwnym razie kompilator zostanie zdezorientowany i nie będzie się poprawnie kompilował.

//this won't compile

implicit def doubleToInt(d: Double) = d.toInt
implicit def doubleToIntSecond(d: Double) = d.toInt
val x: Int = 42.0
Rileyss
źródło
0

Bardzo prosty przykład Implicits in scala.

Domniemane parametry :

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

Uwaga: Tutaj multiplierzostanie domyślnie przekazane do funkcji multiply. Brakujące parametry wywołania funkcji są wyszukiwane według typu w bieżącym zakresie, co oznacza, że ​​kod nie zostanie skompilowany, jeśli w zasięgu nie będzie niejawnej zmiennej typu Int.

Niejawne konwersje :

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

Uwaga: Kiedy wywołujemy multiplyfunkcję przekazującą podwójną wartość, kompilator spróbuje znaleźć funkcję niejawną konwersji w bieżącym zakresie, który konwertuje Intna Double(Jako parametr multiplyakceptujący funkcję Int). Jeśli nie ma niejawnej convertfunkcji, kompilator nie skompiluje kodu.

Keshav Lodhi
źródło
0

Miałem dokładnie to samo pytanie, co ty i myślę, że powinienem podzielić się tym, jak zacząłem to rozumieć, poprzez kilka naprawdę prostych przykładów (zauważ, że obejmuje on tylko typowe przypadki użycia).

Istnieją dwa typowe przypadki użycia przy użyciu Scali implicit.

  • Używanie go na zmiennej
  • Używanie go w funkcji

Przykłady są następujące

Używanie go w zmiennej . Jak widać, jeśli implicitsłowo kluczowe zostanie użyte na liście ostatnich parametrów, zostanie użyta najbliższa zmienna.

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

Używanie go w funkcji . Jak widać, jeśli implicitzostanie użyta w funkcji, zastosowana zostanie metoda konwersji typu najbliższego.

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!

Mam nadzieję, że to może pomóc.

RobotCharlie
źródło