码迷,mamicode.com
首页 > 其他好文 > 详细

scala 常用语法

时间:2015-03-05 18:39:11      阅读:193      评论:0      收藏:0      [点我收藏+]

标签:

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 + 2
res0: Int = 3

scala> res0 * 3
res1: Int = 9

scala> println("Hello, world!")
Hello, world!

 

代码段落

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

val s = "hello"; println(s)

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

x
+ y

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

(x
+ y) //加括号

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

 

1 基本语法

基本类型

技术分享

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

技术分享

 

变量定义

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

 

函数定义

技术分享

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

scala> def max2(x: Int, y: Int) = if (x > y) x else y
max2: (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!")
hello
world!

 

Function literal

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

技术分享

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默认不会产生新的集合, 必须使用yield
def 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

技术分享 技术分享 

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.Set
import 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 ChecksumAccumulator
val csa = new ChecksumAccumulator
acc.sum = 3


技术分享 技术分享

 

Singleton对象

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

import scala.collection.mutable.Map
object 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
    }
}
技术分享

最常见的场景就是, 作为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 + 1
scala> increase(10)

Partially applied functions

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

scala> 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 = 1
scala> val addMore = (x: Int) => x + more  //产生闭包,绑定more
scala> addMore(10)
res19: Int = 11
scala> 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 = 11
scala> inc9999(10)
res25: Int = 10009

 

Currying

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

scala> def plainOldSum(x: Int, y: Int) = x + y
scala> plainOldSum(1, 2)
res4: Int = 3

scala> def curriedSum(x: Int)(y: Int) = x + y  //currying版本的sum
curriedSum: (Int)(Int)Int
scala> curriedSum(1)(2)
res5: Int = 3

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

scala> def first(x: Int) = (y: Int) => x + y
first: (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, 循环版本,oo
def approximateLoop(initialGuess: Double): Double = {
  var guess = initialGuess
  while (!isGoodEnough(guess))
    guess = improve(guess)
  guess
}

//递归版本,FP
def approximate(guess: Double): Double =
  if (isGoodEnough(guess)) guess
  else approximate(improve(guess))

scala 常用语法

标签:

原文地址:http://www.cnblogs.com/xiongjianjunjava/p/4316132.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!