Scala教程之:函数式的Scala


Scala是一门函数式语言,接下来我们会讲一下几个概念:

  • 高阶函数
  • 方法嵌套
  • 多参数列表
  • 样例类
  • 模式匹配
  • 单例对象
  • 正则表达式模式
  • For表达式

高阶函数

高阶函数通常来讲就是函数的函数,也就是说函数的输出参数是函数或者函数的返回结果是函数。在Scala中函数是一等公民。

我们看一下Scala集合类(collections)的高阶函数map:

val salaries = Seq(20000,70000,40000)
val doubleSalary = (x: Int) => x * 2
val newSalaries = salaries.map(doubleSalary) // List(40000,140000,80000)

map接收一个函数为参数。所以map是一个高阶函数,map也可直接接收一个匿名函数,如下所示:

val salaries = Seq(20000,40000)
val newSalaries = salaries.map(x => x * 2) // List(40000,80000)

在上面的例子中,我们并没有显示使用x:Int的形式,这是因为编译器可以通过类型推断推断出x的类型,对其更简化的形式是:

val salaries = Seq(20000,40000)
val newSalaries = salaries.map(_ * 2)

既然Scala编译器已经知道了参数的类型(一个单独的Int),你可以只给出函数的右半部分,不过需要使用_代替参数名(在上一个例子中是x)

强制转换方法为函数

如果你传入一个方法到高阶函数中,scala会将该方法强制转换成函数,如下所示:

case class WeeklyWeatherForecast(temperatures: Seq[Double]) {

  private def convertCtoF(temp: Double) = temp * 1.8 + 32

  def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF
}

在这个例子中,方法convertCtoF被传入forecastInFahrenheit。这是可以的,因为编译器强制将方法convertCtoF转成了函数x => convertCtoF(x) (注: x是编译器生成的变量名,保证在其作用域是唯一的)。

方法嵌套

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

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

程序输出为:

Factorial of 2: 2
Factorial of 3: 6

多参数列表

Scala和java不同的是他可以定义多个参数列表,下面是一个例子:

def foldLeft[B](z: B)(op: (B,A) => B): B

可以看到该方法定义了两个参数列表, z是初始值,op是一个二元运算,下面是它的一个调用:

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

利用scala的类型推断,我们可以让代码更加简洁:

numbers.foldLeft(0)(_ + _)

样例类

case class主要用于不可变的数据。他们和普通类几乎是一样的。

case class Book(isbn: String)

val frankenstein = Book("978-0486282114")

实例化案例类的时候并不需要new关键字,因为case class有一个默认的apply方法来负责对象的创建。

在case class中,参数是public并且val的,这意味着case class的参数不可变:

case class Message(sender: String,recipient: String,body: String)
val message1 = Message("guillaume@quebec.ca","jorge@catalonia.es","Ça va ?")

println(message1.sender)  // prints guillaume@quebec.ca
message1.sender = "travis@washington.us"  // this line does not compile

这里message1.sender不能被重新赋值,因为他是val(不可变)的。

比较

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

case class Message(sender: String,body: String)

val message2 = Message("jorge@catalonia.es","guillaume@quebec.ca","Com va?")
val message3 = Message("jorge@catalonia.es","Com va?")
val messagesAreTheSame = message2 == message3  // true

虽然上面是不同的对象,但是因为他们的值相同,所以最后的比较是true。

拷贝

可以使用copy来做case class的浅拷贝。

case class Message(sender: String,body: String)
val message4 = Message("julien@bretagne.fr","travis@washington.us","Me zo o komz gant ma amezeg")
val message5 = message4.copy(sender = message4.recipient,recipient = "claire@bourgogne.fr")
message5.sender  // travis@washington.us
message5.recipient // claire@bourgogne.fr
message5.body  // "Me zo o komz gant ma amezeg"

模式匹配

scala中使用match关键字和case来做模式匹配,类似java中的switch。

下面是一个简单的模式匹配的例子:

import scala.util.Random

val x: Int = Random.nextInt(10)

x match {
  case 0 => "zero"
  case 1 => "one"
  case 2 => "two"
  case _ => "other"
}

最后一个case _表示匹配其余所有情况。

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

def matchTest(x: Int): String = x match {
  case 1 => "one"
  case 2 => "two"
  case _ => "other"
}
matchTest(3)  // other
matchTest(1)  // one

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

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))  // prints You got an SMS from 12345! Message: Are you there?

println(showNotification(someVoiceRecording))  // you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123

case后面还可以加if语句,我们称之为模式守卫。

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) // nothing special,delegate to our original showNotification function
  }
}

也可以只做类型匹配:

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
}

密封类

特质(trait)和类(class)可以用sealed标记为密封的,这意味着其所有子类都必须与之定义在相同文件中。

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

单例对象

单例对象是一种特殊的类,可以使用关键字object来表示。单例对象是延时创建的,只有当他被第一次使用的时候才会创建。

package logging

object Logger {
  def info(message: String): Unit = println(s"INFO: $message")
}

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

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")  // Prints "INFO: Created projects"
}

伴生对象

伴生对象是指与某个类名相同的单例对象,类和它的伴生对象可以互相访问其私有成员。下面是一个伴生对象的例子:

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

伴生对象circle1可以访问类中定义的area.

注意:类和它的伴生对象必须定义在同一个源文件里。

正则表达式模式

在Scala中,可以使用.r方法将任意字符串变成一个正则表达式。如下所示:

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

你还可以使用括号来同时匹配多组正则表达式。

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 <- keyValPattern.findAllMatchIn(input))
  println(s"key: ${patternMatch.group(1)} value: ${patternMatch.group(2)}")

For表达式

在Scala中for循环是和yield一起使用的,他的形式是for (enumerators) yield e。 此处 enumerators 指一组以分号分隔的枚举器。这里的enumerator 要么是一个产生新变量的生成器,要么是一个过滤器。for 表达式在枚举器产生的每一次绑定中都会计算 e 值,并在循环结束后返回这些值组成的序列。 如下所示:

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 <- userBase if (user.age >=20 && user.age < 30))
  yield user.name  // i.e. add this to a list

twentySomethings.foreach(name => println(name))  // prints Travis Dennis

下面是一个更加复杂的例子:

def foo(n: Int,v: Int) =
   for (i <- 0 until n;
        j <- i until n if i + j == v)
   yield (i,j)

foo(10,10) foreach {
  case (i,j) =>
    println(s"($i,$j) ")  // prints (1,9) (2,8) (3,7) (4,6) (5,5)
}

你可以在使用 for 表达式时省略 yield 语句。此时会返回 Unit。

更多教程请参考 flydean的博客

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


共收录Twitter的14款开源软件,第1页Twitter的Emoji表情 TwemojiTwemoji是Twitter开源的其完整的Emoji表情图片。开发者可以去GitHub下载完整的表情库,并把这些表情加入到自己的应用或网页中。使用示例:var i = 0;twemoji.parse(  ’emoji, m\u276
Java和Scala中关于==的区别Java:==比较两个变量本身的值,即两个对象在内存中的首地址;equals比较字符串中所包含的内容是否相同。publicstaticvoidmain(String[]args){​ Strings1="abc"; Strings2=newString("abc");​ System.out.println(s1==s2)
本篇内容主要讲解“Scala怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Scala怎么使用”吧!语法scala...
这篇文章主要介绍“Scala是一种什么语言”,在日常操作中,相信很多人在Scala是一种什么语言问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,
这篇文章主要介绍“Scala Trait怎么使用”,在日常操作中,相信很多人在Scala Trait怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,
这篇文章主要介绍“Scala类型检查与模式匹配怎么使用”,在日常操作中,相信很多人在Scala类型检查与模式匹配怎么使用问题上存在疑惑,小编查阅了各式资料,整理...
这篇文章主要介绍“scala中常用但不常见的符号有哪些”,在日常操作中,相信很多人在scala中常用但不常见的符号有哪些问题上存在疑惑,小编查阅了各式资料,整理...
本篇内容主要讲解“Scala基础知识有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Scala基础知识有哪些”...
本篇内容介绍了“scala基础知识点有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧...
本篇内容介绍了“Scala下划线怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧...
本篇内容主要讲解“Scala提取器怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Scala提取器怎么使用”...
这篇文章主要讲解了“Scala基础语法有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Scala基础语法有...
本篇内容主要讲解“Scala方法与函数怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Scala方法与函数怎...
这篇文章主要讲解了“scala条件控制与循环怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“scala条...
这篇文章主要介绍“scala函数怎么定义和调用”,在日常操作中,相信很多人在scala函数怎么定义和调用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操
这篇文章主要介绍“scala如何声明变量”,在日常操作中,相信很多人在scala如何声明变量问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对...
这篇文章主要讲解了“scala的Map和Tuple怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“scala的Ma...
这篇文章主要介绍“scala的隐式参数有什么作用”,在日常操作中,相信很多人在scala的隐式参数有什么作用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的...
本篇内容主要讲解“Scala怎么进行文件写操作”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Scala怎么进行文件...
这篇文章主要讲解了“Scala怎么声明数组”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Scala怎么声明数组...