10分鐘簡介Scala

10分鐘簡介Scala

7 人贊了文章

因為最近項目的需要,構建一個項目組的數據平台用於搜集數據所以接觸到了Scala這門語言。因此想寫文章來總結自己最近看到的知識點,這篇文章會簡介Scala這門語言中的一些與眾不同的特性,能讓剛接觸這門語言的童鞋快速的對Scala有一個大體了解。

目錄:

- Scala簡介

- Scala中的變數與函數

- Scala的類定義相關

Scala簡介

Scala是一門非常靈活的編程語言,主要的特點是支持函數式的編程。在使用Scala的過程中支持嵌套調用Java代碼,Scala本身會被編譯為Java bytecode,可以被JVM執行。

Scala中的變數與函數

變數定義

在Scala中可以使用val和var關鍵字來申明變數。val表名變數是immutable類型,有點類似於C++的常量只能在初始化的時候賦值一次,var關鍵字則表示mutable類型可以被多次賦值。最後面一種def關鍵字申明的是function value,同樣是immutable類型,並且只用在使用到的時候才會正在分配該變數(lazily evaluated)。可以參考鏈接

val k = 0var n = 1def m = 6

函數定義

Scala中的函數定義方式真的是五花八門多種多樣,下面列舉一些常用的函數定義語法:

def f(x: Int) = x+1; def g1(x: Int) = x+1def g2(x: Int) = {x+1}; def h1(x: Int) = (x + 1)def fun(x: Int): Int = x + 1def printTest(x: Unit): Unit = { println(x) // return value is Unit x}// 下面是一個匿名函數的定義(x: Int) ? x + 1// 等價於{def f(x: Int) = x + 1; f _}

在Scala中函數都是有返回類型的,而且不需要使用關鍵字return,默認的返回值是Unit。最後一個函數是匿名函數,為了避免每次使用都需要命名。(x1: T1, ..., xn: Tn)?E 等價於 { def f (x1: T1, ..., xn: Tn) = E ; f _ }

高階函數

在Scala中函數的參數同樣可以是函數,這樣以其他函數作為參數的函數我們稱之為高階函數。

// 參數f為函數類型的變數,Test為高階函數,函數會把一個List中的元素按照f來做映射def Test(arr: List[Int], f: (Int) => Int): List[Int] = { arr.map(f)}Test(List(1,2,3,4,5), (x: Int) => x*x)

這裡要注意區分參數為函數類型的申明以及匿名函數的具體定義。

尾遞歸(Tail Recursion)

函數式編程的特性之一就是遞歸非常多,比如我們以下求階乘的代碼:

def factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n - 1)/* 函數的調用方式如下factorial(4)if (4 == 0) 1 else 4 * factorial(4 - 1)4 * factorial(3)4 * (3 * factorial(2))4 * (3 * (2 * factorial(1)))4 * (3 * (2 * (1 * factorial(0)))4 * (3 * (2 * (1 * 1)))24*/

可以發現上面這個函數會嵌到很多棧空間,Scala針對這種情況提出了尾遞歸的概念,下面我們換一種函數實現方式。

// def factorial(n: Int): Int = { @tailrec def iter(x: Int, result: Int): Int = if (x == 0) result else iter(x - 1 , result * x) iter(n, 1)}// factorial(3) shouldBe 6/*函數的調用方式如下factorial(3)iter(3,1)if(3==0) 1 else iter(2,3*1)if(2==0) 3 else iter(1,3*2)if(1==0) 6 else iter(0,3*2*1)if(0==0) 6 else iter(...)return 6*/

這兩個版本最大的區別在於第二種實現方式可以做到多次遞歸調用的時候不需要保存之前的調用堆棧,因為result作為一個參數傳遞下去了。這樣在Scala的編譯器會自動優化,避免過多的堆棧調用開銷,這種優化就叫做尾遞歸。所以在實現遞歸函數時最好實現能讓編譯器優化的尾遞歸版本。 例子來源傳送門

柯里化(Currying)

Currying也是一個Scala的語言特性,簡單來說就是Scala函數的返回值依然為一個函數:

def add(x:Int): (Int)=>Int = { def square(t:Int): Int = { t*t } x + square(_)}// add(1)(2) is 5// 等價於下面的實現方式def add(x:Int, y:Int): Int = { x + y*y}

Currying通過把函數作為函數值,所以支持類似f(x1)(x2)(x3)這種調用方式。這樣做能夠簡化函數的定義方式。

函數參數的延遲載入(lazily evaluated)

Scala支持只有在變數真正使用的才開始給變數分配存儲空間,這個叫做延遲延遲載入也是Scala的語言特性之一。之前其實已經介紹了比如使用def定義的變數就是延遲載入。同樣函數也支持延遲載入,使用:=>的方式來標明延遲載入。

