标签:
Clojure首先是FP, 但是由于基于JVM, 所以不得已需要做出一些妥协, 包含一些OO的编程方式
Scala首先是OO, Java语法过于冗余, 一种比较平庸的语言, Scala首先做的是简化, 以更为简洁的方式来编写OO, 主要利用‘type inference’能推断出来的, 你就不用写, 但如果仅仅这样, 不如用python
所以Scala象其名字一样, “可伸展的语言”, 它是个大的集市, 它积极吸纳其他语言的优秀的特征, 最重要的就是FP, 你可以使用Scala来写OO, 但它推荐使用FP的方式来写Scala; 还包括Erlang里面的actor模型
所以Scala并不容易学, 因为比较繁杂
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 //把操作符写在前一行, 暗示这句没完
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!
如何翻译...
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)
可变的同类对象序列, 适用于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")
相对于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和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类型不同
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
相对于Java定义比较简单, 默认public
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = {
sum += b
} def checksum(): Int = {
return ~(sum & 0xFF) + 1
}
}
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = sum += b
def checksum(): Int = ~(sum & 0xFF) + 1
}
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
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)
}
成员函数, 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
先提高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
前面说了, 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))
标签:
原文地址:http://www.cnblogs.com/xiongjianjunjava/p/4316132.html