1. 程式人生 > >scala 從入門到入門+

scala 從入門到入門+

新手向,面向剛從java過渡到scala的同學,目的是寫出已已易於維護和閱讀的程式碼.

從語句到表示式

語句(statement): 一段可執行的程式碼
表示式(expression): 一段可以被求值的程式碼

在Java中語句和表示式是有區分的,表示式必須在return或者等號右側,而在scala中,一切都是表示式.

一個例子:
假設我們在公司的內網和外網要從不同的域名訪問一樣的機器

//Java程式碼
String urlString = null;
String hostName = InetAddress.getLocalHost().getHostName();
if (isInnerHost(hostName)) {
  urlString = "http://inner.host"
; } else { urlString = "http://outter.host"; }

剛轉到scala的人很可能這麼寫

var urlString: String = null
var hostName = InetAddress.getLocalHost.getHostName
if (isInnerHost(hostName)) {
  urlString = "http://inner.host"
} else {
  urlString = "http://outter.host"
}

我們讓它更像scala一點吧

val hostName = InetAddress.getLocalHost.getHostName
val urlString = if
(isInnerHost(hostName)) { "http://inner.host" } else { "http://outter.host" }

這樣做的好處都有啥?

  1. 程式碼簡練,符合直覺
  2. urlString 是值而不是變數,有效防止 urlString 在後續的程式碼中被更改(編譯時排錯)

很多時候,我們程式設計時說的安全並不是指怕被黑客破壞掉,而是預防自己因為逗比而讓程式崩了.

純函式和非純函式

純函式(Pure Function)是這樣一種函式——輸入輸出資料流全是顯式(Explicit)的。
顯式(Explicit)的意思是,函式與外界交換資料只有一個唯一渠道——引數和返回值;函式從函式外部接受的所有輸入資訊都通過引數傳遞到該函式內部;函式輸出到函式外部的所有資訊都通過返回值傳遞到該函式外部。

如果一個函式通過隱式(Implicit)方式,從外界獲取資料,或者向外部輸出資料,那麼,該函式就不是純函式,叫作非純函式(Impure Function)。
隱式(Implicit)的意思是,函式通過引數和返回值以外的渠道,和外界進行資料交換。比如,讀取全域性變數,修改全域性變數,都叫作以隱式的方式和外界進行資料交換;比如,利用I/O API(輸入輸出系統函式庫)讀取配置檔案,或者輸出到檔案,列印到螢幕,都叫做隱式的方式和外界進行資料交換。

//一些例子
//純函式
def add(a:Int,b:Int) = a + b
//非純函式
var a = 1
def addA(b:Int) = a + b
 
def add(a:Int,b:Int) = {
  println(s"a:$a b:$b")
  a + b
}
def randInt() = Random.nextInt()

純函式的好處(來自維基百科)

  • 無狀態,執行緒安全,不需要執行緒同步.
  • 純函式相互呼叫組裝起來的函式,還是純函式.
  • 應用程式或者執行環境(Runtime)可以對純函式的運算結果進行快取,運算加快速度.

純函式的好處(來自我的經驗)

  • 單元測試非常方便!
  • 分散式/併發環境下,斷點除錯的方式無以為繼,你需要單元測試.

惰性求值/Call by name

維基百科中惰性求值的解釋
惰性求值(Lazy Evaluation),又稱惰性計算、懶惰求值,是一個計算機程式設計中的一個概念,它的目的是要最小化計算機要做的工作。它有兩個相關而又有區別的含意,可以表示為“延遲求值”和“最小化求值”,本條目專注前者,後者請參見最小化計算條目。除可以得到效能的提升外,惰性計算的最重要的好處是它可以構造一個無限的資料型別。
惰性求值的相反是及早求值,這是一個大多數程式語言所擁有的普通計算方式。

惰性求值不是新鮮事

import scala.io.Source.fromFile
val iter: Iterator[String] =
  fromFile("sampleFile")
    .getLines()

檔案迭代器就用到了惰性求值.
使用者可以完全像操作記憶體中的資料一樣操作檔案,然而檔案只有一小部分傳入了記憶體中.

用lazy關鍵詞指定惰性求值

lazy val firstLazy = {
  println("first lazy")
  1
}
lazy val secondLazy = {
  println("second lazy")
  2def add(a:Int,b:Int) = {
  a+b
}
//在 scala repl 中的結果
scala> add(secondLazy,firstLazy)
second lazy
first lazy
res0: Int = 3

res0: Int = 3

second lazy 先於 first lazy輸出了

Call by value 就是函式引數的惰性求值

def firstLazy = {
  println("first lazy")
  1
}
def secondLazy = {
  println("second lazy")
  2
}
def chooseOne(first: Boolean, a: Int, b: Int) = {
  if (first) a else b
}
def chooseOneLazy(first: Boolean, a: => Int, b: => Int) = {
  if (first) a else b
}
chooseOne(first = true, secondLazy, firstLazy)
//second lazy
//first lazy
//res0: Int = 2
chooseOneLazy(first = true, secondLazy, firstLazy)
//second lazy
//res1: Int = 2

對於非純函式,惰性求值會產生和立即求值產生不一樣的結果.

一個例子,假設你要建立一個本地快取

//需要查詢mysql等,可能來自於一個第三方jar包
def itemIdToShopId: Int => Int  
var cache = Map.empty[Int, Int]
def cachedItemIdToShopId(itemId: Int):Int = {
  cache.get(itemId) match {
    case Some(shopId) => shopId
    case None =>
      val shopId = itemIdToShopId(itemId)
      cache += itemId -> shopId
      shopId
  }
}
  • 羅輯沒什麼問題,但測試的時候不方便連mysql怎麼辦?
  • 如果第三方jar包發生了改變,cachedItemIdToShopId也要發生改變.
//用你的本地mock來測試程式
def mockItemIdToSHopId: Int => Int
def cachedItemIdToShopId(itemId: Int): Int ={  
  cache.get(itemId) match { 
    case Some(shopId) => shopId
   case None => 
      val shopId = mockItemIdToSHopId(itemId)
      cache += itemId -> shopId
     shopId 
  } 
}   
  • 在測試的時候用mock,提交前要換成線上的,反覆測試的話要反覆改動,非常令人沮喪.
  • 手工操作容易忙中出錯.
//將遠端請求的結果作為函式的一個引數
def cachedItemIdToShopId(itemId: Int, remoteShopId: Int): Int = {   
  cache.get(itemId) match { 
    case Some(shopId) => shopId 
    case None =>    
     val shopId = remoteShopId  
     cache += itemId -> shopId  
      shopId
  } 
}
//呼叫這個函式
cachedItemIdToShopId(itemId,itemIdToShopId(itemId))
  • 函式對mysql的依賴沒有了
  • 不需要在測試和提交時切換程式碼
  • 貌似引入了新問題?

沒錯,cache根本沒有起應有的作用,函式每次執行的時候都呼叫了itemIdToShopId從遠端取資料

//改成call by name就沒有這個問題啦
def cachedItemIdToShopId(itemId: Int, remoteShopId: =>Int): Int = { 
  cache.get(itemId) match { 
    case Some(shopId) => shopId 
    case None =>    
     val shopId = remoteShopId  
     cache += itemId -> shopId  
      shopId
  } 
}
//呼叫這個函式
cachedItemIdToShopId(itemId,itemIdToShopId(itemId))
  • 函式對mysql的依賴沒有了
  • 不需要在測試和提交時切換程式碼
  • 只在需要的時候查詢遠端庫

Tuple/case class/模式匹配

Tuple為程式設計提供許多便利

  • 函式可以通過tuple返回多個值
  • tuple可以儲存在容器類中,代替java bean
  • 可以一次為多個變數賦值

使用tuple的例子

val (one, two) = (1, 2)     
one //res0: Int = 1 
two //res1: Int = 2         
def sellerAndItemId(orderId: Int): (Int, Int) =
   orderId match {  
    case 0 => (1, 2)    
 }          
val (sellerId, itemId) = sellerAndItemId(0)
sellerId // sellerId: Int = 1
itemId // itemId: Int = 2       
val sellerItem = sellerAndItemId(0)
sellerItem._1 //res4: Int = 1
sellerItem._2 //res5: Int = 2

用模式匹配增加tuple可讀性

val sampleList = List((1, 2, 3), (4, 5, 6), (7, 8, 9))
sampleList.map(x => s"${x._1}_${x._2}_${x._3}")
//res0: List[String] = List(1_2_3, 4_5_6, 7_8_9)
sampleList.map {    
  case (orderId, shopId, itemId) =>
    s"${orderId}_${shopId}_$itemId"
}   
//res1: List[String] = List(1_2_3, 4_5_6, 7_8_9)

上下兩個map做了同樣的事情,但下一個map為tuple中的三個值都給了名字,增加了程式碼的可讀性.

match和java和switch很像,但有區別

  1. match是表示式,會返回值
  2. match不需要”break”
  3. 如果沒有任何符合要求的case,match會拋異常,因為是表示式
  4. match可以匹配任何東西,switch只能匹配數字或字串常量
//case如果是常量,就在值相等時匹配.
//如果是變數,就匹配任何值.
def describe(x: Any) = x match {
   case 5 => "five" 
   case true => "truth" 
   case "hello" => "hi!"    
   case Nil => "the empty list"
   case somethingElse => "something else " + somethingElse  
}   

case class,tuple以及列表都可以在匹配的同時捕獲內部的內容.

case class Sample(a:String,b:String,c:String,d:String,e:String)
def showContent(x: Any) =
 x match {      
  case Sample(a,b,c,d,e) => 
  s"Sample $a.$b.$c.$d.$e"  
  case (a,b,c,d,e) =>   
  s"tuple $a,$b,$c,$d,$e"   
  case head::second::rest =>    
  s"list head:$head second:$second rest:$rest"
}

Case class

  1. 模式匹配過程中其實呼叫了類的unapply方法
  2. Case class 是為模式匹配(以及其他一些方面)提供了特別的便利的類
  3. Case class 還是普通的class,但是它自動為你實現了apply,unapply,toString等方法
  4. 其實tuple就是泛型的case class

用 option 代替 null

null 的問題

Map<String, String> map = ???
String valFor2014 = map.get(“1024”); // null

if (valFor1024 == null)
    abadon();
else doSomething();
  • null到底代表key找不到還是說1024對應的值就是null?
  • 某年某月某日,我把為null則abandon這段程式碼寫了100遍.

option介紹

  • option可以看作是一個容器,容器的size是1或0
  • Size為1的時候就是一個Some[A](x: A),size為0的時候就是一個None

看看scala的map

def get(key: A): Option[B]

def getOrElse[B1 >: B](key: A, default: => B1): B1 = get(key) match {
  case Some(v) => v
  case None => default
}
  • 可以區分Map中到底又沒有這個key.
  • 我見過許多java專案自己實現了getOrElse這個方法並放在一個叫做MapUtils的類裡.
  • 為什麼java經過這麼多代演進,Map仍然沒有預設包含這個方法,一直想不通.
    (寫完這段突然發現java8開始包含getOrDefault了)

好像沒有太大區別?

確實能夠區分Map是無值還是值為null了.
但是if(為null) 則 abandon 要寫一百遍.
case Some(v) => v
case None => default
似乎也得寫一百遍.

試試容器裡的各種方法

val a: Option[String] = Some("1024")
val b: Option[String] = None
a.map(_.toInt)
//res0: Option[Int] = Some(1024)
b.map(_.toInt)
//res1: Option[Int] = None,不會甩exception
a.filter(_ == "2048")
//res2: Option[String] = None
b.filter(_ == "2048")
//res3: Option[String] = None
a.getOrElse("2048")
//res4: String = 1024
b.getOrElse("2048")
//res5: String = 2048
a.map(_.toInt)
  .map(_ + 1)
  .map(_ / 5)
  .map(_ / 2 == 0) //res6: Option[Boolean] = Some(false)
//如果是null,恐怕要一連check abandon四遍了

option配合其他容器使用

val a: Seq[String] =
  Seq("1", "2", "3", null, "4")
val b: Seq[Option[String]] =
  Seq(Some("1"), Some("2"), Some("3"), None, Some("4"))

a.filter(_ != null).map(_.toInt)
//res0: Seq[Int] = List(1, 2, 3, 4)
//如果你忘了檢查,編譯器是看不出來的,只能在跑崩的時候拋異常
b.flatMap(_.map(_.toInt))
//res1: Seq[Int] = List(1, 2, 3, 4)
  • option幫助你把錯誤扼殺在編譯階段
  • flatMap則可以在過濾空值的同時將option恢復為原始資料.

scala原生容器類都對option有良好支援

Seq(1,2,3).headOption
//res0: Option[Int] = Some(1)

Seq(1,2,3).find(_ == 5)
//res1: Option[Int] = None

Seq(1,2,3).lastOption
//res2: Option[Int] = Some(3)

Vector(1,2,3).reduceLeft(_ + _)
//res3: Int = 6

Vector(1,2,3).reduceLeftOption(_ + _)
//res4: Option[Int] = Some(6)
//在vector為空的時候也能用

Seq("a", "b", "c", null, "d").map(Option(_))
//res0: Seq[Option[String]] =
// List(Some(a), Some(b), Some(c), None, Some(d))
//原始資料轉換成option也很方便

用Try類儲存異常

傳統異常處理的侷限性

try {
  1024 / 0
} catch {
  case e: Throwable => e.printStackTrace()
}

用try-catch的模式,異常必須在丟擲的時候馬上處理.
然而在分散式計算中,我們很可能希望將異常集中到一起處理,來避免需要到每臺機器上單獨看錯誤日誌的窘態.

 val seq = Seq(0, 1, 2, 3, 4)
 //seq: Seq[Int] = List(0, 1, 2, 3, 4)

val seqTry = seq.map(x => Try {
  20 / x
})
//seqTry: Seq[scala.util.Try[Int]] = List(Failure(java.lang.ArithmeticException: devide by zero),Success(20), Success(10), Success(6), Success(5))

val succSeq = seqTry.flatMap(_.toOption)
//succSeq: Seq[Int] = List(20, 10, 6, 5) Try可以轉換成Option
val succSeq2 = seqTry.collect {
  case Success(x) => x
}
//succSeq2: Seq[Int] = List(20, 10, 6, 5) 和上一個是一樣的
val failSeq: Seq[Throwable] = seqTry.collect {
  case Failure(e) => e
}
//failSeq: Seq[Throwable] = List(java.lang.ArithmeticException: devide by zero)

Try例項可以序列化,並且在機器間傳送.

函式是一等公民

一個需求

  • 假設我們需要檢查許多的數字是否符合某一範圍
  • 範圍儲存在外部系統中,並且可能隨時更改
  • 數字範圍像這樣儲存著”>= 3,< 7”

一個java版本

List<String> params = new LinkedList<>();
List<Integer> nums = new LinkedList<>();
List<String> marks = new LinkedList<>();

public JavaRangeMatcher(List<String> params) {
    this.params = params;
    for (String param : params) {
        String[] markNum = param.split(" ");
        marks.add(markNum[0]);
        nums.add(Integer.parseInt(markNum[1]));
    }
}

public boolean check(int input) {
    for (int i = 0; i < marks.size(); i++) {
        int num = nums.get(i);
        String mark = marks.get(i);
        if (mark.equals(">") && input <= num) return false;
        if (mark.equals(">=") && input < num) return false;
        if (mark.equals("<") && input >= num) return false;
        if (mark.equals("<=") && input > num) return false;
    }
    return true;
}

List<String> paramsList = new LinkedList<String>() {{
    add(“>= 3”);
    add(“< 7”);
}};
JavaRangeMatcher matcher = new JavaRangeMatcher(paramsList);
int[] inputs = new int[]{1, 3, 5, 7, 9};
for (int input : inputs) {
    System.out.println(matcher.check(input));
}
//給自己有限的時間,想想又沒有效能優化的餘地
//我們一起來跑跑看

一個 scala 版本

def exprToInt(expr: String): Int => Boolean = {
  val Array(mark, num, _*) = expr.split(" ")
  val numInt = num.toInt
  mark match {
    case "<" => numInt.>
    case ">" => numInt.<
    case ">=" => numInt.<=
    case "<=" => numInt.>=
  } //返回函式的函式
}

case class RangeMatcher(range: Seq[String]) {
  val rangeFunc: Seq[(Int) => Boolean] = range.map(exprToInt)

  def check(input: Int) = rangeFunc.forall(_(input))
}

def main(args: Array[String]) {
  val requirements = Seq(">= 3", "< 7")
  val rangeMatcher = RangeMatcher(requirements)
  val results = Seq(1, 3, 5, 7, 9).map(rangeMatcher.check)
  println(results.mkString(","))
  //false,true,true,false,false
}

關於效能

這裡有一個效能測試網站

相關推薦

scala 入門入門+

新手向,面向剛從java過渡到scala的同學,目的是寫出已已易於維護和閱讀的程式碼. 從語句到表示式 語句(statement): 一段可執行的程式碼 表示式(expression): 一段可以被求值的程式碼 在Java中語句和表示式是有區分的,表示式必須在re

Scala編程入門---Map與Tuple

ack mmu contain ges lin ice logs 簡單 創建 創建Map //創建一個不可變的Map val ages = Map("Leo" -> 30,"Jen" ->25,"Jack" ->23) ages("Leo") =31 /

scala程序開發入門

blank nbsp sha http 交流群 學習 shang 開發入門 快速 scala程序開發入門,快速步入scala的門檻: 大數據學習交流群:217770236 scala程序開發入門

Docker小白入門實戰

one service pre var AI play 不知道 ping splay 環境:Centos 6.9 0.查看是否滿足安裝需求。 先檢查服務器環境,docker要求操作系統CentOS6以上,kernel 版本必須2.6.32-431或更高,即>=Cent

Docker小白入門到實戰系列【二】

nta 1.0 mkdir -p strong tails RR 註意 3.0 通過 1.安裝好Centos 7 2.關閉SELINUX sed -i ‘s#SELINUX=enforcing#SELINUX=disabled#g‘ /etc/selinux/configs

scala筆記-函式入門(3)

函式的定義與呼叫 在Scala中定義函式時,需要定義函式的函式名、引數、函式體。 我們的第一個函式如下所示: def sayHello(name: String, age: Int) = { if (age > 18) { printf("hi %s, you are

GAN學習指南 原理入門到製作生成Demo

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

scala隨筆之入門篇(一)

scala 簡介 Java 方言之一,java的方言除了scala還有kotlin、groovy、clojure等。 執行在JVM之上 scala和kotlin、groovy、clojure一樣是多正規化程式語言,支援函數語言程式設計 scala常用領域是併發程

Python入門教程 | 在不同的作業系統中安裝Python程式設計環境

Python是一種跨平臺的程式語言,這意味著它能夠執行在所有主要的作業系統中,那麼我們所熟知的作業系統包括:Windows、MacOs、 Linux。那麼今天要講的就是如何在每個作業系統中成功的安裝python. 一、在Windows系統中搭建Python程式設計環境 01.下

Scala程式設計快速入門

1.scala資料型別 上表中列出的資料型別都是物件,也就是說scala沒有java中的原生型別。在scala是可以對數字等基礎型別呼叫方法的。例如數字1可以調方法,使用1.方法名。scala擁有和java一樣的資料型別,和java的資料型別的記憶體佈局完全一致,精度也完全一致。

三個月教你入門人工智慧+深度學習精華實踐課程|深度學習視訊教程2018

課程特色: 規劃全面:涵蓋目前主流的深度學習領域,包括影象識別,影象檢測,自然語言處理,GAN,分散式訓練框架等等。掌握每 一項技能都能在從事該領域邁進一步。 重點突出:摒棄繁冗的數學證明,一切從實際出發,突出重點,短時間內掌握重點知識。 實戰演練:課程包含多

CoreML入門機器學習

人工智慧 && 機器學習 人工智慧就是讓機器擁有和人一樣的智慧,它不僅僅限於機器學習,它還包括其他部分,比如:自然語言處理、計算機視覺等。關係就如圖: 其中簡單提示一下: 機 器學習:資料預處理、線性迴歸、監督學習、加強學習等 Deep Learning:人工神經網路、主成分分析、線

Scala學習-快速入門

Scala 是一門多正規化的程式語言,設計初衷是實現可伸縮的語言、並整合面向物件程式設計和函數語言程式設計的各種特性。 學習Scala會讓人有開啟眼界的感覺,非常的優雅的寫法和與C/C++一些不同的思想。 1. 安裝Scala 非常的簡單,在ubuntu中

Scala開發快速入門》書籍出版

Apache Spark、Apache Kafka等基於Scala語言實現的框架在大資料生態圈內佔有舉足輕重的地位,它們一定程度上引領著大資料最前沿技術方向,另外Akka、Apache Gearpump等基於Scala語言實現的框架在大資料生態圈內也備受關注

Scala基礎學習入門

從技術上來講,scala程式並不是一個直譯器,你在命令列中輸入的內容被快速地編譯成位元組碼,然後這段位元組碼交給Java虛擬機器執行。 變數宣告: val val不能再繼續賦值, 鼓勵使用該命令方式 var 生命週期中可以被多次賦值 大多數程式並不需要那麼多var變數 scala中變數或函式的

scala開發快速入門 | 第一篇 入門

一、scala簡介Scala語言是集面向物件和麵向函式思想於一體的程式語言。特點:1)它的設計吸收借鑑了多種程式語言的思想,只有很少的特點是scala自己獨有的。2)其程式執行在JVM上,並且相容java程式,Scala可以呼叫java方法,訪問java欄位,繼承java類,

scala開發快速入門 | 第六篇 面向物件程式設計(上)

類的定義、變數的宣告初始化、private修飾變數、伴生類伴生物件 1)Class 關鍵字宣告一個類Person    2)類成員變數的宣告的時候必須初始化    3)編譯完後 通過位元組碼檔案發現 定義的變數都是private型別。    *    val 宣告的成

scala開發快速入門 | 第四篇 資料結構

一、集(Set)Scala中的集合分為可變的集合、不可變的集合。Set中的資料是無序的,不重複的。預設情況下,Scala使用的是不可變的集合,預設引用的是 scala.collection.immutable.Set。                    如果你想使用可變的

scala開發快速入門 | 第六篇 面向物件程式設計(下)

trait簡介在scala中並沒有提供java語言的interface關鍵字來定義介面,而是可以使用trait實現多重繼承,繼承的時候使用extends和with關鍵字。/*定義三個trait*/ trait TraitDemo01 { // 抽象方法定義 def say(

Scala程式設計快速入門系列(一)

目    錄 一、Scala概述 二、Scala資料型別 三、Scala函式 四、Scala集合 五、Scala伴生物件 六、Scala trait 七、Actor 八、隱式轉換與隱式引數 九、Scala JDBC 由於整理的篇幅較長,所以文章計劃分三次釋出。