В этом уроке вы узнаете:

Видимое ограничение (“Классы-типы”)

Неявные функции в 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)
  }
}

Ограничения контекста и 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 {
  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 = 

Пример: Finagle

Смотрите: 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

Пишите безопасный код!