Implicit scala что это
В этом уроке вы узнаете:
- Видимое ограничение («Классы-типы»)
- Типы высшего порядка и специальный полиморфизм
- F-ограниченный полиморфизм / рекурсивные типы
- Структурные типы
- Абстрактные типы членов
- Тип чисток и манифесты
- Пример: Finagle
Видимое ограничение («Классы-типы»)
Неявные функции в Scala позволяют использовать функции по требованию, когда это может помочь при выводе типа, например:
scala> implicit def strToInt(x: String) = x.toInt strToInt: (x: String)Int scala> "123" res0: java.lang.String = 123 scala> val y: Int = "123" y: Int = 123 scala> math.max("123", 111) res1: Int = 123
Видимое ограничение, подобно ограничению типа, требует функцию, которая существует для данного типа, например:
scala> class Container[A defined class Container
Это говорит, что A должен быть «видим» подобно Int. Давайте попробуем.
scala> (new Container[String]).addIt("123") res11: Int = 246 scala> (new Container[Int]).addIt(123) res12: Int = 246 scala> (new Container[Float]).addIt(123.2F) :8: error: could not find implicit value for evidence parameter of type (Float) => Int (new Container[Float]).addIt(123.2) ^
Другие ограничения типов
Методы могут запросить конкретные «доказательства» для типа, а именно:
| A =:= B | A должен быть равен B |
| A | A должен быть подтипом B |
| A | A должен выглядеть как B |
scala> class Container[A](value: A) < def addIt(implicit evidence: A =:= Int) = 123 + value >defined class Container scala> (new Container(123)).addIt res11: Int = 246 scala> (new Container("123")).addIt :10: error: could not find implicit value for parameter evidence: =:=[java.lang.String,Int]
Кроме того, учитывая наше предыдущее неявное значение, мы можем ослабить ограничение для видимости:
scala> class Container[A](value: A) < def addIt(implicit evidence: A defined class Container scala> (new Container("123")).addIt res15: Int = 246
Обобщенное программирование с помощью видов
В стандартной библиотеке Scala, виды в основном используются для реализации обобщенных функций коллекций. Например, функция «min» (Seq[]), использует эту технику:
def min[B >: A](implicit cmp: Ordering[B]): A = < if (isEmpty) throw new UnsupportedOperationException("empty.min") reduceLeft((x, y) =>if (cmp.lteq(x, y)) x else y) >
Основными преимуществами этого являются:
- Элементам коллекции не требуется реализовывать Ordered, хотя Ordered по-прежнему использует статическую проверку типов.
- Вы можете определить свой собственный порядок сортировки без необходимости использовать дополнительную библиотеку:
scala> List(1,2,3,4).min res0: Int = 1 scala> List(1,2,3,4).min(new Ordering[Int] < def compare(a: Int, b: Int) = b compare a >) res3: Int = 4
Небольшое замечание, есть виды в стандартной библиотеке, которые переводят Ordered в Ordering (и наоборот).
trait LowPriorityOrderingImplicits < implicit def ordered[A >
Ограничения контекста и implicitly[]
В Scala 2.8 введена сокращенная форма для передачи и для доступа с использованием неявных аргументов.
scala> def foo[A](implicit x: Ordered[A]) <> foo: [A](implicit x: Ordered[A])Unit scala> def foo[A : Ordered] <> foo: [A](implicit evidence$1: Ordered[A])Unit
Неявные значения могут быть доступны через implicitly
scala> implicitly[Ordering[Int]] res37: Ordering[Int] = scala.math.Ordering$Int$@3a9291cf
В совокупности это часто приводит к меньшему количеству кода, особенно при передаче с использованием видов.
Типы высшего порядка и специальный полиморфизм
Scala может абстрагировать типы «высшего порядка». Это похоже на каррирование функции. Например, в то время как «унарные типы» имеют конструкторы вроде этого:
List[A]
То есть мы должны удовлетворять определенному «уровню» типовых переменных с целью получения конкретных типов (подобно тому, как uncurried функция должна применяться только к одному списку аргументов, при вызове), типам высшего порядка требуется больше:
scala> trait Container[M[_]] < def put[A](x: A): M[A]; def get[A](m: M[A]): A >scala> val container = new Container[List] < def put[A](x: A) = List(x); def get[A](m: List[A]) = m.head >container: java.lang.Object with Container[List] = $anon$1@7c8e3f75 scala> container.put("hey") res24: List[java.lang.String] = List(hey) scala> container.put(123) res25: List[Int] = List(123)
Заметьте, что Container является полиморфным в параметрическом типе («тип контейнер»).
Если мы объединим использование контейнеров с неявными выражениями, мы получим «специальный» полиморфизм: возможность писать обобщенные контейнеры поверх контейнеров.
scala> trait Container[M[_]] < def put[A](x: A): M[A]; def get[A](m: M[A]): A >scala> implicit val listContainer = new Container[List] < def put[A](x: A) = List(x); def get[A](m: List[A]) = m.head >scala> implicit val optionContainer = new Container[Some] < def put[A](x: A) = Some(x); def get[A](m: Some[A]) = m.get >scala> def tupleize[M[_]: Container, A, B](fst: M[A], snd: M[B]) = < | val c = implicitly[Container[M]] | c.put(c.get(fst), c.get(snd)) | >tupleize: [M[_],A,B](fst: M[A],snd: M[B])(implicit evidence$1: Container[M])M[(A, B)] scala> tupleize(Some(1), Some(2)) res33: Some[(Int, Int)] = Some((1,2)) scala> tupleize(List(1), List(2)) res34: List[(Int, Int)] = List((1,2))
F-ограниченный полиморфизм
Часто необходим доступ к конкретному подклассу в (обобщенном) трейте. Например, представьте себе некоторый трейт, который является обобщенным, но может быть сравним с конкретным подклассом данного трейта.
trait Container extends Ordered[Container]
Тем не менее, сейчас требуется сравнение
def compare(that: Container): Int
И поэтому мы не можем получить доступ к конкретному подтипу, например:
class MyContainer extends Container
код не скомпилируется, так как мы определяем Ordered для Container, а не конкретный подтип.
Чтобы это согласовать, мы используем F-ограниченный полиморфизм.
trait Container[AСтранный тип! Но заметьте, как Ordered параметризован с помощью A, который сам по себе является Container[A]
class MyContainer extends Container[MyContainer]Они сейчас упорядочены:
scala> List(new MyContainer, new MyContainer, new MyContainer) res3: List[MyContainer] = List(MyContainer@30f02a6d, MyContainer@67717334, MyContainer@49428ffa) scala> List(new MyContainer, new MyContainer, new MyContainer).min res4: MyContainer = MyContainer@33dfeb30Учитывая, что все они являются подтипами Container[_], мы можем определить другой подкласс и создать смешанный список Container[_]:
scala> class YourContainer extends Container[YourContainer] < def compare(that: YourContainer) = 0 >defined class YourContainer scala> List(new MyContainer, new MyContainer, new MyContainer, new YourContainer) res2: List[Container[_ >: YourContainer with MyContainer : YourContainer with MyContainerОбратите внимание, как результирующий тип в настоящее время ограничен снизу YourContainer с MyContainer. Это работа системы вывода типов. Интересно, что этот тип не имеет дополнительного смысла, он только обеспечивает логическую нижнюю границу для списка. Что произойдет, если мы попытаемся использовать Ordered сейчас?
(new MyContainer, new MyContainer, new MyContainer, new YourContainer).min :9: error: could not find implicit value for parameter cmp: Ordering[Container[_ >: YourContainer with MyContainer : YourContainer with MyContainerOrdered[] не существует для единого типа. Это слишком плохо.
Структурные типы
Scala имеет поддержку структурных типов — тип выражается интерфейсом structure вместо конкретного типа.
scala> def foo(x: < def get: Int >) = 123 + x.get foo: (x: AnyRef)Int scala> foo(new < def get = 10 >) res0: Int = 133Это может быть полезно во многих ситуациях, но реализация использует отражения, так что обращайте внимание на производительность.
Абстрактные типы членов
В трейте, вы можете оставить тип членов абстрактным.
scala> trait Foo < type A; val x: A; def getX: A = x >defined trait Foo scala> (new Foo < type A = Int; val x = 123 >).getX res3: Int = 123 scala> (new Foo < type A = String; val x = "hey" >).getX res4: java.lang.String = heyЧасто это полезный трюк, когда делается внедрение зависимостей, например.
Вы можете обратиться к абстрактному типу переменной, используя хеш-оператор:
scala> trait Foo[M[_]] < type t[A] = M[A] >defined trait Foo scala> val x: Foo[List]#t[Int] = List(1) x: List[Int] = List(1)Тип очистки и манифесты
Как вы знаете, информация о типе теряется во время компиляции благодаря очистке. Одна из особенностей Scala — это Манифесты, которые позволяют выборочно восстановить информацию о типе. Манифесты предоставляются в качестве неявного значения, которое генерируется компилятором по мере необходимости.
scala> class MakeFoo[A](implicit manifest: Manifest[A]) < def make: A = manifest.erasure.newInstance.asInstanceOf[A] >scala> (new MakeFoo[String]).make res10: String =Пример: Finagle
trait Service[-Req, +Rep] extends (Req => Future[Rep]) trait Filter[-ReqIn, +RepOut, +ReqOut, -RepIn] extends ((ReqIn, Service[ReqOut, RepIn]) => Future[RepOut]) < def andThen[Req2, Rep2](next: Filter[ReqOut, RepIn, Req2, Rep2]) = new Filter[ReqIn, RepOut, Req2, Rep2] < def apply(request: ReqIn, service: Service[Req2, Rep2]) = < Filter.this.apply(request, new Service[ReqOut, RepIn] < def apply(request: ReqOut): Future[RepIn] = next(request, service) override def release() = service.release() override def isAvailable = service.isAvailable >) > > def andThen(service: Service[ReqOut, RepIn]) = new Service[ReqIn, RepOut] < private[this] val refcounted = new RefcountedService(service) def apply(request: ReqIn) = Filter.this.apply(request, refcounted) override def release() = refcounted.release() override def isAvailable = refcounted.isAvailable >>Можно определить запросы с помощью filter.
trait RequestWithCredentials extends Request < def credentials: Credentials >class CredentialsFilter(credentialsParser: CredentialsParser) extends Filter[Request, Response, RequestWithCredentials, Response] < def apply(request: Request, service: Service[RequestWithCredentials, Response]): Future[Response] = < val requestWithCredentials = new RequestWrapper with RequestWithCredentials < val underlying = request val credentials = credentialsParser(request) getOrElse NullCredentials >service(requestWithCredentials) > >Обратите внимание, как основной сервис требует определения запроса, и что это проверяется статически. Фильтры можно рассматривать как преобразователи.
Множество фильтров могут быть объединены вместе:
val upFilter = logTransaction andThen handleExceptions andThen extractCredentials andThen homeUser andThen authenticate andThen routeПишите безопасный код!
Built at @twitter by @stevej, @marius, and @lahosken with much help from @evanm, @sprsquish, @kevino, @zuercher, @timtrueman, @wickman, @mccv and @garciparedes; Russian translation by appigram; Chinese simple translation by jasonqu; Korean translation by enshahar;
Неявные (implicit) параметры и преобразования в Scala
Пробежавшись по предыдущим статьям на Хабре, тыц и тыц так и не удалось в быстром режиме понять, что делает неявность (implicit) в Scala. Попробуем разобраться вместе.
Итак, implicit в Scala позволяют избежать вызывания методов или явных ссылок на переменные, и взамен этого позволяют компилятору самому найти нужные неявные данные.
Например, мы могли бы написать функцию для преобразования из Float в Int(FloatToInt) и, вместо того, чтобы вызвать эту функцию явно, компилятор бы сделал это вместо нас неявно:
def double(value: Int) = value * 2 implicit def FloatToInt(value: Float):Int = value.toInt println(double(2.5F))Запутанно? Давайте обо всём по порядку.
Итак, сегодня мы рассмотрим две категории implicit, а именно:
- Неявные параметры (implicit parameters, values). Они являются автоматически переданными компилятором значениями, объявленными через implicit.
- Неявное преобразование (implicit conversion). При несовпадении типа ожидаемого параметра с входящим параметром компилятор ищет любой метод в области видимости, отмеченный implicit и с нужными в данной ситуации ожидаемым параметром и входящим параметром и автоматически его передаёт.
Неявный параметр
Неявный параметр — это параметр функции, которому предшествует ключевое слово implicit. Оно значит, что, если не передано никакого значения при вызове функции, то компилятор собственноручно будет искать неявный параметр и передаст его за нас.
Например, такая функция, принимающая на вход неявный параметр value и удваивающая его:
def double(implicit value: Int) = value * 2
Без неявного параметра упадёт с ошибкой:
def double(implicit value: Int) = value * 2 println(double) // error: could not find implicit value for parameter value
С переданным явно параметром сработает:
def double(implicit value: Int) = value * 2 println(double(3)) // 6
С неявным параметром это будет выглядеть так:
def double(implicit value: Int) = value * 2 implicit val multiplier = 2 println(double) // 4
Но нужно быть аккуратным:
def double(implicit value: Int) = value * 2 implicit val multiplier = 2 implicit val multiplier2 = 1 println(double) // error: ambiguous implicit values
И напоследок пример с передачей в качестве неявного параметра def:
def double(implicit value: Int) = value * 2 val cappucinoLarge: Boolean = false implicit def cappucinoPrice: Int = if (cappucinoLarge) 200 else 100 println(double) // 200
Т.е. в итоге у нас получится
double(cappucinoPrice())
Примечания по синтаксису:
def func1(implicit value1: Int) // value1 неявно def func2(implicit value1: Int, value2: Int) // value1 и value2 неявны def func3(value1: Int, implicit value2: Int) // ошибка компиляции def func4(value1: Int)(implicit value2: Int) // только value2 неявно def func5(implicit value1: Int)(value2: Int) // ошибка компиляции def func6(implicit value1: Int)(implicit value2: Int) // ошибка компиляции
Неявное преобразование
Возвращаясь к примеру из Float в Int:
def double(value: Int) = value * 2 implicit def FloatToInt(value: Float):Int = value.toInt println(double(2.5F))
При вызове double у нас происходит несовпадение типа ожидаемого параметра (Int) с входящим параметром (Float). Поэтому компилятор ищет любой метод в области видимости, отмеченный implicit и с нужными в данной ситуации ожидаемым параметром (Int) и входящим параметром (Float). Находит FloatToInt, производит преобразование и передает дальше нужное значение в double.
Теперь, надеюсь, стало понятнее. Всем успехов в освоении Scala!
- scala
- implicit
- функциональное программирование
- Программирование
- Scala
- Функциональное программирование
Неявные (implicit) параметры и преобразования в Scala
Пробежавшись по предыдущим статьям на Хабре, тыц и тыц так и не удалось в быстром режиме понять, что делает неявность (implicit) в Scala. Попробуем разобраться вместе.

Итак, implicit в Scala позволяют избежать вызывания методов или явных ссылок на переменные, и взамен этого позволяют компилятору самому найти нужные неявные данные.
Например, мы могли бы написать функцию для преобразования из Float в Int(FloatToInt) и, вместо того, чтобы вызвать эту функцию явно, компилятор бы сделал это вместо нас неявно:
def double(value: Int) = value * 2 implicit def FloatToInt(value: Float):Int = value.toInt println(double(2.5F))
Запутанно? Давайте обо всём по порядку.
Итак, сегодня мы рассмотрим две категории implicit, а именно:
- Неявные параметры (implicit parameters, values). Они являются автоматически переданными компилятором значениями, объявленными через implicit.
- Неявное преобразование (implicit conversion). При несовпадении типа ожидаемого параметра с входящим параметром компилятор ищет любой метод в области видимости, отмеченный implicit и с нужными в данной ситуации ожидаемым параметром и входящим параметром и автоматически его передаёт.
Неявный параметр
Неявный параметр — это параметр функции, которому предшествует ключевое слово implicit. Оно значит, что, если не передано никакого значения при вызове функции, то компилятор собственноручно будет искать неявный параметр и передаст его за нас.
Например, такая функция, принимающая на вход неявный параметр value и удваивающая его:
def double(implicit value: Int) = value * 2
Без неявного параметра упадёт с ошибкой:
def double(implicit value: Int) = value * 2 println(double) // error: could not find implicit value for parameter value
С переданным явно параметром сработает:
def double(implicit value: Int) = value * 2 println(double(3)) // 6
С неявным параметром это будет выглядеть так:
def double(implicit value: Int) = value * 2 implicit val multiplier = 2 println(double) // 4
Но нужно быть аккуратным:
def double(implicit value: Int) = value * 2 implicit val multiplier = 2 implicit val multiplier2 = 1 println(double) // error: ambiguous implicit values
И напоследок пример с передачей в качестве неявного параметра def:
def double(implicit value: Int) = value * 2 val cappucinoLarge: Boolean = false implicit def cappucinoPrice: Int = if (cappucinoLarge) 200 else 100 println(double) // 200
Т.е. в итоге у нас получится
double(cappucinoPrice())
Примечания по синтаксису:
def func1(implicit value1: Int) // value1 неявно def func2(implicit value1: Int, value2: Int) // value1 и value2 неявны def func3(value1: Int, implicit value2: Int) // ошибка компиляции def func4(value1: Int)(implicit value2: Int) // только value2 неявно def func5(implicit value1: Int)(value2: Int) // ошибка компиляции def func6(implicit value1: Int)(implicit value2: Int) // ошибка компиляции
Неявное преобразование
Возвращаясь к примеру из Float в Int:
def double(value: Int) = value * 2 implicit def FloatToInt(value: Float):Int = value.toInt println(double(2.5F))
При вызове double у нас происходит несовпадение типа ожидаемого параметра (Int) с входящим параметром (Float). Поэтому компилятор ищет любой метод в области видимости, отмеченный implicit и с нужными в данной ситуации ожидаемым параметром (Int) и входящим параметром (Float). Находит FloatToInt, производит преобразование и передает дальше нужное значение в double.
Теперь, надеюсь, стало понятнее. Всем успехов в освоении Scala!
Понимаем implicit'ы в Scala

В последнее время у меня было несколько разговоров с друзьями из Java мира об их опыте использования Scala. Большинство использовали Scala, как улучшенную Java и, в итоге, были разочарованы. Основная критика была направлена но то, что Scala слишком мощный язык с высоким уровнем свободы, где одно и тоже можно реализовать различными способами. Ну и вишенкой на торте недовольства являются, конечно же, implicit'ы. Я соглашусь, что implicit'ы одна из самых спорных фич языка, особенно для новичков. Само название «неявные», как бы намекает. В неопытных руках implicit'ы могут стать причиной плохого дизайна приложения и множества ошибок. Я думаю каждый, работающий со Scala, хотя бы раз сталкивался с ошибками разрешения ипмлиситных зависимостей и первые мысли были что делать? куда смотреть? как решить проблему? В результате приходилось гуглить или даже читать документацию к библиотеке, если она есть, конечно же. Обычно решение находится импортом необходимых зависимостей и проблема забывается до следующего раза.
В этом посте я бы хотел рассказать о некоторых распространенных практиках использования имплиситов и помочь их сделать более «явными» и понятными. Наиболее распространенные варианты их использования:
- Неявные параметры (implicit parameters)
- Неявные преобразования (implicit conversions)
- Неявные классы (implicit classes — «Pimp My Library» паттерн)
- Тайп-классы (type classes)
import com.typesafe.config.ConfigFactory val conf = ConfigFactory.load(); val foo = config.getString("simple-lib.foo") val bar = config.getInt("simple-lib.bar")
Я вижу здесь, как минимум, две проблемы:
- Обработка ошибок. Например, если метод getInt не сможет вернуть значение нужного типа, то будет брошено исключение. А мы хотим писать «чистый» код, без исключений.
- Расширяемость. Этот API поддерживает некоторые Java типы, но что, если мы захотим расширить поддержку типов?
implicit class RichConfig(val config: Config) extends AnyVal
Теперь мы можем использовать метод getLocalDate , как если бы он был определен в исходном классе. Неплохо. Но мы решили проблему только локально и мы должны поддерживать всю новую функциональность в одном RichConfig классе или потенциально иметь ошибку «Ambiguous implicit values», если одинаковые методы будут определены в разных неявных классах.
Можно ли как-то это улучшить? Здесь давайте вспомним, что обычно в Java, наследование используется для реализации полиморфизма. На самом деле, полиморфизм бывает разных видов:
- Ad hoc полиморфизм.
- Параметрический полиморфизм.
- Полиморфизм подтипов.
Давайте рассмотрим на примере. Для нашего случая, определим контракт для чтения значения из конфига:
trait Reader[A]
Как мы видим, трейт Reader параметризирован типом A . Для решения первой проблемы мы возвращаем Either . Больше никаких исключений. Для упрощения кода можем написать тайп алиас.
trait Reader[A] < def read(config: Config, path: String): Reader.Result[A] >object Reader < type Result[A] = Either[Throwable, A] def apply[A](read: (Config, String) =>A): Reader[A] = new Reader[A] < def read[A](config: Config, path: String): Result[A] = Try(read(config, path)).toEither >implicit val intReader = Reader[Int]((config: Config, path: String) => config.getInt(path)) implicit val stringReader = Reader[String]((config: Config, path: String) => config.getString(path)) implicit val localDateReader = Reader[LocalDate]((config: Config, path: String) => LocalDate.parse(config.getString(path), DateTimeFormatter.ISO_DATE);) >
Мы определили тайп класс Reader и добавили несколько реализаций для типов Int , String , LocalDate . Теперь нужно научить Config работать с нашим тайп классом. И здесь уже пригодится «Pimp My Library» паттерн и неявные аргументы:
implicit class ConfigSyntax(config: Config) extends AnyVal
Мы можем переписать более кратко при помощи ограничения контекста(context bounds):
implicit class ConfigSyntax(config: Config) extends AnyVal
И теперь, пример использования:
val foo = config.as[String]("simple-lib.foo") val bar = config.as[Int]("simple-lib.bar")
Тайп классы — очень мощный механизм, который позволяет писать легко расширяемый код. Если требуется поддержка новых типов, то можно просто написать реализацию нужного тайп класса и поместить её в контекст. Также, используя приоритет в разрешении неявных зависимостей, можно переопределять стандартную реализацию. Например, можно определить другой вариант LocalDate ридера:
implicit val localDateReader2 = Reader[LocalDate]((config: Config, path: String) => Instant .ofEpochMilli(config.getLong(path)) .atZone(ZoneId.systemDefault()) .toLocalDate() )
Как мы видим, implicit'ы, при правильном использовании, позволяют писать чистый и расширяемый код. Они позволяют расширить функциональность сторонних библиотек, без изменения исходного кода. Позволяют писать обобщённый код и использовать ad hoc полиморфизм при помощи тайп классов. Нет необходимости беспокоиться о сложной иерархии классов, можно просто разделить функциональность на части и реализовывать их отдельно. Принцип разделяй и властвуй в действии.
Github проект с примерами.
- Scala
- Функциональное программирование