Scala 常用语法

来源:互联网 发布:linux curl post 参数 编辑:程序博客网 时间:2024/06/06 01:43


Clojure首先是FP, 但是由于基于JVM, 所以不得已需要做出一些妥协, 包含一些OO的编程方式
Scala首先是OO, Java语法过于冗余, 一种比较平庸的语言, Scala首先做的是简化, 以更为简洁的方式来编写OO, 主要利用‘type inference’能推断出来的, 你就不用写, 但如果仅仅这样, 不如用python
所以Scala象其名字一样, “可伸展的语言”, 它是个大的集市, 它积极吸纳其他语言的优秀的特征, 最重要的就是FP, 你可以使用Scala来写OO, 但它推荐使用FP的方式来写Scala; 还包括Erlang里面的actor模型
所以Scala并不容易学, 因为比较繁杂

 

0

Scala interpreter

scala> 1 + 2res0: Int = 3scala> res0 * 3res1: Int = 9scala> println("Hello, world!")Hello, world!

 

代码段落

scala中;常常可以省略, 但如果一行有多条语句, 则必须加上

val s = "hello"; println(s)

如果一条语句, 要用多行写

x+ y

这样会当作2个语句, 两种方法解决,

(x+ y) //加括号

x +y +z   //把操作符写在前一行, 暗示这句没完

 

1 基本语法

基本类型

image

Rich wrappers, 为基本类型提供更多的操作

image

 

变量定义

val, 不可变变量, 常量, 适用于FP
var, 可变变量, 适用于OO

scala> val msg = "Hello, world!"msg: java.lang.String = Hello, world!scala> msg = "Goodbye cruel world!"<console>:5: error: reassignment to val msg = "Goodbye cruel world!"

 

函数定义

image

可以简写为, 返回值类型不需要写, 可以推断出, 只有一条语句, 所以{}可以省略

scala> def max2(x: Int, y: Int) = if (x > y) x else ymax2: (Int,Int)Int

简单的funciton, 返回值为Unit, 类似Void(区别在于void为无返回值, 而scala都有返回值, 只是返回的为Unit, ())

scala> def greet() = println("Hello, world!")greet: ()Unit

scala> greet() == ()Boolean = true

函数参数不可变