def function(x: Int, y: ? Int): Int { x*2}

在上面這個例子中y這個變數在函數中就沒有沒使用到所以不會分配空間。

函數內部嵌套定義函數(NESTED METHODS)

在一個Scala函數內部是可以嵌套定義其他函數的,這樣可以避免函數名的泛濫,在一個函數內部定義相關的工具函數也非常好理解。有一個和C++或Python不一樣的地方是在嵌套的函數中可以直接引用外部定義的變數。

def NestFunction(number: Int): Unit = { val number_inside = number + 1 def PrintInput(): Unit = { println (number) println (number_inside) } PrintInput()}NestFunction(4) // 4,5

Scala的類定義相關

Scala的類定義和大多數語言一樣使用Class關鍵字,可以做訪問許可權的控制比如private變數,支持繼承,多態等等。下面介紹的是幾個Scala比較有特點的和類相關的特性。

Case Classes and Pattern Match-ing

Scala中的類可以使用Case來作為關鍵字,使用case定義的類,可以在實例化的時候不顯示的使用new關鍵字,並且Case Class可以和match case配合在一起使用。

Pattern Match-ing有點類似於C++中的switch()..case表達式,但是比其更強大。

import scala.util.Randomval x: Int = Random.nextInt(10)x match { case 0 => "zero" case 1 => "one" case 2 => "two" case _ => "many"}

case _表示默認值,下面的例子是case class和pattern match-ing一起使用的例子。

abstract class Devicecase class Phone(model: String) extends Device{ def screenOff = "Turning screen off"}case class Computer(model: String) extends Device { def screenSaverOn = "Turning screen saver on..."} case class Telephone(model: String) extends Device { def screenSaverOn = "Turning screen saver on..."} def goIdle(device: Device) = device match { case p: Phone => p.screenOff case c: Computer => c.screenSaverOn case t: Telephone if !t.screenSaverOn.empty() => t.screenSaverOn }

我們可以發現在Scala中match case還能匹配出類型一個的變數。傳送門

Generate Types

有點類似C++中的泛型模板,很多編程語言都有這種類似的語言特性:

class Stack[A] { private var elements: List[A] = Nil def push(x: A) { elements = x :: elements } def peek: A = elements.head def pop(): A = { val currentTop = peek elements = elements.tail currentTop }}// Stack[Int], Stack[String]

Variances

這個我不太清楚應該怎麼翻譯比較好,但是是一個很有意思的語言特性,可以讓並不是直接繼承的類成為父類和子類的關係,請看下面的例子:

class Stack[+A] { private var elements: List[A] = Nil def push(x: A) { elements = x :: elements } def peek: A = elements.head def pop(): A = { val currentTop = peek elements = elements.tail currentTop }}abstract class Animal { def name: String}case class Cat(name: String) extends Animalcase class Dog(name: String) extends Animal def printAnimalNames(animals: Stack[Animal]): Unit = { println(animal.peek)} val cats = new Stack[Cat]val dogs = new Stack[Dog] cats.push(Cat("this is cat"))dogs.push(Cat("this is dog"))printAnimalNames(cats)printAnimalNames(dogs)

使用Class[+A]的作用在於如果Type1是Type2子類,那麼Class[Type1]同樣也是Class[+Type2]的子類,上面看起來非常合理的調用就不會報錯。

[-A]和[+A]剛好相反,如果Type1是Type2的子類,那麼Class[Type2]是Class[Type1]的子類。

Upper/Lower Type Bounds

同樣屬於Scala的語言特性,可以限制模板類中的類型,class Type[A :< T]表示A必須要是T的子類。

abstract class Animal { def name: String}abstract class Pet extends Animal {}class Cat extends Pet { override def name: String = "Cat"}class Lion extends Animal { override def name: String = "Lion"}class PetContainer[P <: Pet](p: P) { def pet: P = p}// new PetContainer[Cat](new Cat) is Ok// new PetContainer[Lion](new Lion) Error

反之class Type[A :> T]表示A必須是T的父類。

總結

這裡主要介紹以及瀏覽了一些Scala中的語言特性,這些特性往往在其他編程語言中並不存在(C++/Python),而之所以設計這些語言特性並沒有深入的介紹,後面會繼續完善這個Scala系列的文章。

引用

  • ScalaByExample by Martin Odersky
  • Scala的官方說明文檔

推薦閱讀:

Dotty 0.8.0-RC1 發布
Scala學習筆記02_函數入門
學習Scala一段時間的感想
Scala 的Future 有時並不「Future」

TAG:計算機科學 | Scala | 編程語言 |