scala 常用语法

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.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 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))
时间: 2024-11-06 03:52:39

scala 常用语法的相关文章

scala常用语法

Scala数组遍历, 语法结构: for (i <- 区间) val common = Array(1,2,3,4) // 遍历数组 for (i <- 0 until common.length)   println(i + " : " + common(i)) println("=======================") // 每两个元素一跳 for (i <- 0 until(common.length,2))   println(i

SQL常用语法大全

一.基础1.说明:创建数据库CREATE DATABASE database-name 2.说明:删除数据库drop database dbname3.说明:备份sql server--- 创建 备份数据的 deviceUSE masterEXEC sp_addumpdevice 'disk', 'testBack', 'c:\mssql7backup\MyNwind_1.dat'--- 开始 备份BACKUP DATABASE pubs TO testBack 4.说明:创建新表create

Emmet常用语法

Emmet常用语法1.输入!和html:5(不能大写),按下TAB 键,快速生成一个 HTML5 的标准文档初始结构. html:xt 生成 HTML4 过渡型 html:4s 生成 HTML4 严格型2.生成带有 id .class 的 HTML 标签 (1)Emmet 的语法有点类似 CSS 的语法,生成 id 为 aaa 的 div 标签,我们只需要编写下面指令:#aaaEmmet 默认的标签为 div ,如果我们不给出标签名称的话,默认就生成 div 标签. (2)如果编写一个 clas

Scala基础语法 (一)

如果你之前是一名 Java 程序员,并了解 Java 语言的基础知识,那么你能很快学会 Scala 的基础语法. Scala 与 Java 的最大区别是:Scala 语句末尾的分号 ; 是可选的. 我们可以认为 Scala 程序是对象的集合,通过调用彼此的方法来实现消息传递.接下来我们来理解下,类,对象,方法,实例变量的概念: 对象 - 对象有属性和行为.例如:一只狗的状属性有:颜色,名字,行为有:叫.跑.吃等.对象是一个类的实例. 类 - 类是对象的抽象,而对象是类的具体实例. 方法 - 方法

php正则表达式入门-常用语法格式

原文地址:http://www.jbxue.com/article/24467.html 分享下php正则表达式中的一些常用语法格式,用于匹配字母.数字等,个人感觉还不错. 语法格式:位于定界符"/"之间.较为常用的元字符包括: “+”, “*”,以及 “?”.其中, “+”元字符规定其前导字符必须在目标对象中连续出现一次或多次, “*”元字符规定其前导字符必须在目标对象中出现零次或连续多次, 而“?”元字符规定其前导对象必须在目标对象中连续出现零次或一次. /jim{2,6}/<

SQLServer2005 常用语法大全

SQL分类: DDL-数据定义语言(CREATE,ALTER,DROP,DECLARE) DML-数据操纵语言(SELECT,DELETE,UPDATE,INSERT) DCL-数据控制语言(GRANT,REVOKE,COMMIT,ROLLBACK) 首先,简要介绍基础语句: 1.说明:创建数据库 CREATE DATABASE database-name 2.说明:删除数据库 drop database dbname 3.说明:备份sql server --- 创建备份数据的 device U

ABAP 指針常用语法

1 .定义指針 :指針的定義主 要有以下語句 定義任意類型的指針,但是不具備欄位結構(僅僅是一個地址) FIELD-SYMBOLS <carrid> TYPE ANY. 參考數據庫表定義(這種指針是含有欄位結構的,參考內表同理) FIELD-SYMBOLS <sflight> TYPE sflight. FIELD-SYMBOLS <sflight> LIKE sflight. FIELD-SYMBOLS <sflight> LIKE LINE OF sfl

Oracle常用语法

1,case用法 SELECT CASE WHEN T.FLAG='0' THEN T.USERID WHEN T.FLAG='1' THEN T.ORGID ELSE NULL END AS '标识' FROM XTXMXX T 2,decode用法 --如果FLAG等于1,则转为USERID,如果为0,则转为ORGID,其他的为2 SELECT DECODE(T.FLAG,1,T.USERID,0,T.ORGID,2) FROM XTXMXX T ; 3,创建sequence -- Crea

T-SQL和MySQL的一些常用语法的区别

本文将主要列出MySQL与SqlServer的SQL语句的一些常用语法的不同之处,且以常用的存储过程的相关内容为主. 1. 标识符限定符 SqlServer [] MySql `` 2. 字符串相加 SqlServer 直接用 + MySql concat() 3. isnull() SqlServer isnull() MySql ifnull() 注意:MySql也有isnull()函数,但意义不一样 4. getdate() SqlServer getdate() MySql now()