Scala 类型系统二

2017/08/16 Scala

Scala类型系统二

结构化类型

结构化类型是一种类型安全的鸭子类型(dock typing)。鸭子类型是动态类型语言的一种解析方式。Scala不支持这种运行时的方法解析,Scala有一个类似的机制,允许你指定对象必须符合某种特定的结构:必须包含特定成员,介不要求指定封装了这些成员的类型具有什么名称。被称为指名类型(naming typing),对应的类型具有名称,在结构化类型中只考虑该类型的结构,所以它可以是匿名的。如将观察者模式处理为结构化类型。

trait Subject{
    import Scala.language.reflectiveCalls //导入反射是因为Scala必须确认这个对象有receiveUpdate函数存在,在添加observer的时候会有一定的性能开销
    type State
    type Observer ={ def receiveUpdate(state:Any):Unit } //这个声明的含义是任何有这种函数声明的对象都可以作为一个观察者,这是结构化类型的体现。
    private var observers:List[Observer]:Nil
    def addObserver(observer:Observer):Unit=
        observers::=observer
    
    def notifyObservers(state:State):Unit=
        observers foreach (_.receiveUpdate(state))
}

这个实现可以更近一步

trait SubjectFunc{

    type State

    type Observer = State => Unit

    private var observers:List[Observer]=Nil

    def addObserver(observer:Observer):Unit=
        observers::=observer
    
    def notifyObservers(state:State):Unit=
        observers foreach (o => o(state))

}

最终版本的实现进一步解除了耦合,同时也消除了结构化类型,但是这不意味着结构化类型是没用的,一般情况下一个结构类型可能有好几个成员,不能简单的用匿名函数替换。

复合类型

声明一个组合了若干种类型的实例时,就得到了复合类型(compound type):

trait T1
trait T2
class C
val c=new C with T1 with T2     //c被认为所有三个类型的子类型,也可以直接用继承
class D extends C with T2 with T1{

}

类型细化

如果想解决对特定类型数组的排序,可以使用Java的类库这样处理:

List<C> listOfC=...
Java.util.Collections.sort(listofC,new Comparator<C>(){
    public int compare(C c1,C c2){...}
})

也可以创建新的类型处理,在Scala中,创建实例的时候混入特征会创建新的细化的类型,JVM会在字节码中为它合成一个名字,需要使用反射机制来从外部访问细化后的特性或成员。

存在类型

(existential type)是对类型做抽象的方法,可以在不知道具体类型的存在的时候断言类型存在。 在以下三种场景中,存在类型对 Scala 与 Java 的类型兼容来说尤为重要。

  1. 泛型的类型参数被 JVM 字节码“抹去”了(称为类型擦除 )。例如:创建 List[Int] 时,我们在字节码中看不到 Int 这个类型。所以,在运行时无法根据已知的类型信息区分 List[Int] 和 List[String] 。
  2. 你可能接触过“裸”类型,如在 Java 5 之前,库中的集合类型没有类型参数(所有的参数都是 Object 类型)。
  3. 当 Java 在泛型中使用通配符来表示多样行为时,实际的类型是未知的。 如:
    Object Doubler{ 
     def double(seq:Seq[String]):Seq[Int] = double(seq map (_.toInt))
     def double(seq:Seq[Int]):Seq[Int]=seq map (_*2)
    }
    

    这段代码会得到一个编译错误,因为类型擦除后两个方法没有区别,下面给出一个可用版本:

    object Doubler {
     def double(seq: Seq[_]): Seq[Int] = seq match {
         case Nil => Nil
         case head +: tail => (toInt(head) * 2) +: double(tail) 
     }
        
     private def toInt(x: Any): Int = x match {
         case i: Int => i
         case s: String => s.toInt
         case x => throw new RuntimeException(s"Unexpected list element $x")
     } 
    }
    

    尽管上面的代码有些繁琐,但是是可以运行的,在这种上下文中,表达式Seq[_]是存在类型Seq[T] forSome { type T }的简写,表达列表的参数可以是任意类型。 | 简写 | 完整形式 | 描述 | | —– | —– | —–| | Seq[_] | Seq[T] forSome {type T} |T 可以是 Any 的任意子类 | | Seq[_ <: A] | Seq[T] forSome {type T} |T 可以是 A (在某处已经定义了)的任意子类 | | Seq[_ >: Z <: A] | Seq[T] forSome {type T >: Z <: A} |T 可以是 A 的子类且是 Z 的超类 |

Scala中 util.List[_ <: A]的表达式结构和Java中Java.util.List<? extends A>很像,它们是同一个声明,Scala的变异行为在声明是已经定义,但是可以用存在类型定义出调用的时才确定的变异行为,只有很少这样做。 在Scala中,存在类型是为了支持Java的范形,同时保证范形在Scala类型系统中的正确性。在多数情况类型推断已经处理了足够多的细节 。

路径相关类型

一句话,Scala允许使用路径表达式对类型进行访问。可以使用C.this,C.super,C.super[T],但是不可以把super串行化。并且类型表达式中除了最后一个元素之外,所以的其他的元素都要保持固定。即这些元素必须是包,单例对象或具有相同别名的类型声明。

单例类型

使用Object可以声明单例对象,AnyRef子类的所有实例都有一个单例类型,使用T.type可以获得T的单例类型,在对象表达式中使用该表达式,可以将允许的实例数量限定到一个。

值类型

值类型包括:参数类型、单例类型、类型映射、类型标识符、复合类型、既存类型、元组类型、函数类型以及中缀类型。除了常规的写法之 外,Scala 还为最后三种类型提供了更为方便的简写语法。

元组类型

Scala允许通过(A,B,C)表达式声明Tuple3[A,B,C]对象,这一类型称为元组类型(tuple type),一般都会省略TupleN语法。

函数类型

可以用箭头表达式编写类型为函数类型的能对象,如Function2类型:

val f1:Function2[Int,Double,String] = (i,d) => s"int $i,double $d"
val f2:(Int,Double) => String = (i,d) => s"int $i,double $d"

很少使用FunctionN来创建类型,因为没有必要。

中缀类型

可以使用中缀表达式编写包含两个类型参数的类型。如下:

val left1:Either[String,Int] = Left("hello")
val left2:String Either Int = Left("hello")
val right1:Either[String,Int] = Right(1)
val right2:String Either Int = Right(2)

可以嵌套使用多个中缀类型,除了类型名称以冒号结尾的情况,中缀表达式是左结合的。假如类型名称以冒号结尾,该类型则为右结合。这与for表达式的用法相同。可以使用小括号改写默认的类型结合顺序。

val x111:Int Either Double Either String = Left(Left(1))
val x112:(Int Either Double) Either String = Left(Left(1))

val x1r1:Int Either Double Either String = Left(Right(3.14))
val x1r2:(Int Either Double) Either String = Left(Right(3.14))

val xr1:Int Either Double Either String = Right("foo")
val xr2:(Int Either Double) Either String = Right("foo")

val x1: Int Either (Double Either String) =Left(1)
val xr1:Int Either (Double Either String) =Right(Left(3.14))

val xrr:Int Either (Double Either String) =Right(Right("bar"))

Higher-Kinded类型

操作Seq实例时,通常编写下面的代码:

def sum(seq:Seq[Int]):Int =seq reduce (_+_)
sum(Vector(1,2,3,4,5))

Scala支持Higher-Kinded 类型,通过应用该类型,我们可以对参数化进行抽象。如:

import scala.language.higherKinds
trait Reduce[T,-M[T]]{
    def reduce(m:M[T])(f:(T,T)=>T):T
}
object Reduce{
    implicit def seqReduce[T]=new Reduce[T,Seq]{
        def reduce(seq:Seq[T])(f:(T,T)=>T):T=seq reduce f
    }

