Szukaj…


Składnia

  • niejawna wartość x: T = ???

Uwagi

Klasy niejawne pozwalają na dodawanie niestandardowych metod do istniejących typów bez konieczności modyfikowania ich kodu, wzbogacając w ten sposób typy bez konieczności kontroli kodu.

Używanie niejawnych typów w celu wzbogacenia istniejącej klasy jest często nazywane wzorcem „wzbogacaj moją bibliotekę”.

Ograniczenia dotyczące klas niejawnych

  1. Ukryte klasy mogą istnieć tylko w obrębie innej klasy, obiektu lub cechy.
  2. Klasy niejawne mogą mieć tylko jeden niejawny parametr konstruktora podstawowego.
  3. W tym samym zakresie nie może istnieć inna definicja obiektu, klasy, cechy lub elementu klasy o takiej samej nazwie jak klasa niejawna.

Implikowana konwersja

Ujawniona konwersja pozwala kompilatorowi automatycznie konwertować obiekt jednego typu na inny typ. Pozwala to kodowi traktować obiekt jako obiekt innego typu.

case class Foo(i: Int)

// without the implicit
Foo(40) + 2    // compilation-error (type mismatch)

// defines how to turn a Foo into an Int
implicit def fooToInt(foo: Foo): Int = foo.i

// now the Foo is converted to Int automatically when needed
Foo(40) + 2    // 42

Konwersja jest jednokierunkowa: w tym przypadku nie można przekonwertować 42 powrotem na Foo(42) . W tym celu należy zdefiniować drugą niejawną konwersję:

implicit def intToFoo(i: Int): Foo = Foo(i)

Zauważ, że jest to mechanizm, za pomocą którego można na przykład dodać liczbę zmiennoprzecinkową do wartości całkowitej.

Niejawnych konwersji należy używać oszczędnie, ponieważ zaciemniają to, co się dzieje. Najlepszą praktyką jest użycie jawnej konwersji za pomocą wywołania metody, chyba że istnieje wyraźny wzrost czytelności dzięki zastosowaniu domniemanej konwersji.

Niejawne konwersje nie mają znaczącego wpływu na wydajność.

Scala automatycznie importuje różne niejawne konwersje do scala.Predef , w tym wszystkie konwersje z Javy na Scalę iz powrotem. Są one domyślnie zawarte w dowolnej kompilacji plików.

Implikowane parametry

Parametry niejawne mogą być przydatne, jeśli parametr danego typu należy zdefiniować raz w zakresie, a następnie zastosować do wszystkich funkcji, które używają wartości tego typu.

Normalne wywołanie funkcji wygląda mniej więcej tak:

// import the duration methods
import scala.concurrent.duration._

// a normal method:
def doLongRunningTask(timeout: FiniteDuration): Long = timeout.toMillis

val timeout = 1.second
// timeout: scala.concurrent.duration.FiniteDuration = 1 second

// to call it
doLongRunningTask(timeout) // 1000

Powiedzmy teraz, że mamy pewne metody, które mają limit czasu, i chcemy wywołać wszystkie te metody przy użyciu tego samego limitu czasu. Możemy zdefiniować limit czasu jako zmienną domyślną.

// import the duration methods
import scala.concurrent.duration._

// dummy methods that use the implicit parameter
def doLongRunningTaskA()(implicit timeout: FiniteDuration): Long = timeout.toMillis
def doLongRunningTaskB()(implicit timeout: FiniteDuration): Long = timeout.toMillis

// we define the value timeout as implicit
implicit val timeout: FiniteDuration = 1.second

// we can now call the functions without passing the timeout parameter
doLongRunningTaskA() // 1000
doLongRunningTaskB() // 1000

Działa to w ten sposób, że kompilator scalac szuka wartości w zakresie, który jest oznaczony jako niejawny i którego typ jest zgodny z parametrem niejawnym. Jeśli znajdzie taki, zastosuje go jako domyślny parametr.

Zauważ, że to nie zadziała, jeśli zdefiniujesz dwa lub nawet więcej implicytów tego samego typu w zakresie.

Aby dostosować komunikat o błędzie, użyj adnotacji implicitNotFound na typie:

@annotation.implicitNotFound(msg = "Select the proper implicit value for type M[${A}]!")
case class M[A](v: A) {}

def usage[O](implicit x: M[O]): O = x.v

//Does not work because no implicit value is present for type `M[Int]`
//usage[Int]   //Select the proper implicit value for type M[Int]!
implicit val first: M[Int] = M(1)
usage[Int]     //Works when `second` is not in scope
implicit val second: M[Int] = M(2)
//Does not work because more than one implicit values are present for the type `M[Int]`
//usage[Int]   //Select the proper implicit value for type M[Int]!

Limit czasu jest typowym przypadkiem użycia w tym przypadku, lub na przykład w Akka ActorSystem jest (przez większość czasu) zawsze taki sam, więc zwykle jest przekazywany niejawnie. Innym przykładem użycia może być projekt biblioteki, najczęściej w bibliotekach FP, które opierają się na typach klas (takich jak scalaz , koty lub rapture ).

Ogólnie uważa się za złą praktykę stosowanie niejawnych parametrów z podstawowymi typami, takimi jak Int , Long , String itp., Ponieważ spowoduje to zamieszanie i sprawi, że kod będzie mniej czytelny.

Klasy niejawne

Klasy niejawne umożliwiają dodawanie nowych metod do wcześniej zdefiniowanych klas.

Klasa String nie ma metody withoutVowels . Można to dodać w następujący sposób:

object StringUtil {
  implicit class StringEnhancer(str: String) {
    def withoutVowels: String = str.replaceAll("[aeiou]", "")
  }
}

Klasa niejawna ma jeden parametr konstruktora ( str ) z typem, który chcesz rozszerzyć ( String ) i zawiera metodę, którą chcesz „dodać” do typu (bez withoutVowels ). Nowo zdefiniowane metody mogą być teraz używane bezpośrednio na ulepszonym typie (gdy ulepszony typ ma ukryty zakres):

import StringUtil.StringEnhancer // Brings StringEnhancer into implicit scope

println("Hello world".withoutVowels) // Hll wrld

Pod maską klasy niejawne definiują niejawną konwersję z typu rozszerzonego na klasę niejawną, w następujący sposób:

implicit def toStringEnhancer(str: String): StringEnhancer = new StringEnhancer(str)

Klasy niejawne są często definiowane jako klasy wartości, aby uniknąć tworzenia obiektów środowiska wykonawczego, a tym samym usuwania narzutu środowiska wykonawczego:

implicit class StringEnhancer(val str: String) extends AnyVal {
    /* conversions code here */
}

Dzięki powyższej ulepszonej definicji nowe wystąpienie StringEnhancer nie musi być tworzone za każdym razem, gdy withoutVowels metoda withoutVowels .

Rozwiązywanie niejawnych parametrów za pomocą „niejawnie”

Zakładając niejawną listę parametrów z więcej niż jednym niejawnym parametrem:

case class Example(p1:String, p2:String)(implicit ctx1:SomeCtx1, ctx2:SomeCtx2)

Teraz, zakładając, że jedna z niejawnych instancji nie jest dostępna ( SomeCtx1 ), podczas gdy wszystkie inne potrzebne niejawne instancje są objęte zakresem, aby utworzyć instancję klasy, należy podać instancję SomeCtx1 .

Można to zrobić, zachowując się wzajemnie w niejawnej instancji w zakresie za pomocą słowa kluczowego implicitly :

Example("something","somethingElse")(new SomeCtx1(), implicitly[SomeCtx2])

Wpisuje się w REPL

Aby wyświetlić wszystkie implicits w zakresie podczas sesji REPL:

scala> :implicits

Aby uwzględnić także niejawne konwersje zdefiniowane w Predef.scala :

scala> :implicits -v

Jeśli ktoś ma wyrażenie i chce zobaczyć efekt wszystkich reguł przepisywania, które go dotyczą (w tym implikacji):

scala> reflect.runtime.universe.reify(expr) // No quotes. reify is a macro operating directly on code.

(Przykład:

scala> import reflect.runtime.universe._
scala> reify(Array("Alice", "Bob", "Eve").mkString(", "))
resX: Expr[String] = Expr[String](Predef.refArrayOps(Array.apply("Alice", "Bob", "Eve")(Predef.implicitly)).mkString(", "))

)



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow