Scala教程之:函數式的Scala

Scala是一門函數式語言,接下來我們會講一下幾個概念:

  • 高階函數
  • 方法嵌套
  • 多參數列表
  • 樣例類
  • 模式匹配
  • 單例對象
  • 正則表達式模式
  • For表達式

高階函數

高階函數通常來講就是函數的函數,也就是說函數的輸出參數是函數或者函數的返回結果是函數。在Scala中函數是一等公民。

我們看一下Scala集合類(collections)的高階函數map:

<code>

val

salaries = Seq(

20000

,

70000

,

40000

)

val

doubleSalary = (x:

Int

) => x *

2

val

newSalaries = salaries.map(doubleSalary) /<code>

map接收一個函數為參數。所以map是一個高階函數,map也可直接接收一個匿名函數,如下所示:

<code>val salaries = Se

q(20000, 70000, 40000)

val newSalaries = salaries.map(

x =>

x

*

2

) // List(

40000

,

140000

,

80000

) /<code>

在上面的例子中,我們並沒有顯示使用x:Int的形式,這是因為編譯器可以通過類型推斷推斷出x的類型,對其更簡化的形式是:

<code>val salaries = Se

q(20000, 70000, 40000)

val newSalaries = salaries.map(

_

*

2

) /<code>

既然Scala編譯器已經知道了參數的類型(一個單獨的Int),你可以只給出函數的右半部分,不過需要使用_代替參數名(在上一個例子中是x)

強制轉換方法為函數

如果你傳入一個方法到高階函數中,scala會將該方法強制轉換成函數,如下所示:

<code>case class WeeklyWeatherForecast(temperatures: Se

q[Double]

) { private def convertCtoF(temp: Double) = temp *

1.8

+

32

def forecastInFahrenheit: Se

q[Double]

= temperatures.map(convertCtoF) // /<code>

在這個例子中,方法convertCtoF被傳入forecastInFahrenheit。這是可以的,因為編譯器強制將方法convertCtoF轉成了函數x => convertCtoF(x) (注: x是編譯器生成的變量名,保證在其作用域是唯一的)。

方法嵌套

在Scala的方法中可以嵌套方法,如下所示:

<code> 

def

factorial

(x: Int)

:

Int = {

def

fact

(x: Int, accumulator: Int)

:

Int = {

if

(x <=

1

) accumulator

else

fact(x -

1

, x * accumulator) } fact(x,

1

) } println(

"Factorial of 2: "

+ factorial(

2

)) println(

"Factorial of 3: "

+ factorial(

3

)) /<code>

程序輸出為:

<code>

Factorial of 2:

2

Factorial of 3:

6

/<code>

多參數列表

Scala和java不同的是他可以定義多個參數列表,下面是一個例子:

<code>def foldLeft[

B

]()(op: (B, A) => B): B /<code>

可以看到該方法定義了兩個參數列表, z是初始值,op是一個二元運算,下面是它的一個調用:

<code>

val

numbers

=

List(1,

2

,

3

,

4

,

5

,

6

,

7

,

8

,

9

,

10

)

val

res

=

numbers.foldLeft(0)((m,

n)

=>

m

+

n)

print(res)

//

55

/<code>

利用scala的類型推斷,我們可以讓代碼更加簡潔:

<code>

numbers

.foldLeft

(

0

)(_ + _) /<code>

樣例類

case class主要用於不可變的數據。他們和普通類幾乎是一樣的。

<code>case 

class

Book

(isbn: String)

val

frankenstein = Book(

"978-0486282114"

) /<code>

實例化案例類的時候並不需要new關鍵字,因為case class有一個默認的apply方法來負責對象的創建。

在case class中,參數是public並且val的,這意味著case class的參數不可變:

<code>

case

class

Message(sender:

String

, recipient:

String

, body:

String

) val message1 = Message(

"[email protected]"

,

"[email protected]"

,

"Ça va ?"

) println(message1.sender) message1.sender =

"[email protected]"

/<code>

這裡message1.sender不能被重新賦值,因為他是val(不可變)的。

比較

case class的比較是按值比較的,而不是按引用:

<code>case 

class

Message

(sender: String, recipient: String, body: String)

val

message2 = Message(

"[email protected]"

,

"[email protected]"

,

"Com va?"

)

val

message3 = Message(

"[email protected]"

,

"[email protected]"

,

"Com va?"

)

val

messagesAreTheSame = message2 == message3 /<code>

雖然上面是不同的對象,但是因為他們的值相同,所以最後的比較是true。

拷貝

可以使用copy來做case class的淺拷貝。

<code>

case

class

Message(sender:

String

, recipient:

String

, body:

String

) val message4 = Message(

"[email protected]"

,

"[email protected]"

,

"Me zo o komz gant ma amezeg"

) val message5 = message4.copy(sender = message4.recipient, recipient =

"[email protected]"

) message5.sender message5.recipient message5.body /<code>

模式匹配

scala中使用match關鍵字和case來做模式匹配,類似java中的switch。

下面是一個簡單的模式匹配的例子:

<code>import scala.util.Random

val x: Int = Random.nextInt(

10

) x match {

case

0

=>

"zero"

case

1

=>

"one"

case

2

=>

"two"

case

_ =>

"other"

} /<code>

最後一個case _表示匹配其餘所有情況。

match表達式是有值的,如下所示:

<code>def matchTest(x: Int): 

String

= x match {

case

1

=>

"one"

case

2

=>

"two"

case

_

=>

"other"

} matchTest(

3

) matchTest(

1

) /<code>

case也可以匹配case class, 如下所示:

<code>

abstract

class

Notification

case

class

Email(sender:

String

, title:

String

, body:

String

)

extends

Notification

case

class

SMS(caller:

String

, message:

String

)

extends

Notification

case

class

VoiceRecording(contactName:

String

, link:

String

)

extends

Notification def showNotification(notification: Notification):

String

= { notification match {

case

Email(sender, title, _) => s

"You got an email from $sender with title: $title"

case

SMS(

number

, message) => s

"You got an SMS from $number! Message: $message"

case

VoiceRecording(name, link) => s

"you received a Voice Recording from $name! Click the link to hear it: $link"

} } val someSms = SMS(

"12345"

,

"Are you there?"

) val someVoiceRecording = VoiceRecording(

"Tom"

,

"voicerecording.org/id/123"

) println(showNotification(someSms)) println(showNotification(someVoiceRecording)) /<code>

case後面還可以加if語句,我們稱之為模式守衛。

<code>def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[

String

]):

String

= { notification match {

case

Email(sender, _, _)

if

importantPeopleInfo.contains(sender) =>

"You got an email from special someone!"

case

SMS(

number

, _)

if

importantPeopleInfo.contains(

number

) =>

"You got an SMS from special someone!"

case

other

=>

showNotification(other) } } /<code>

也可以只做類型匹配:

<code>

abstract

class

Device

case

class

Phone(model:

String

)

extends

Device { def screenOff =

"Turning screen off"

}

case

class

Computer(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 } /<code>

密封類

特質(trait)和類(class)可以用sealed標記為密封的,這意味著其所有子類都必須與之定義在相同文件中。

<code>sealed 

abstract

class

Furniture

case

class

Couch()

extends

Furniture

case

class

Chair()

extends

Furniture def findPlaceToSit(piece: Furniture):

String

= piece match {

case

a:

Couch

=>

"Lie on the couch"

case

b:

Chair

=>

"Sit on the chair"

} /<code>

單例對象

單例對象是一種特殊的類,可以使用關鍵字object來表示。單例對象是延時創建的,只有當他被第一次使用的時候才會創建。

<code>

package

logging

object

Logger { def info(message: String):

Unit

= println(s

"INFO:

$message

"

) } /<code>

單例對象的一個作用就是定義功能性方法,可以在任何地方被使用,如上例中的info方法。可以像如下的方式使用:

<code>

import

logging.Logger.info

class

Project

(name: String, daysToComplete:

Int

)

class

Test

{

val

project1 = new Project(

"TPS Reports"

,

1

)

val

project2 = new Project(

"Website redesign"

,

5

) info(

"Created projects"

) } /<code>

伴生對象

伴生對象是指與某個類名相同的單例對象,類和它的伴生對象可以互相訪問其私有成員。下面是一個伴生對象的例子:

<code>

import

scala.math._ case

class

Circle

(radius:

Double

) {

import

Circle._ def area:

Double

= calculateArea(radius) }

object

Circle {

private

def calculateArea(radius:

Double

):

Double

= Pi * pow(radius,

2.0

) }

val

circle1 = Circle(

5.0

) circle1.area /<code>

伴生對象circle1可以訪問類中定義的area.

注意:類和它的伴生對象必須定義在同一個源文件裡。

正則表達式模式

在Scala中,可以使用.r方法將任意字符串變成一個正則表達式。如下所示:

<code>

import

scala.util.matching.Regex val numberPattern:

Regex

=

"[0-9]"

.r numberPattern.findFirstMatchIn(

"awesomepassword"

) match {

case

Some

(

_

) =>

println

(

"Password OK"

)

case

None

=>

println

(

"Password must contain a number"

) } /<code>

你還可以使用括號來同時匹配多組正則表達式。

<code>

import

scala.util.matching.Regex val keyValPattern: Regex =

"([0-9a-zA-Z-#() ]+): ([0-9a-zA-Z-#() ]+)"

.r val input: String =

"""background-color: #A03300; |background-image: url(img/header100.png); |background-position: top center; |background-repeat: repeat-x; |background-size: 2160px 108px; |margin: 0; |height: 108px; |width: 100%;"""

.stripMargin

for

(patternMatch "key: ${patternMatch.group(1)} value: ${patternMatch.group(2)}") /<code>

For表達式

在Scala中for循環是和yield一起使用的,他的形式是for (enumerators) yield e。 此處 enumerators 指一組以分號分隔的枚舉器。這裡的enumerator 要麼是一個產生新變量的生成器,要麼是一個過濾器。for 表達式在枚舉器產生的每一次綁定中都會計算 e 值,並在循環結束後返回這些值組成的序列。 如下所示:

<code>

case

class

User(name:

String

, age: Int) val userBase = List(User(

"Travis"

,

28

), User(

"Kelly"

,

33

), User(

"Jennifer"

,

44

), User(

"Dennis"

,

23

)) val twentySomethings =

for

(user if (user.age >=

20

&& user.age

30

))

yield

user.name twentySomethings.foreach(

name

=>

println(name)) /<code>

下面是一個更加複雜的例子:

<code>

def

foo

(

n: Int, v: Int

)

=

for

(i 0 until n; j if i + j == v)

yield

(i, j) foo(

10

,

10

)

foreach

{

case

(i, j) => println(s

"($i, $j) "

) } /<code>

你可以在使用 for 表達式時省略 yield 語句。此時會返回 Unit。

歡迎關注我的公眾號:程序那些事,更多精彩等著您!

更多內容請訪問:flydean的博客 flydean.com


Scala教程之:函數式的Scala


分享到:


相關文章: