简洁的scala(oop)

  • 如果构造函数没有参数,不需要new后加的括号–可以用scala new ClassName格式取代scala new ClassName()
  • 用对应的object class(伴生对象)可以不加new来实例化对象.默认调用了className.apply方法.
public class Car{
    private final int year;
    private int miles;
    
    public int getYear(){return year;}
    public getMiles(){return miles;}
    
    public void drive(int distance){
        miles += Math.abs(distance);
    }
}

等同于以下scala代码

class Car(val year: Int){
    private var milesDriven: Int = 0
    
    def miles() = mileDriven
    
    def drive(distance: Int){
        mileDriven += Math.abs(distance)
    }
    
}

to()和until()都是RichInt的方法,这个类型是由Int隐式转换而来的.

this别名&自身类型

看scala的源码的话很发现很多源码开头都有一句:self => 这句相当于给this起了一个别名为self

class A { 
    self =>  //this别名
    val x=2 
    def foo = self.x + this.x 
}

self不是关键字,可以用除了this外的任何名字命名(除关键字)。就上面的代码,在A内部,可以用this指代当前对象,也可以用self指代,两者是等价的。

它的一个场景是用在有内部类的情况下:

class Outer { outer => 
    val v1 = "here"
    class Inner {
        println(outer.v1) // 用outer表示外部类,相当于Outer.this
    }
}

对于this别名 self =>这种写法形式,是自身类型(self type)的一种特殊方式。

self在不声明类型的情况下,只是this的别名,所以不允许用this做this的别名

scala> class C { this => } //error 不能用this做别名

但当声明了类型的时候,就不是别名的用途了,这个时候表示自身类型,比如:

scala> class C { this:X => }

this:X => 要求C在实例化时或定义C的子类时,必须混入指定的X类型,这个X类型也可以指定为当前类型

scala> class C { this:C => } // 不会报错

自身类型的存在相当于让当前类变得“抽象”了,它假设当前对象(this)也符合指定的类型,因为自身类型 this:X =>的存在,当前类构造实例时需要同时满足X类型

scala> new C // 不满足
<console>:10: error: class C cannot be instantiated because it does not conform to its self-type C with X

// ok, 相当于构造一个复合类型(C with X)的实例
scala> val c = new C with X

在定义C的子类时,因为自身类型的约束,也必须满足X类型,即子类必须也混入X

scala> class D extends C with X

注意上面两种情况下X都为特质(trait)。

如果自身类型是定义在特质中(大多情况下):

scala> trait T { this:X => } 

那么当某个class或object 要继承或混入 T 时,必须也要满足 X 类型,如果该类/单例不是X的子类的话就要同时混入X才可以

scala> object A extends T with X 

最后,自身类型也可以声明为复合类型

this: X with Y with Z =>  

或声明为结构类型

this: { def close:Unit} => 

另外,自身类型中,可以用this也可以用其他名字,如self。

scala中的泛开和泛型函数

sealed abstract class List[+A] extends AbstractSeq[A] with

[+A] 如果list是String类型,而另个List是Any类型,则表示,前一个 是后一个 的子类

class Triple[F,S,T](val first:F, val second:S, val third:T)

val triple = new Triple("spark",3,3.14.5)
val bigData= new Triple[String,String,Char]("ab","cd",'R')

def getData[T](List : List[T]) = list(list.length / 2)
println(getData(List("Spark","Hadoop",'R')))

val f = getData[Int] _
println(f(List(1,2,3,4,5)))

def buildArray[T:ClassTag](len:Int)=new Array[T](len)
println(buildArray[Int](5).toList)

后边的ClassTag,编译时不确定T是什么,在真正运行时上下文的反射类。通过这个类,会帮助我们在运行
时会添加多功能

abstract class RDD[T: ClassTag]{

}

http://www.scala-lang.org/api/2.12.2/scala/reflect/ClassTag.html

scala.reflect
ClassTag
Companion object ClassTag

trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serializable

A ClassTag[T] stores the erased class of a given type T, accessible via the runtimeClass field. This is particularly useful for instantiating Arrays whose element types are unknown at compile time.

ClassTags are a weaker special case of scala.reflect.api.TypeTags#TypeTags, in that they wrap only the runtime class of a given type, whereas a TypeTag contains all static type information. That is, ClassTags are constructed from knowing only the top-level class of a type, without necessarily knowing all of its argument types. This runtime information is enough for runtime Array creation.

For example:

scala> def mkArray[T : ClassTag](elems: T*) = Array[T](elems: _*)
mkArray: [T](elems: T*)(implicit evidence$1: scala.reflect.ClassTag[T])Array[T]

scala> mkArray(42, 13)
res0: Array[Int] = Array(42, 13)

scala> mkArray("Japan","Brazil","Germany")
res1: Array[String] = Array(Japan, Brazil, Germany)

See scala.reflect.api.TypeTags for more examples, or the Reflection Guide: TypeTags for more details.

header 1header 2
Annotations@implicitNotFound( msg = “No ClassTag available for ${T}” )
SourceClassTag.scala
def foo[A, B](f: A => List[A], b: A) = f(b)

f: A = > List[A]

f是A类型,输出结果是List[A],所以b一定要为A类型

scala中泛型在Spark中应用

abstract class RDD[T : ClassTg](
    @transient private var _sc: SparkContext,
    @transient private var deps: Seq[Dependency[_]
) extends Serializable with Logging

def treeReduce(f: (T, T) => T, depth: Int = 2): T = withScope{
    require(depth >=1, s"Depth must be greater than or equal to 1 but got $depth.")
    val cleanF = context.clean(f)
    val reducePartition: Iterator[T] => Option[T] = iter =>{
        if(iter.hasNext){
            Some(iter.reduceLeft(cleanF))
        }else{
            None
        }
    }
}

def textFile(
    path: String,
    minPartitions: Int = defaultMinPartitions): RDD[String] = withScope{
        assertNotStopped()
        hadoopFile(path, classOf[TextInputFormat], classOf[LongWriteable], classOf[Text],minPartitions).map(par => pair => pair._2.toString)
        
        def map[U: ClassTag](f: T => U): RDD[U] = withScope{
            val cleanF = sc.clean(f)
            new MapPartitionsRDD[U, T](this, (context, pid, iter) => iter.map(cleanF)
        }
    }

看xx.map(pair = > pair._2.toString),参数为pair类型,而输入为u类型s

scala中类型变量Bounds代码

//class Pair[T](val first : T, val second : T)
lcass Pair[T <: Comparable[T]](val first : T, val second : T){
    def bigger = if(first.compareTo(second) > 0 ) first else second
}

class Pair_lower_Bound[T](val first : T, val second : T){
    def replaceFirst[R >: T](newFirst : R) = new Pair_lower_Bound[R](newFirst, second)
}

object Typy_variables_Bounds{
    def main(args: Array[String]){
        val pair = new Pair("spark","hadoop")
        println(pair.bigger)
    }
}

pair
对这个T有更高的要求,T具有一个方法,或指定其它方法。
<: 右侧为限定的内容,表示T一定是Comparible的子类。

[R >: T]
R是T的上界,T是R的下界

  /**
   * Smarter version of hadoopFile() that uses class tags to figure out the classes of keys,
   * values and the InputFormat so that users don't need to pass them directly. Instead, callers
   * can just write, for example,
   * {{{
   * val file = sparkContext.hadoopFile[LongWritable, Text, TextInputFormat](path, minPartitions)
   * }}}
   *
   * '''Note:''' Because Hadoop's RecordReader class re-uses the same Writable object for each
   * record, directly caching the returned RDD or directly passing it to an aggregation or shuffle
   * operation will create many references to the same object.
   * If you plan to directly cache, sort, or aggregate Hadoop writable objects, you should first
   * copy them using a `map` function.
   */
  def hadoopFile[K, V, F <: InputFormat[K, V]]
      (path: String, minPartitions: Int)
      (implicit km: ClassTag[K], vm: ClassTag[V], fm: ClassTag[F]): RDD[(K, V)] = withScope {
    hadoopFile(path,
      fm.runtimeClass.asInstanceOf[Class[F]],
      km.runtimeClass.asInstanceOf[Class[K]],
      vm.runtimeClass.asInstanceOf[Class[V]],
      minPartitions)
  }

类型和多态基础

什么是静态类型?它们为什么有用?

按Pierce的话讲:

“类型系统是一个语法方法,它们根据程序计算的值的种类对程序短语进行分类,通过分类结果错误行为进行自动检查。”

类型允许你表示函数的定义域和值域。例如,从数学角度看这个定义:

f: R -> N

它告诉我们函数“f”是从实数集到自然数集的映射。

抽象地说,这就是 具体 类型的准确定义。类型系统给我们提供了一些更强大的方式来表达这些集合。
鉴于这些注释,编译器可以 静态地 (在编译时)验证程序是 合理 的。也就是说,如果值(在运行时)不符合程序规定的约束,编译将失败。
一般说来,类型检查只能保证 不合理 的程序不能编译通过。它不能保证每一个合理的程序都 可以 编译通过。
随着类型系统表达能力的提高,我们可以生产更可靠的代码,因为它能够在我们运行程序之前验证程序的不变性(当然是发现类型本身的模型bug!)。学术界一直很努力地提高类型系统的表现力,包括值依赖(value-dependent)类型!
需要注意的是,所有的类型信息会在编译时被删去,因为它已不再需要。这就是所谓的擦除。

Scala中的类型

Scala强大的类型系统拥有非常丰富的表现力。其主要特性有:

  • 参数化多态性 粗略地说,就是泛型编程
  • (局部)类型推断 粗略地说,就是为什么你不需要这样写代码val i: Int = 12: Int
  • 存在量化 粗略地说,为一些没有名称的类型进行定义
  • 视窗 我们将下周学习这些;粗略地说,就是将一种类型的值“强制转换”为另一种类型

参数化多态性

态性是在不影响静态类型丰富性的前提下,用来(给不同类型的值)编写通用代码的。
例如,如果没有参数化多态性,一个通用的列表数据结构总是看起来像这样(事实上,它看起来很像使用泛型前的Java):

scala> 2 :: 1 :: "bar" :: "foo" :: Nil
res5: List[Any] = List(2, 1, bar, foo)

现在我们无法恢复其中成员的任何类型信息。

scala> res5.head
res6: Any = 2

所以我们的应用程序将会退化为一系列类型转换(“asInstanceOf[]”),并且会缺乏类型安全的保障(因为这些都是动态的)。
多态性是通过指定 类型变量 实现的。

scala> def drop1[A](l: List[A]) = l.tail
drop1: [A](l: List[A])List[A]

scala> drop1(List(1,2,3))
res1: List[Int] = List(2, 3)

Scala有秩1多态性

粗略地说,这意味着在Scala中,有一些你想表达的类型概念“过于泛化”以至于编译器无法理解。假设你有一个函数

def toList[A](a: A) = List(a)

你希望继续泛型地使用它:

def foo[A, B](f: A => List[A], b: B) = f(b)

这段代码不能编译,因为所有的类型变量只有在调用上下文中才被固定。即使你“钉住”了类型B:

def foo[A](f: A => List[A], i: Int) = f(i)

…你也会得到一个类型不匹配的错误。

类型推断

静态类型的一个传统反对意见是,它有大量的语法开销。Scala通过 类型推断 来缓解这个问题。
在函数式编程语言中,类型推断的经典方法是 Hindley Milner算法,它最早是实现在ML中的。
Scala类型推断系统的实现稍有不同,但本质类似:推断约束,并试图统一类型。
例如,在Scala中你无法这样做:

scala> { x => x }
<console>:7: error: missing parameter type
       { x => x }

而在OCaml中你可以:

# fun x -> x;;
- : 'a -> 'a = <fun>

在Scala中所有类型推断是 局部的 。Scala一次分析一个表达式。例如:

scala> def id[T](x: T) = x
id: [T](x: T)T

scala> val x = id(322)
x: Int = 322

scala> val x = id("hey")
x: java.lang.String = hey

scala> val x = id(Array(1,2,3,4))
x: Array[Int] = Array(1, 2, 3, 4)

类型信息都保存完好,Scala编译器为我们进行了类型推断。请注意我们并不需要明确指定返回类型。

变性 Variance

Scala的类型系统必须同时解释类层次和多态性。类层次结构可以表达子类关系。
在混合OO和多态性时,一个核心问题是:如果T’是T一个子类,Container[T’]应该被看做是Container[T]的子类吗?
变性(Variance)注解允许你表达类层次结构和多态类型之间的关系:

含义Scala标记
协变covariantC[T’]是 C[T] 的子类[+T]
逆变contravariantC[T] 是 C[T’]的子类[-T]
不变invariantC[T] 和 C[T’]无关[T]

子类型关系的真正含义:对一个给定的类型T,如果T’是其子类型,你能替换它吗?

scala> class Covariant[+A]
defined class Covariant

scala> val cv: Covariant[AnyRef] = new Covariant[String]
cv: Covariant[AnyRef] = Covariant@4035acf6

scala> val cv: Covariant[String] = new Covariant[AnyRef]
<console>:6: error: type mismatch;
 found   : Covariant[AnyRef]
 required: Covariant[String]
       val cv: Covariant[String] = new Covariant[AnyRef]
                                   ^
scala> class Contravariant[-A]
defined class Contravariant

scala> val cv: Contravariant[String] = new Contravariant[AnyRef]
cv: Contravariant[AnyRef] = Contravariant@49fa7ba

scala> val fail: Contravariant[AnyRef] = new Contravariant[String]
<console>:6: error: type mismatch;
 found   : Contravariant[String]
 required: Contravariant[AnyRef]
       val fail: Contravariant[AnyRef] = new Contravariant[String]         
                                     ^

逆变似乎很奇怪。什么时候才会用到它呢?令人惊讶的是,函数特质的定义就使用了它!

trait Function1 [-T1, +R] extends AnyRef

如果你仔细从替换的角度思考一下,会发现它是非常合理的。让我们先定义一个简单的类层次结构:

scala> class Animal { val sound = "rustle" }
defined class Animal

scala> class Bird extends Animal { override val sound = "call" }
defined class Bird

scala> class Chicken extends Bird { override val sound = "cluck" }
defined class Chicken

假设你需要一个以Bird为参数的函数:

scala> val getTweet: (Bird => String) = // TODO

标准动物库有一个函数满足了你的需求,但它的参数是Animal。在大多数情况下,如果你说“我需要一个___,我有一个___的子类”是可以的。但是,在函数参数这里是逆变的。如果你需要一个接受参数类型Bird的函数变量,但却将这个变量指向了接受参数类型为Chicken的函数,那么给它传入一个Duck时就会出错。然而,如果将该变量指向一个接受参数类型为Animal的函数就不会有这种问题:

scala> val getTweet: (Bird => String) = ((a: Animal) => a.sound )
getTweet: Bird => String = <function1>

函数的返回值类型是协变的。如果你需要一个返回Bird的函数,但指向的函数返回类型是Chicken,这当然是可以的。

scala> val hatch: (() => Bird) = (() => new Chicken )
hatch: () => Bird = <function0>

边界

Scala允许你通过 边界 来限制多态变量。这些边界表达了子类型关系。

scala> def cacophony[T](things: Seq[T]) = things map (_.sound)
<console>:7: error: value sound is not a member of type parameter T
       def cacophony[T](things: Seq[T]) = things map (_.sound)
                                                        ^

scala> def biophony[T <: Animal](things: Seq[T]) = things map (_.sound)
biophony: [T <: Animal](things: Seq[T])Seq[java.lang.String]

scala> biophony(Seq(new Chicken, new Bird))
res5: Seq[java.lang.String] = List(cluck, call)

类型下界也是支持的,这让逆变和巧妙协变的引入得心应手。
List[+T]是协变的
一个Bird的列表也是Animal的列表。List定义一个操作::(elem T)返回一个加入了elem的新的List。新的List和原来的列表具有相同的类型:

scala> val flock = List(new Bird, new Bird)
flock: List[Bird] = List(Bird@7e1ec70e, Bird@169ea8d2)

scala> new Chicken :: flock
res53: List[Bird] = List(Chicken@56fbda05, Bird@7e1ec70e, Bird@169ea8d2)

List 同样 定义了::B >: T 来返回一个List[B]。请注意B >: T,这指明了类型B为类型T的超类。这个方法让我们能够做正确地处理在一个List[Bird]前面加一个Animal的操作:

scala> new Animal :: flock
res59: List[Animal] = List(Animal@11f8d3a8, Bird@7e1ec70e, Bird@169ea8d2)

注意返回类型是Animal。

量化

有时候,你并不关心是否能够命名一个类型变量,例如:

scala> def count[A](l: List[A]) = l.size
count: [A](List[A])Int

这时你可以使用“通配符”取而代之:

scala> def count(l: List[_]) = l.size
count: (List[_])Int

这相当于是下面代码的简写:

scala> def count(l: List[T forSome { type T }]) = l.size
count: (List[T forSome { type T }])Int

注意量化会的结果会变得非常难以理解:

scala> def drop1(l: List[_]) = l.tail
drop1: (List[_])List[Any]

突然,我们失去了类型信息!让我们细化代码看看发生了什么:

scala> def drop1(l: List[T forSome { type T }]) = l.tail
drop1: (List[T forSome { type T }])List[T forSome { type T }]

我们不能使用T因为类型不允许这样做。
你也可以为通配符类型变量应用边界:

scala> def hashcodes(l: Seq[_ <: AnyRef]) = l map (_.hashCode)
hashcodes: (Seq[_ <: AnyRef])Seq[Int]

scala> hashcodes(Seq(1,2,3))
<console>:7: error: type mismatch;
 found   : Int(1)
 required: AnyRef
Note: primitive types are not implicitly converted to AnyRef.
You can safely force boxing by casting x.asInstanceOf[AnyRef].
       hashcodes(Seq(1,2,3))
                     ^

scala> hashcodes(Seq("one", "two", "three"))
res1: Seq[Int] = List(110182, 115276, 110339486)

参考 D. R. MacIver写的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)
        ^

其他类型限制

方法可以通过隐含参数执行更复杂的类型限制。例如,List支持对数字内容执行sum,但对其他内容却不行。可是Scala的数字类型并不都共享一个超类,所以我们不能使用T <: Number。相反,要使之能工作,Scala的math库对适当的类型T 定义了一个隐含的Numeric[T]。 然后在List定义中使用它:

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

如果你调用List(1,2).sum(),你并不需要传入一个 num 参数;它是隐式设置的。但如果你调用List(“whoop”).sum(),它会抱怨无法设置num。
在没有设定陌生的对象为Numeric的时候,方法可能会要求某种特定类型的“证据”。这时可以使用以下类型-关系运算符

标记含义
A =:= BA 必须和 B相等
A <:< BA 必须是 B的子类
A <%< BA 必须可以被看做是 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)
}

其主要优点是:

  • 集合中的元素不必实现 Ordered 特质,但 Ordered 的使用仍然可以执行静态类型检查。
  • 无需任何额外的库支持,你也可以定义自己的排序:
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 可以对“高阶”的类型进行抽象。例如,假设你需要用几种类型的容器处理几种类型的数据。你可能定义了一个 Container 的接口,它可以被实现为几种类型的容器:Option、List 等。你要定义可以使用这些容器里的值的接口,但不想确定值的类型。

这类似于函数柯里化。例如,尽管“一元类型”有类似 List[A] 的构造器,这意味着我们必须满足一个“级别”的类型变量来产生一个具体的类型(就像一个没有柯里化的函数需要只提供一个参数列表来被调用),更高阶的类型需要更多。

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 是参数化类型的多态(“容器类型”)。

如果我们结合隐式转换 implicits 使用容器,我们会得到“特设的”多态性:即对容器写泛型函数的能力。

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]

现在子类必须实现 compare 方法

def compare(that: Container): Int

但我们不能访问具体子类型,例如:

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

编译失败,因为我们对 Container 指定了Ordered特质,而不是对特定子类型指定的。

一个可选的解决方案是将 Container 参数化,以便我们能在子类中访问其子类型。

trait Container[A] extends Ordered[A]

现在子类可以这样做:

class MyContainer extends Container[MyContainer] {
  def compare(that: MyContainer): Int
}

但问题在于 A 类型没有被任何东西约束,这导致你可能会做类似这样的事情:

class MyContainer extends Container[String] {
  def compare(that: String): Int
}

为了调和这一点,我们改用F-界的多态性。

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

奇怪的类型!但注意现在如何用 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 with 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 支持 结构类型(structural types) — 类型需求由接口 结构 表示,而不是由具体的类型表示。

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

在做依赖注入等情况下,这往往是一个有用的技巧。

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

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 提供了*清单(Manifests)*功能,使我们能够选择性地恢复类型信息。清单作为一个隐式的值被提供,它由编译器根据需要生成。

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
  }    
}

一个服务可以通过过滤器对请求进行身份验证。

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

享用安全的类型吧!

模式匹配

这是Scala中最有用的部分之一。

匹配值

val times = 1

times match {
  case 1 => "one"
  case 2 => "two"
  case _ => "some other number"
}

使用守卫进行匹配

times match {
  case i if i == 1 => "one"
  case i if i == 2 => "two"
  case _ => "some other number"
}

注意我们是怎样将值赋给变量’i’的。
在最后一行指令中的_是一个通配符;它保证了我们可以处理所有的情况。 否则当传进一个不能被匹配的数字的时候,你将获得一个运行时错误。我们以后会继续讨论这个话题的。
参考 Effective Scala 对什么时候使用模式匹配 和 模式匹配格式化的建议. A Tour of Scala 也描述了 模式匹配

匹配类型

你可以使用 match来分别处理不同类型的值。

def bigger(o: Any): Any = {
  o match {
    case i: Int if i < 0 => i - 1
    case i: Int => i + 1
    case d: Double if d < 0.0 => d - 0.1
    case d: Double => d + 0.1
    case text: String => text + "s"
  }
}

匹配类成员

还记得我们之前的计算器吗。
让我们通过类型对它们进行分类。
一开始会很痛苦。

def calcType(calc: Calculator) = calc match {
  case _ if calc.brand == "HP" && calc.model == "20B" => "financial"
  case _ if calc.brand == "HP" && calc.model == "48G" => "scientific"
  case _ if calc.brand == "HP" && calc.model == "30B" => "business"
  case _ => "unknown"
}

(⊙o⊙)哦,太痛苦了。幸好Scala提供了一些应对这种情况的有效工具。

运算符

http://blog.csdn.net/bobozhengsir/article/details/13023023

符号语法糖

初学Scala看到那些稀奇古怪的符号(e.g. <: , >: , <% , =:= , <:< , <%<, +T, -T ),总让人摸不着头脑,Scala创造这些语法糖究竟是要做甚?再参详了几篇文章(具体见参考文献)后,作者终于可以摸着一些皮毛了,于是决定记录下来。

1. 上下界约束符号 <: 与 >:

这对符号个人觉得是里面最好理解的了,这对符号用于写范型类/函数时约束范型类型。
先举个栗子:

def using[A <: Closeable, B](closeable: A) (getB: A => B): B =  
  try {   
    getB(closeable)  
  } finally {  
    closeable.close()   
  } 

例子中A <: Closeable(java.io.Cloaseable)的意思就是保证类型参数A是Closeable的子类(含本类),语法“A <:B“定义了B为A的上界;同理相反的A>:B的意思就是A是B的超类(含本类),定义了B为A的下界。
其实<: 和 >: 就等价于Java范型编程中的 extends,super(PS: 说起来C#中只有where A:B形似的上界约束,怎么没有下界约束呢?求高人指教)

2. 协变与逆变符号+T, -T

“协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。
e.g. String => AnyRef

“逆变”则是指能够使用派生程度更小的类型。
e.g. AnyRef => String

【+T】表示协变,【-T】表示逆变

** eg. List类原理**
列表并非scala"内建"语言结构,它们由List抽象定义在scala包之中,并且包含了两个子类–:: 和 Nil.

sealed abstract class List[+A] extends AbstractSeq[A] with LinearSeq[A] with Product with GenericTraversableTemplate[A, List] with LinearSeqOptimized[A, List[A]] with Serializable

This class comes with two implementing case classes scala.Nil and scala.:: that implement the abstract members isEmpty, head and tail.

List 是抽象类,所以不能通过调用空的 List 构造器定义元素.eg. new List
类具有类型参数T.类型参数前的+号特指列表是协变的.也就是说可以把List[Int]类型的值赋给类型为List[Any]的变量:

 val xs = List(1,2,34)
xs: List[Int] = List(1, 2, 34)

scala> val ys : List[Any] = xs
ys: List[Any] = List(1, 2, 34)

所有的列表都可以用以下3种方法:
def isEmpty:Boolean
def head: T
def tail: List[T]

3. view bounds(视界) 与 <%

<%的意思是“view bounds”(视界),它比<:适用的范围更广,除了所有的子类型,还允许隐式转换过去的类型

def method [A <% B](arglist): R = ...  

等价于:

def method [A](arglist)(implicit viewAB: A => B): R = ...  

或等价于:

implicit def conver(a:A): B =def method [A](arglist): R = ...  

<% 除了方法使用之外,class声明类型参数时也可使用:

scala> class A[T <% Int]  
defined class A  

但无法对trait的类型参数使用 <%,

scala> trait A[T <% Int]  
<console>:1: error: traits cannot have type parameters with context bounds `: ...' nor view bounds `<% ...'  

4. 广义类型约束符号 =:=, <:<, <%<

这些被称为广义的类型约束。他们允许你从一个类型参数化的class或trait,进一步约束其类型参数之一。下面是一个例子:

case class Foo[A](a:A) { // 'A' can be substituted with any type  
    // getStringLength can only be used if this is a Foo[String]  
    def getStringLength(implicit evidence: A =:= String) = a.length  
} 

这个隐式的参数 evidence 由编译器提供,A =:=String表示证明A是String类型(PS:即使A可以隐式转换成String类型也不行),因此参数a就可以调用a.length 而编译器不会报错。
我们可以如下使用:

scala> Foo("blah").getStringLength  
res0: Int = 4  

一旦我们使用其他不能转换成String类型的参数,就会报错,如下:

scala> Foo(123).getStringLength  
<console>:10: error: Cannot prove that Int =:= String.  
              Foo(123).getStringLength  
                       ^  
scala> implicit def charSeq2String(s: Seq[Char]) = s.mkString  
charSeq2String: (s: Seq[Char])String  
  
scala> Foo(Seq[Char]('a','b','c')).getStringLength  
<console>:11: error: Cannot prove that Seq[Char] =:= String.  
              Foo(Seq[Char]('a','b','c')).getStringLength  

<:< 和 <%< 使用类似, 有细微不同:

  • A =:= B 表示 A 必须是 B 类型
  • A <:< B 表示 A 必须是B的子类型 (类似于简单类型约束 <:)
  • A <%< B 表示 A 必须是可视化为 B类型, 可能通过隐式转换 (类似与简单类型约束 <%)

5. 传名调用(call-by-name)符号: => type

  • 传名调用 (Call by name)

在“传名调用”求值中,根本就不求值给函数的实际参数 — 而是使用避免捕获代换把函数的实际参数直接代换入函数体内。如果实际参数在函数的求值中未被用到,则它永不被求值;如果这个实际参数使用多次,则它每次都被重新求值

传名调用求值超过传值调用求值的优点是传名调用求值在一个值存在的时候总是生成这个值,而传名调用可能不终止如果这个函数的实际参数是求值这个函数所不需要的不终止计算。反过来说,在函数的实际参数会用到的时候传名调用就非常慢了,这是因为实践中几乎总是要使用如 thunk 这样的机制.

  • 传需求调用 (Call by need)

“传需求调用”是传名调用的记忆化版本,如果“函数的实际参数被求值了”,这个值被存储起来已备后续使用。在“纯”(无副作用)设置下,这产生同传名调用一样的结果;当函数实际参数被使用两次或更多次的时候,传需求调用总是更快。

Scala中call by name使用

object TargetTest2 extends Application {  

  def loop(body: => Unit): LoopUnlessCond =  
    new LoopUnlessCond(body)  
    
  protected class LoopUnlessCond(body: => Unit) {  
    def unless(cond: => Boolean) {  
      body  
      if (!cond) unless(cond)  
    }  
  }  
  
  var i = 10  
  loop {  
    println("i = " + i)  
    i -= 1  
  } unless (i == 0)  
  
} 

上面的程序运行结果是

i = 10  
i = 9  
i = 8  
i = 7  
i = 6  
i = 5  
i = 4  
i = 3  
i = 2  
i = 1  

6.参考文献

1.http://stackoverflow.com/questions/3427345/what-do-and-mean-in-scala-2-8-and-where-are-they-documented
2.http://stackoverflow.com/questions/7167662/how-to-express-implicit-conv-string-a-as-a-view-bound
3.http://hongjiang.info/scala-type-system-view-bounds/
4.http://zh.wikipedia.org/wiki/%E6%B1%82%E5%80%BC%E7%AD%96%E7%95%A5#.E4.BC.A0.E5.90.8D.E8.B0.83.E7.94.A8_.28Call_by_name.29

运算符重载

从技术角度看,scala没有运算符,"运算符重载"是指重载像+, ±这样的符号. 在scala里这些实际上是方法名:运算符利用了scala灵活的方式调用语法–**在scala里,对象的引用和方法名之间的点(.)不是必需的.
这两个特性给我我们运算符重载的错觉.
ref1 + ref2 实现上为ref1.+(ref2),这是在调用ref1.的+()方法.看个+运算符的例子来自Complex类,这个类表示复数.

class Complex(val real: Int, val imaginary: Int) {
  def +(operand: Complex): Complex = {
    println("Calling +")
    new Complex(real + operand.real, imaginary + operand.imaginary)
  }

  def *(operand: Complex): Complex = {
    println("Calling *")
    new Complex(real * operand.real - imaginary * operand.imaginary,
      real * operand.imaginary + imaginary * operand.real)
  }

  override def toString: String = {
    real + (if(imaginary < 0 ) "" else  "+") + imaginary + "i"
  }
}

运算符优先级

scala中所有都是对象

1 + 2  // eq 1.+(2)

但这里就出现个问题,运行符的优先级是怎么定义的?

class Complex(val real: Int, val imaginary: Int) {
  def +(operand: Complex): Complex = {
    println("Calling +")
    new Complex(real + operand.real, imaginary + operand.imaginary)
  }

  def *(operand: Complex): Complex = {
    println("Calling *")
    new Complex(real * operand.real - imaginary * operand.imaginary,
      real * operand.imaginary + imaginary * operand.real)
  }

  override def toString: String = {
    real + (if(imaginary < 0 ) "" else  "+") + imaginary + "i"
  }
}

class Complex2(val real: Int, val imaginary: Int) {
  def plus(operand: Complex2): Complex2 = {
    println("Calling +")
    new Complex2(real + operand.real, imaginary + operand.imaginary)
  }

  def multiply(operand: Complex2): Complex2 = {
    println("Calling *")
    new Complex2(real * operand.real - imaginary * operand.imaginary,
      real * operand.imaginary + imaginary * operand.real)
  }

  override def toString: String = {
    real + (if(imaginary < 0 ) "" else  "+") + imaginary + "i"
  }
}

object App1 {
  def main(args: Array[String]): Unit = {
       val c1 = new Complex(1, 4)
    val c2 = new Complex(2, -3)
    val c3 = new Complex(2, 2)

    println("Complex")
    println(c1 + c2 * c3)

    println("================")
    val sc1 = new Complex2(1, 4)
    val sc2 = new Complex2(2, -3)
    val sc3 = new Complex2(2, 2)

    println("Complex2")
    println(sc1 plus sc2 multiply sc3)
  }
}
Complex
Calling *
Calling +
11+2i
================
Complex2
Calling +
Calling *
4+8i

由以上结果可以看出来,运行符本身自带优先级.应该是在类加载器时做过处理.

小技巧

简洁

点和括号是可选的

如果方法有0或1个参数,点和括号是可以丢掉的.如果方法的参数多于一个,就必须使用括号,但点仍然是可选的.
eg. a+b 实际上是a.+(b), 1 to 3实际是1.to(3)

利用这样的语法优势,可以创建出读起来更自然的代码.假设类Car定义turn()方法

def turn(direction:String) //...
car turn "right" //享受可选的点和括号,削减代码中的杂乱

避免使用return

以下如果使用return 则必须指定函数的返回值,也就不能让编译器推演返回类型(像check1那样).

def check1() = true

def check2():Boolean = return true

def main(args: Array[String]): Unit = {
    println(check1())
    println(check2())
  }

scala优点

DSL

其实不少投行在用,我们在上海的研发部门里面Scala就用的不少。我自己在做的一个项目核心用Java写的,用Scala提供了一个Domain Language开放给用户 - 用户多半不是程序员出身但是常需要写点代码。
在我们的项目里Scala被看重的主要原因是生产效率和对DSL的支持,但是我们在开发工具支持和一部分类库的性能上还是碰到一些问题,一个典型事例就是在几个月之内几乎整个开发组由Eclipse逐个切换到IntelliJ IDEA去了,主要原因是Eclipse Scala plugin的种种恼人小问题

国外使用scala的公司挺多了,像LinkedIn, EDFT,Twitter, Novell, the Guardian, Xebia, Xerox, FourSquare, Sony, Siemens, Thatcham, OPower, GridGain, AppJet, Reaktor等。
国内好像sina在用,我们也准备使用scala来代替一部分java code。

蘑菇街有个小项目, 我是用 Scala + Akka + Spray 写的, 属于内部中间件项目, 暴露 REST API.
现在在做一个较大的技术产品, 使用 sbt + Scala + Akka, 项目基本上都用 Scala 写. 除了少数 Java 代码.
我比较倾向于使用 Scala 做中间件, 并对外暴露语言无关协议.
同时, 用 ScalaTest 做单元测试, 体验也非常好.
建议使用 idea, eclipse的插件目前真的不如 idea 插件.
目前使用上发现的一个问题是, jstack 出来之后的堆栈, 有太多的 $ 符号了.
@杨普
提到的用来写一些 DSL, 也是一个不错的应用场景. 有机会我们会去尝试的.

有时候Scala编译器会需要你定义函数的结果类型。比方说,如果函数是递归的,你就必须显式地定义函数结果类型。然而在max的例子里,你可以不用写结果类型,编译器也能够推断它。

  • 在Java里,从方法里返回的值的类型被称为返回类型。在Scala里,同样的概念被叫做结果类型。
  • 如果一个方法调用自身,就称为递归。
  • 尽管如此,就算编译器不需要,显式说明函数结果类型也经常是个好主意,这种类型标注可以使代码便于阅读,因为读者不用研究了函数体之后再去猜结果类型。 同样,如果函数仅由一个句子组成,你可以可选地不写大括号。这样,你就可以把max函数写成这样:
    scala> def max2(x: Int, y: Int) = if (x > y) x else y

自适应类型

Option类型

null 的存在是Java 语言的一大混乱来源:它到底是一个有效的返回值,还是表明返回值
缺失了?包括Scala 在内的很多函数式语言通过Option 类来避免这种语义上的含混,其取
值要么是表示没有返回值的None,要么是容纳了返回值的Some —函数式编程思想

scala在指定"不存在"的问题上向前更进了一步.
执行模式匹配时,匹配结果可能是一个对象/一个list/一个元组等等,也可能是不存在.静悄悄的返回null会有两个方面问题.首先,我们没有显式的表达出"我就是希望没有结果"的效果图.
其次,没有办法强迫函数调用者对不存在(null)进行检查.scala相让这样的意图理清晰的表达出来,确实,有时候就需要没有结果.
scala以一种类开安全的方式做到的一一点,它使用Option[T]类型.

 def commentOnPractic(input: String)={
    //rather than retuning null
    if(input == "test") Some("good") else None
  }



  def main(args: Array[String]): Unit = {
    for(input <- Set("test", "hack")){
      val comment = commentOnPractic(input)
      println("input " + input + " comment " + comment.getOrElse("Found no comments"))
    }
   }

commentOnPractise()也会返回一个注释(string),也许压根没有任何注释,这两点分别用Some[T]和None的实例表示.这两个类都继承自Option[T]类.输出为

input test comment good
input hack comment Found no comments

这类型显示声明为Option[String],
Scala会强制我们检查补全的不存在.
如此一来,就不太可能因为没有检查null引用而抛出NullPointerException.调用返回Option[T]的getOrElse()方法,可以主动的应对结果不存在(None)的情形.


Option 是一个表示有可能包含值的容器。
Option基本的接口是这样的:

trait Option[T] {
  def isDefined: Boolean
  def get: T
  def getOrElse(t: T): T
}

Option本身是泛型的,并且有两个子类: Some[T] 或 None
我们看一个使用Option的例子:
Map.get 使用 Option 作为其返回值,表示这个方法也许不会返回你请求的值。

scala> val numbers = Map("one" -> 1, "two" -> 2)
numbers: scala.collection.immutable.Map[java.lang.String,Int] = Map(one -> 1, two -> 2)

scala> numbers.get("two")
res0: Option[Int] = Some(2)

scala> numbers.get("three")
res1: Option[Int] = None

现在我们的数据似乎陷在Option中了,我们怎样获取这个数据呢?
直觉上想到的可能是在isDefined方法上使用条件判断来处理。

// We want to multiply the number by two, otherwise return 0.
val result = if (res1.isDefined) {
  res1.get * 2
} else {
  0
}

我们建议使用getOrElse或模式匹配处理这个结果。
getOrElse 让你轻松地定义一个默认值。

val result = res1.getOrElse(0) * 2

模式匹配能自然地配合Option使用。

val result = res1 match {
  case Some(n) => n * 2
  case None => 0
}

参考 Effective Scala 对使用Options的意见。


方法返回类型推演

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大怀特

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值