Поиск…


Синтаксис

  • неявный val x: T = ???

замечания

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

Использование неявных типов для обогащения существующего класса часто называют шаблоном «обогатить мою библиотеку».

Ограничения на неявные классы

  1. Неявные классы могут существовать только в пределах другого класса, объекта или свойства.
  2. Неявные классы могут иметь только один неявный первичный конструктор.
  3. В пределах той же области действия, которая имеет то же имя, что и неявный класс, не может быть другого объекта, класса, признака или определения члена класса.

Неявное преобразование

Неявное преобразование позволяет компилятору автоматически преобразовывать объект одного типа в другой тип. Это позволяет коду обрабатывать объект как объект другого типа.

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

Преобразование одностороннее: в этом случае вы не можете преобразовать 42 в Foo(42) . Для этого необходимо определить второе неявное преобразование:

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

Обратите внимание, что это механизм, посредством которого, например, можно добавить значение float к целочисленному значению.

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

Значительное влияние неявных преобразований не оказывает.

Scala автоматически импортирует различные неявные преобразования в scala.Predef , включая все преобразования с Java на Scala и обратно. Они включены по умолчанию в любую компиляцию файлов.

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

Неявные параметры могут быть полезны, если параметр типа должен быть определен один раз в области и затем применяться ко всем функциям, использующим значение этого типа.

Обычный вызов функции выглядит примерно так:

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

Теперь давайте скажем, что у нас есть некоторые методы, у которых есть время ожидания, и мы хотим вызвать все эти методы, используя один и тот же тайм-аут. Мы можем определить таймаут как неявную переменную.

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

Способ, которым это работает, заключается в том, что компилятор scalac ищет значение в области, которое помечено как неявное и тип которого соответствует одному из неявных параметров. Если он найдет один, он применит его как неявный параметр.

Обратите внимание, что это не сработает, если вы определяете два или даже больше импликации одного и того же типа в области.

Чтобы настроить сообщение об ошибке, используйте аннотацию implicitNotFound для типа:

@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]!

Тайм-аут является обычным прецедентом для этого, или, например, в Акке, ActorSystem (в большинстве случаев) всегда одинакова, поэтому она обычно передается неявно. Другим вариантом использования будет дизайн библиотеки, чаще всего с библиотеками FP, которые полагаются на классы (например, скалаз , кошки или восторг ).

Обычно считается неправильной практикой использовать неявные параметры с такими базовыми типами, как Int , Long , String и т. Д., Так как это создаст путаницу и сделает код менее читаемым.

Неявные классы

Неявные классы позволяют добавлять новые методы к ранее определенным классам.

Класс String не имеет метода withoutVowels . Это можно добавить так:

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

Неявный класс имеет единственный конструктор ( str ) с типом, который вы хотели бы расширить ( String ), и содержит метод, который вы хотите «добавить» к типу (без withoutVowels ). Новые методы теперь могут использоваться непосредственно для расширенного типа (когда расширенный тип находится в неявной области):

import StringUtil.StringEnhancer // Brings StringEnhancer into implicit scope

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

Под капотом неявные классы определяют неявное преобразование из расширенного типа в неявный класс, например:

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

Неявные классы часто определяются как классы значений, чтобы избежать создания объектов времени выполнения и, таким образом, удалить служебные данные во время выполнения:

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

С приведенным выше улучшенным определением новый экземпляр StringEnhancer не нужно создавать каждый раз, когда withoutVowels метод withoutVowels .

Разрешение неявных параметров с использованием «неявно»

Предположим, что список неявных параметров содержит более одного неявного параметра:

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

Теперь, предполагая, что один из неявных экземпляров недоступен ( SomeCtx1 ), в то время как все другие неявные экземпляры необходимы в области видимости, для создания экземпляра класса должен быть предоставлен экземпляр SomeCtx1 .

Это можно сделать, сохранив друг друга в неявном экземпляре с использованием ключевого слова implicitly :

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

Имплициты в REPL

Чтобы просмотреть все implicits в области видимости во время сеанса REPL:

scala> :implicits

Также включать неявные преобразования, определенные в Predef.scala :

scala> :implicits -v

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

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

(Пример:

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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow