В этом уроке вы узнаете:
Неявные функции в 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 <% Int] { def addIt(x: A) = 123 + x } 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) <console>: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 <:< B | A должен быть подтипом B |
A <%< B | 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 <console>: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 <%< Int) = 123 + value } 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) }
Основными преимуществами этого являются:
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 <: Ordered[A]]: Ordering[A] = new Ordering[A] { def compare(x: A, y: A) = x.compare(y) } }
В 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))
Часто необходим доступ к конкретному подклассу в (обобщенном) трейте. Например, представьте себе некоторый трейт, который является обобщенным, но может быть сравним с конкретным подклассом данного трейта.
trait Container extends Ordered[Container]
Тем не менее, сейчас требуется сравнение
def compare(that: Container): Int
И поэтому мы не можем получить доступ к конкретному подтипу, например:
class MyContainer extends Container { def compare(that: MyContainer): Int }
код не скомпилируется, так как мы определяем Ordered для Container, а не конкретный подтип.
Чтобы это согласовать, мы используем F-ограниченный полиморфизм.
trait Container[A <: Container[A]] extends Ordered[A]
Странный тип! Но заметьте, как Ordered параметризован с помощью A, который сам по себе является Container[A]
Поэтому сейчас
class MyContainer extends Container[MyContainer] { def compare(that: MyContainer) = 0 }
Они сейчас упорядочены:
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 <: Container[_ >: YourContainer with MyContainer <: ScalaObject]]] = List(MyContainer@3be5d207, MyContainer@6d3fe849, MyContainer@7eab48a7, YourContainer@1f2f0ce9)
Обратите внимание, как результирующий тип в настоящее время ограничен снизу YourContainer с MyContainer. Это работа системы вывода типов. Интересно, что этот тип не имеет дополнительного смысла, он только обеспечивает логическую нижнюю границу для списка. Что произойдет, если мы попытаемся использовать Ordered сейчас?
(new MyContainer, new MyContainer, new MyContainer, new YourContainer).min <console>:9: error: could not find implicit value for parameter cmp: Ordering[Container[_ >: YourContainer with MyContainer <: Container[_ >: YourContainer with MyContainer <: ScalaObject]]]
Ordered[] не существует для единого типа. Это слишком плохо.
Scala имеет поддержку структурных типов — тип выражается интерфейсом structure вместо конкретного типа.
scala> def foo(x: { def get: Int }) = 123 + x.get foo: (x: AnyRef{def get: Int})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 =
Смотрите: https://github.com/twitter/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
Пишите безопасный код!