web/ru/advanced-types.textile (270 lines of code) (raw):

--- prev: type-basics.textile next: sbt.textile title: Дополнительные типы layout: post --- В этом уроке вы узнаете: * Видимое ограничение («Классы-типы») * Типы высшего порядка и специальный полиморфизм * F-ограниченный полиморфизм / рекурсивные типы * Структурные типы * Абстрактные типы членов * Тип чисток и манифесты * Пример: Finagle h3. Видимое ограничение («Классы-типы») *Неявные* функции в Scala позволяют использовать функции по требованию, когда это может помочь при выводе типа, например: <pre> 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 </pre> Видимое ограничение, подобно ограничению типа, требует функцию, которая существует для данного типа, например: <pre> scala> class Container[A <% Int] { def addIt(x: A) = 123 + x } defined class Container </pre> Это говорит, что *A* должен быть «видим» подобно *Int*. Давайте попробуем. <pre> 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) ^ </pre> h3. Другие ограничения типов Методы могут запросить конкретные «доказательства» для типа, а именно: |A =:= B|A должен быть равен B| |A <:< B|A должен быть подтипом B| |A <%< B|A должен выглядеть как B| <pre> 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] </pre> Кроме того, учитывая наше предыдущее неявное значение, мы можем ослабить ограничение для видимости: <pre> 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 </pre> h4. Обобщенное программирование с помощью видов В стандартной библиотеке Scala, виды в основном используются для реализации обобщенных функций коллекций. Например, функция «min» (*Seq[]*), использует эту технику: <pre> 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) } </pre> Основными преимуществами этого являются: * Элементам коллекции не требуется реализовывать *Ordered*, хотя *Ordered* по-прежнему использует статическую проверку типов. * Вы можете определить свой собственный порядок сортировки без необходимости использовать дополнительную библиотеку: <pre> 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 </pre> Небольшое замечание, есть виды в стандартной библиотеке, которые переводят *Ordered* в *Ordering* (и наоборот). <pre> trait LowPriorityOrderingImplicits { implicit def ordered[A <: Ordered[A]]: Ordering[A] = new Ordering[A] { def compare(x: A, y: A) = x.compare(y) } } </pre> h4. Ограничения контекста и implicitly[] В Scala 2.8 введена сокращенная форма для передачи и для доступа с использованием неявных аргументов. <pre> 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 </pre> Неявные значения могут быть доступны через *implicitly* <pre> scala> implicitly[Ordering[Int]] res37: Ordering[Int] = scala.math.Ordering$Int$@3a9291cf </pre> В совокупности это часто приводит к меньшему количеству кода, особенно при передаче с использованием видов. h3. Типы высшего порядка и специальный полиморфизм Scala может абстрагировать типы «высшего порядка». Это похоже на каррирование функции. Например, в то время как «унарные типы» имеют конструкторы вроде этого: <pre> List[A] </pre> То есть мы должны удовлетворять определенному «уровню» типовых переменных с целью получения конкретных типов (подобно тому, как uncurried функция должна применяться только к одному списку аргументов, при вызове), типам высшего порядка требуется больше: <pre> 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) </pre> Заметьте, что *Container* является полиморфным в параметрическом типе («тип контейнер»). Если мы объединим использование контейнеров с неявными выражениями, мы получим «специальный» полиморфизм: возможность писать обобщенные контейнеры поверх контейнеров. <pre> 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)) </pre> h3. F-ограниченный полиморфизм Часто необходим доступ к конкретному подклассу в (обобщенном) трейте. Например, представьте себе некоторый трейт, который является обобщенным, но может быть сравним с конкретным подклассом данного трейта. <pre> trait Container extends Ordered[Container] </pre> Тем не менее, сейчас требуется сравнение <pre> def compare(that: Container): Int </pre> И поэтому мы не можем получить доступ к конкретному подтипу, например: <pre> class MyContainer extends Container { def compare(that: MyContainer): Int } </pre> код не скомпилируется, так как мы определяем Ordered для *Container*, а не конкретный подтип. Чтобы это согласовать, мы используем F-ограниченный полиморфизм. <pre> trait Container[A <: Container[A]] extends Ordered[A] </pre> Странный тип! Но заметьте, как Ordered параметризован с помощью *A*, который сам по себе является *Container[A]* Поэтому сейчас <pre> class MyContainer extends Container[MyContainer] { def compare(that: MyContainer) = 0 } </pre> Они сейчас упорядочены: <pre> 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 </pre> Учитывая, что все они являются подтипами *Container[_]*, мы можем определить другой подкласс и создать смешанный список *Container[_]*: <pre> 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) </pre> Обратите внимание, как результирующий тип в настоящее время ограничен снизу *YourContainer с MyContainer*. Это работа системы вывода типов. Интересно, что этот тип не имеет дополнительного смысла, он только обеспечивает логическую нижнюю границу для списка. Что произойдет, если мы попытаемся использовать *Ordered* сейчас? <pre> (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]]] </pre> *Ordered[]* не существует для единого типа. Это слишком плохо. h3. Структурные типы Scala имеет поддержку *структурных типов* — тип выражается интерфейсом _structure_ вместо конкретного типа. <pre> 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 </pre> Это может быть полезно во многих ситуациях, но реализация использует отражения, так что обращайте внимание на производительность. h3. Абстрактные типы членов В трейте, вы можете оставить тип членов абстрактным. <pre> 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 </pre> Часто это полезный трюк, когда делается внедрение зависимостей, например. Вы можете обратиться к абстрактному типу переменной, используя хеш-оператор: <pre> 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) </pre> h3. Тип очистки и манифесты Как вы знаете, информация о типе теряется во время компиляции благодаря _очистке_. Одна из особенностей Scala — это *Манифесты*, которые позволяют выборочно восстановить информацию о типе. Манифесты предоставляются в качестве неявного значения, которое генерируется компилятором по мере необходимости. <pre> scala> class MakeFoo[A](implicit manifest: Manifest[A]) { def make: A = manifest.erasure.newInstance.asInstanceOf[A] } scala> (new MakeFoo[String]).make res10: String = </pre> h3. Пример: Finagle Смотрите: https://github.com/twitter/finagle <pre> 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 } } </pre> Можно определить запросы с помощью filter. <pre> 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) } } </pre> Обратите внимание, как основной сервис требует определения запроса, и что это проверяется статически. Фильтры можно рассматривать как преобразователи. Множество фильтров могут быть объединены вместе: <pre> val upFilter = logTransaction andThen handleExceptions andThen extractCredentials andThen homeUser andThen authenticate andThen route </pre> Пишите безопасный код!