def add(b: Byte): Unit = {  b = 1 // This won’t compile, because b is a val  sum += b}

重复参数, 最后的*表示可变参数列表, 可传入多个string

scala> def echo(args: String*) = for (arg <- args) println(arg)

scala> echo("hello", "world!")helloworld!

 

Function literal

如何翻译...
Scala FP的基础, function作为first class, 以function literal的形式作为参数被传递

image

args.foreach((arg: String) => println(arg))args.foreach(arg => println(arg)) //省略类型args.foreach(println) //其实连参赛列表也可以省略

可以看到scala在省略代码量上可以说下足功夫, 只要能推断出来的你都可以不写, 这也是对于静态类型系统的一种形式的弥补

对于oo程序员, 可能比较难理解, 其实等于

for (arg <args)  println(arg)

 

控制结构

由于scala是偏向于FP的, 所以所有控制结构都有返回值, 这样便于FP编程

If, 可以返回值

val filename =    if (!args.isEmpty) args(0)    else "default.txt"

 

While, 在FP里面不推荐使用循环, 应该用递归,尽量避免

 

For, 没有python和clojure的好用或简洁

for (  file <- filesHere //generator,用于遍历,每次file都会被从新初始化  if file.isFile;  //过滤条件, 多个间需要用;  if file.getName.endsWith(".scala"); //第二个过滤  line <- fileLines(file)  //嵌套for  trimmed = line.trim  //Mid-stream variable bindings, val类型,类似clojure let  if trimmed.matches(pattern)) println(file +": "+ trimmed)
//for默认不会产生新的集合, 必须使用yielddef scalaFiles =  for {    file <filesHere    if file.getName.endsWith(".scala")  } yield file //yield产生新的集合,类似python

 

match, switch-case

可以返回值, FP风格, 这样只需要最后println一次
默认会break, 不需要每次自己加

val firstArg = if (!args.isEmpty) args(0) else ""val friend =  firstArg match {    case "salt" => "pepper"     case "chips" => "salsa"    case "eggs" => "bacon"    case _ => "huh?"    //default}println(friend)

 

2 数据结构

数组

可变的同类对象序列, 适用于OO场景
val greetStrings = new Array[String](3) //greetStrings为val, 但是内部的数组值是可变的greetStrings(0) = "Hello"  //scala用()而非[]greetStrings(1) = ", "greetStrings(2) = "world!\n"
for (i <- 0 to 2)  print(greetStrings(i))
 

Scala 操作符等价于方法, 所以任意方法都可以以操作符的形式使用

1 + 2 //(1).+(2), 在只有一个参数的情况下, 可以省略.和()

0 to 2 //(0).to(2)

greetStrings(0) //greetStrings.apply(0),这也是为什么scala使用(), 而非[]

greetStrings(0) = "Hello" //greetStrings.update(0, "Hello")

简化的array初始化
val numNames = Array("zero", "one", "two")  //Array.apply("zero", "one", "two")

List

相对于array, List为不可变对象序列, 适用于FP场景

val oneTwo = List(1, 2)val threeFour = List(3, 4)

val zeroOneTwo = 0 :: oneTwo //::val oneTwoThreeFour = oneTwo ::: threeFour


对于List最常用的操作符为::, cons, 把新的elem放到list最前端
:::, 两个list的合并

右操作数, ::

普通情况下, 都是左操作数, 比如, a * b  => a.*(b)
但是当方法名为:结尾时, 为右操作数
1 :: twoThree  => twoThree.::(1)

不支持append
原因是, 这个操作的耗时会随着list的长度变长而线性增长, 所以不支持, 只支持前端cons, 实在需要append可以考虑ListBuffer

image image 

Tuple

tuple和list一样是不可变的, 不同是, list中的elem必须是同一种类型, 但tuple中可以包含不同类型的elem

val pair = (99, "Luftballons") //自动推断出类型为,Tuple2[Int, String]println(pair._1)  //从1开始,而不是0,依照Haskell and ML的传统println(pair._2) //elem访问方式不同于list, 由于元组中elem类型不同

 

Set和Map

var jetSet = Set("Boeing", "Airbus")jetSet += "Lear"println(jetSet.contains("Cessna"))


val treasureMap = Map[Int, String]()treasureMap += (1 -> "Go to island.")treasureMap += (2 -> "Find big X on ground.")println(treasureMap(2))
val romanNumeral = Map(1 -> "I", 2 -> "II", 3 -> "III" ) //简写

 

Scala需要兼顾OO和FP, 所以需要提供mutable和immutable版本
这里默认是Immutable, 如果需要使用mutable版本, 需要在使用前显示的引用...

import scala.collection.mutable.Setimport scala.collection.mutable.Map

 

3 面向对象-OO

类和对象

相对于Java定义比较简单, 默认public

class ChecksumAccumulator {  private var sum = 0  def add(b: Byte): Unit = {    sum += b  }  def checksum(): Int = {    return ~(sum & 0xFF) + 1  }}

进一步简化, 去掉{}和return, 默认将最后一次计算的值返回
不写return是推荐的方式, 因为函数尽量不要有多个出口

class ChecksumAccumulator {  private var sum = 0  def add(b: Byte): Unit = sum += b  def checksum(): Int = ~(sum & 0xFF) + 1}

其实对于Unit(void), 即没有返回值, 对于FP而言, 就是该function只会产生side effect, 还有另外一种简写的方式

class ChecksumAccumulator {  private var sum = 0  def add(b: Byte) { sum += b } //对于Unit返回的, 另一种简写, 用{}来表示无返回, 所以前面的就不用写了  def checksum(): Int = ~(sum & 0xFF) + 1}

实例化

val acc = new ChecksumAccumulatorval csa = new ChecksumAccumulatoracc.sum = 3


image image

 

Singleton对象

Scala不能定义静态成员, 所以用Singleton对象来达到同样的目的

import scala.collection.mutable.Mapobject ChecksumAccumulator {       //用object代替class  private val cache = Map[String, Int]()  def calculate(s: String): Int =    if (cache.contains(s))      cache(s)    else {      val acc = new ChecksumAccumulator      for (c <s)        acc.add(c.toByte)      val cs = acc.checksum()      cache += (s -> cs)      cs    }}
image 

最常见的场景就是, 作为scala程序的入口,

To run a Scala program, you must supply the name of a standalone singleton object with a main method that takes one parameter(Array[String]), and has a result type of Unit.

import ChecksumAccumulator.calculate  object Summer {    def main(args: Array[String]) {      for (arg <args)        println(arg +": "+ calculate(arg))    }}

 

不可变对象

适用于FP场景的对象, 所以也叫做Functional Objects.
好处, 消除可变带来的复杂性, 可以放心的当参数传递, 多线程下使用啊...

下面以定义有理数类为例

class Rational(n: Int, d: Int)  //极简方式,没有类主体

和上面的定义比, 不需定义成员变量, 而只是通过参数, 因为根本没有定义成员变量, 所以无处可变.

class Rational(n: Int, d: Int) {  require(d != 0)    //Precondition, 如果require返回false会抛出IllegalArgumentException,阻止初始化  private val g = gcd(n.abs, d.abs)  val numer = n / g  //添加不可变成员字段,便于引用  val denom = d / g    def this(n: Int) = this(n, 1) //辅助构造函数    def + (that: Rational): Rational =  //定义操作符    new Rational(      numer * that.denom + that.numer * denom, denom * that.denom //也可以使用this.number引用成员    )  def + (i: Int): Rational =    //典型的成员函数重载    new Rational(numer + i * denom, denom)  override def toString = numer +"/"+ denom //override, 方法重载  private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)}

 

4 函数编程-FP

函数和闭包

成员函数, OO的方式

内部函数, 需要切分功能, 又不想污染外部的命名空间

First-class function, unnamed function literal
function象变量一样, 可以被赋值和当参数传递, 但在scala需要以function literal的形式, 在运行期的时候会实例化为函数值(function value)

scala> var increase = (x: Int) => x + 1scala> increase(10)

Partially applied functions

scala> def sum(a: Int, b: Int, c: Int) = a + b + cscala> val a = sum _  //用占位符代替整个参数列表scala> a(1, 2, 3)  //a.apply(1, 2, 3)res13: Int = 6scala> val b = sum(1, _: Int, 3) //partial function, 用占位符代替一个参数scala> b(2)res15: Int = 6

Closures

关于闭包的解释,
对于通常的function, (x: Int) => x + 1, 称为closed term
而对于(x: Int) => x + more, 称为open term
所以对于开放的, 必须在定义的时候对里面的自由变量more动态进行绑定, 所以上下文中必须要有对more的定义, 这种关闭open term过程产生了closure

scala> var more = 1scala> val addMore = (x: Int) => x + more  //产生闭包,绑定morescala> addMore(10)res19: Int = 11scala> more = 9999 scala> addMore(10)res21: Int = 10009  //可见闭包绑定的不是value,而是变量本身

刚看到有些惊讶, 去clojure里面试一下, 也是这样的, 绑定的变量本身, 闭包会取最新的值
当然一般不会这样使用闭包.

下面这个例子, 是较常用的case, 其中闭合了函数的参数
如何在闭包调用时, 可以访问到已经不存在的变量? 当产生闭包时, 编译器会将这个变量从堆栈放到堆里面, 所以函数结束后还能访问

def makeIncreaser(more: Int) = (x: Int) => x + more

scala> val inc1 = makeIncreaser(1)scala> val inc9999 = makeIncreaser(9999)scala> inc1(10)res24: Int = 11scala> inc9999(10)res25: Int = 10009

 

Currying

先提高sum的两个版本的比较,

scala> def plainOldSum(x: Int, y: Int) = x + yscala> plainOldSum(1, 2)res4: Int = 3scala> def curriedSum(x: Int)(y: Int) = x + y  //currying版本的sumcurriedSum: (Int)(Int)Intscala> curriedSum(1)(2)res5: Int = 3

其实currying, 等同于调用两次function, first会返回第二个函数的函数值, 其中closure了x

scala> def first(x: Int) = (y: Int) => x + yfirst: (Int)(Int) => Int

取出函数值, 效果是减少了参数个数, 第一个函数的参数已经closure在第二个函数中了, 和partially有些类似(区别)

scala> val onePlus = curriedSum(1)_onePlus: (Int) => Int = <function>scala> onePlus(2)res7: Int = 3

有什么用? 用于创建更像built-in的控制结构
如下, 使用{}更像built-in, 但{}有个限制是, 只有单个参数的参数列表可以用{}替换(), 所以这个时候需要用currying来降低参赛个数

scala> println("Hello, world!")    //象方法调用scala> println { "Hello, world!" }  //更像built-in的控制结构,比如if

对于FP, 相对于OO使用继承和多态, 使用函数作为参数来实现代码重用, 希望可以将函数值放在{}, 显得更象built-in
比如下面, 每次打开文件, 操作, 关闭文件, 固定模式, 所以实现withPrintWriter, 每次传入不同的op就可以进行不同的操作, 而不用考虑文件开关
如果是oo实现, 就需要传入基类对象, 利用多态实现, 明显使用函数更轻量级一些

def withPrintWriter(file: File, op: PrintWriter => Unit) {  val writer = new PrintWriter(file)  try {    op(writer)  } finally {    writer.close()  }}//以调用方法的方式使用withPrintWriter(  new File("date.txt"),  writer => writer.println(new java.util.Date))

通过currying减少了参数, 所以就可以使用{}

def withPrintWriter(file: File)(op: PrintWriter => Unit) {......} //currying版本val file = new File("date.txt")withPrintWriter(file) { writer => writer.println(new java.util.Date) } //将函数值放在{}, 很像built-in


 

尾递归,Tail recursion

前面说了, FP尽量避免使用循环, 而应该使用递归
但是递归效率有问题, 不停的压栈, 也很容易爆堆栈
所以对于某种递归, 尾递归, 编译器会自动优化成循环执行, 避免多次使用堆栈
局限是, 不是什么情况都能写成尾递归, 其实只有循环可以...
比clojuer好, 编译器会自动进行优化

//while, 循环版本,oodef approximateLoop(initialGuess: Double): Double = {  var guess = initialGuess  while (!isGoodEnough(guess))    guess = improve(guess)  guess}//递归版本,FPdef approximate(guess: Double): Double =  if (isGoodEnough(guess)) guess  else approximate(improve(guess))
0 0
原创粉丝点击