Scala教程:高级类型

这个章节的内容包含:

  • 视图边界(“类型类”)
  • 其他类型边界
  • 高度类型化的类型&临时多态
  • F-bounded多态/可递归类型
  • 结构化的类型
  • 抽象类型的成员
  • 类型擦除和Manifest
  • 实例学习:Finagle

视图边界(“类型类”)

有时候你并不需要指定一个类型等价于另外一个类型,或者是它的子类或者父类,如果那样做的话,你可能会和类型转换搞混淆。视图边界定义了可以“看作”是另一个类型的一种类型。这个对于需要“读取”一个对象,但是不需要修改它的场景是非常实用的。

Implicit函数允许自动进行类型转换。更加确切地说,这些函数允许按需的函数应用,这将有助于类型推导,例如:

1
2
3
4
5
6
7
8
9
10
11
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

 

视图边界,和类型边界相似,也需要一个对于指定类型存在的函数。你可以用一个%来表示一个类型边界,例如:

1
2
scala> class Container[A <% Int] { def addIt(x: A) = 123 + x }
defined class Container

这个表示类型A可以被“看作”是类型“Int”。让我们来试试。

1
2
3
4
5
6
7
8
9
10
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 )
         ^

其他类型边界

函数可以通过implicit参数来使用更加复杂的类型边界。例如,List对于数字内容支持sum函数,但是对于其他的则不行。悲剧的是,Scala的数字类型并不都共享同一个父类,因此我们不能使用T <: Number来实现。为了达到这样的效果,Scala的math库,为合适的类型定义了一个implicitNumeric[T]。然后再在List的定义中使用它:

1
sum[B >: A](implicit num: Numeric[B]): B

如果你调用List(1,2).sum(),你无需传入num参数,它会被隐式地进行设置。但是如果你通过List("whoop").sum()的方式来调用的话,会无法完成参数的设置。

方法也可能会需要一些特定的“证据”来表明哪些类型可以进行设置,从而避免把奇怪的对象给设置成Numeric。并且,在这里你还可以使用之前介绍的类型关系操作符:

A =:= B A必须等于B
A <:< B A必须是B的子类
A <%< B A必须看作是B
1
2
3
4
5
6
7
8
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]

同样的,对于前面的implicit,我们可以把限制放宽,可以进行对应的视图转换即可:

1
2
3
4
5
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[]里),使用到了这个技术:

1
2
3
4
5
6
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进行静态类型检测
  • 你可以直接定义你自己的排序,而不需要额外的类库支持
1
2
3
4
5
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视图的方法。(反向转换也可以)

1
2
3
4
5
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 引入了一个使用和访问implicit参数的快捷方法。

1
2
3
4
5
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

Implicit的值可以通过implicitly来进行访问。

1
2
scala> implicitly[Ordering[Int]]
res37: Ordering[Int] = scala.math.Ordering$Int$ @3a9291cf

把这些给组合起来,可以使得代码变得更加简洁,特别是在处理视图的时候。

 

高度类型化的类型&临时多态

Scala可以抽象出“高度类型化”的类型。例如,假设你需要多个类型的container来处理多个类型的数据。你可能会定义一个Container接口,然后它会被多个container类型实现:一个Option,一个List,等等。你想要定义一个Container接口,并且你需要使用其中的值,但是你不想要确定值的实际类型。

这个和currying函数的场景非常相似。例如,鉴于“一元的类型”有着类似List[A]的构造器,这就意味着我们需要满足一“级”类型变量的条件,这样才能够产生具体的类型(就像一个非currying的函数只能有一个参数列表,它才能够被调用),一个高度类型化的类型需要更多的信息。

1
2
3
4
5
6
7
8
9
10
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 )

如果我们结合implicit和container接口,我们就能够得到“即时”多态(”ad-hoc” polymorphism):这是一种可以在container上编写泛型函数的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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-bounded多态

很多时候,我们需要在一个(泛型的)traint里访问一个具体的子类。例如,假设你有一些泛型的trait,但是需要和trait的一个特定的子类进行比较。

1
trait Container extends Ordered[Container]

现在,在这里需要一个compare方法。

1
def compare(that: Container): Int

这样的话,我们就不能访问具体的子类型了,例如:

1
2
3
class MyContainer extends Container {
   def compare(that: MyContainer): Int
}

这段代码会编译失败,因为我们给Container指定的是Ordered,而不是具体的子类型。

我们可以使用F-bounded多态来修复它。

1
trait Container[A <: Container[A]] extends Ordered[A]

很奇怪的类型!但是请注意Ordered在A上是如何指定类型的,A本身也是一个Container[A]

现在

1
2
3
class MyContainer extends Container[MyContainer] {
   def compare(that: MyContainer) = 0
}

现在它们都是有序的:

1
2
3
4
5
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[_]的混合列表。

1
2
3
4
5
6
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进行限制的。这是类型推导器的工作。有趣的是–这个类型并不需要有实际的意义,它只是为List的所有类型提供了一个逻辑上的最小边界。那么,如果我们使用Ordered会怎么样呢?

1
2
3
( new MyContainer, new MyContainer, new MyContainer, new YourContainer).min
: 9 : error: could not find implicit value for parameter cmp:
   Ordering[Container[_ >: YourContainer with MyContainer <: Container[_ >: YourContainer with MyContainer <: ScalaObject]]]

对于这个统一的类型没有Ordered[] 存在。这个太不给力了。

 

结构化的类型

Scala 支持结构化的类型 – 对于这个类型的需求一般用接口结构(iterface structure)来表示,而非使用具体的某个类型。

1
2
3
4
5
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

这个特性在很多场景都特别有用,但是具体的实现用的是反射,所以需要注意性能问题。

 

抽象类型的成员

在一个trait里,你可以使用抽象类型的成员。

1
2
3
4
5
6
7
8
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

 

在处理依赖注入等场景时,这是一个很有用的手段。

你可以通过hash操作来引用一个抽象的类型变量:

1
2
3
4
5
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 )

 

类型擦除和Manifest

我们都知道,由于擦除的原因,类型信息在编译期都丢失了。Scala提供了Manifests,它可以让我们有选择地进行类型恢复。Manifest是一个implicit值,它是由编译器按需生成的。

1
2
3
4
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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来验证请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
   }
}

 

注意底层的服务对于请求验证的实现,它是静态地实现的。Filter也可以被认为是服务的转换器。

 

现在,多个filter可以组合一起使用。

1
2
3
4
5
6
7
val upFilter =
   logTransaction     andThen
   handleExceptions   andThen
   extractCredentials andThen
   homeUser           andThen
   authenticate       andThen
   route

安全地使用类型吧!

你可能感兴趣的:(Scala)