    implicit def optionReduce[T] = new Reduce[T,Option]{
        def reduce(opt:Option[T])(f:(T,T)=>T):T=opt reduce f 
    }
}

把reduce操作封装成higher-kinded类型,一些类库中higher-kinded命名为M是不成文的规定。higher-kinded 类型给我们带来一些额外的抽象,并且使代码变得更加复杂,为了能够以一种非常简洁和强大的方式将 代码组合起来,Scalaz(https://github.com/scalaz/scalaz )和 Shapeless(https://github.com/milessabin/shapeless )这样的类库大量使用了 higher-kinded 类型 带来的额外抽象和复杂代码。

类型lambda

类型 Lambda 指的是嵌入在另一函数中的函数,它只作用于类型。假如我们需要使用的参数化类型中包含了太多的类型参数,便可以使用类型Lambda 进行处理。类型 Lambda 是一个编程术语,它并不是类型系统的特殊功能。

trait Functor[A,+M[_]]{
    def map2[B]:(f:A=>B):M[B]//Functor常用于对包含了 map 操作的类型进行命名
}
object Functor{
    implicit class SeqFunctor[A](seq:Seq[A]) extends Functor[A,Seq]{
        def map2[B](f:A=>B):Seq[B]=seq map f 
    }
    implicit class OptionFunctor[A](opt:Option[A]) extends Functor[A,Option]{
        def map2[B](f: A => B): Option[B] = opt map f
    }
    implicit class MapFunctor[K,V1](mapKV1:Map[K,V1]) extends Functor[V1,({type λ[α]=Map[K,α]})#λ]{
        def map2[V2](f:V1=>V2):Map[K,V2]=mapKV1 map{
            case (k,v) => (k,f(v))
        }
    }
}

无需经常使用类型 Lambda 的语法,不过它有助于解决描述的问题。Scala 的未来版本也许会为类型 Lambda 习语提供更简洁的语法。这个版本的语法实在是太复杂了,我认为这个地方工作中的实际应用会非常少。

处递归类型:F-Bounded多态

自递归类型也被称为 F-bounded 多态类型 ,用于表示指向自身的类型。Java 的 Enum(http://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html )抽象类便是经典的一例,也是所有 Java 枚举的基础类型,声明如下:

public abstract class Enum<E extends Euum<E>>
extends Object
implements Comparable<E>,Serializable

大多数Java开发者都会对Enum<E extends Enum>这样的语法感到困扰,不过该语法却能带来一些重要的好处,其中一个就是如果传入 compareTo 方法的对象并不是相同类型的某一枚举值,便会出现编译错误。 在 Scala 中,使用递归类型能够方便我们定义返回类型与调用者类型相同的方法,即便是返回类型与调用者类型具有继承关系,递归类型也能起作用。

trait Parent[T<:Parent[T]]{
    def make:T
}
case class Child1(s:String) extends Parent[Child1]{
    def maek:Child1=Child1(s"Child1:Make:$s")
}
case class Child2(s:String) extends Parent[Child2]{
    def make:Child2 =Child2(s"Child2:Make:$s")
}

val c1=Child1("C1") //c1: Child1 = Child1(c1)
val c2=Child2("C2") //c2: Child2 = Child2(c2)    
val c11=c1.make     //c11: Child1 = Child1(Child1: make: c1)
val c12=c2.make     //c22: Child2 = Child2(Child2: make: c2)

val p1: Parent[Child1] = c1 //p1: Parent[Child1] = Child1(c1)
val p2: Parent[Child2] = c2 //p2: Parent[Child2] = Child2(c2)
val p11 = p1.make           //p11: Child1 = Child1(Child1: make: c1)
val p22 = p2.make           //p22: Child2 = Child2(Child2: make: c2)
Show Disqus Comments

Search

    Table of Contents