crazyjohn / crazyjohn.github.io

crazyjohn's blog
9 stars 3 forks source link

scala programming #5

Open serverBiatch opened 9 years ago

serverBiatch commented 9 years ago

scala

1. 函数定义

scala中函数使用以下的语法进行定义

2. 函数字面量(Function Literals)

这个词我每次看到都很困惑,我自己一直把它理解为匿名函数或者类lambda表达式之类的,我去google了一下,看到stackoverflow的解释如下:

A function literal is an alternate syntax for defining a function. It's useful for when you want to pass a function as an argument to a method (especially a higher-order one like a fold or a filter operation) but you don't want to define a separate function. Function literals are anonymous -- they don't have a name by default, but you can give them a name by binding them to a variable. A function literal is defined like so:

(a:Int, b:Int) => a + b
You can bind them to variables:

val add = (a:Int, b:Int) => a + b
add(1, 2) // Result is 3
Like I said before, function literals are useful for passing as arguments to higher-order functions. They're also useful for defining one-liners or helper functions nested within other functions.

A Tour of Scala gives a pretty good reference for function literals (they call them anonymous functions).

shareimprove this answer

整体表达的意思也跟我理解的差不多,也就是类似匿名函数,你可以很方便的把它作为高阶函数的参数,或者是把它赋值给某个变量。看码:

scala> (x: Int) => x + 1
res0: Int => Int = <function1>

scala> val increase = (x: Int) => x + 1
increase: Int => Int = <function1>

scala> increase(1)
res1: Int = 2

scala> increase(10)
res2: Int = 11

scala> increase = (x: Int) => x + 10
<console>:8: error: reassignment to val
       increase = (x: Int) => x + 10
                ^

scala> var add = (x: Int) => x + 10
add: Int => Int = <function1>

scala> add(10)
res3: Int = 20

scala> val someNumbers = List(-11, -10, -5, 0, 5, 10)
someNumbers: List[Int] = List(-11, -10, -5, 0, 5, 10)

scala> someNumbers.foreach((x: Int) => println(x))
-11
-10
-5
0
5
10

scala> someNumbers.filter(_ > 0)
res5: List[Int] = List(5, 10)

scala> someNumbers.filter((x: Int) => x > 0)
res6: List[Int] = List(5, 10)

赋值给变量的情况:

scala> val add = (a: Int, b: Int) => a + b
add: (Int, Int) => Int = <function2>

scala> add(10, 20)
res9: Int = 30

以上是运行于scala解释器的代码表现。

3. 可变参数

在scala中定义可变参数使用:类型*,看码:

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

scala> echo("hi", "john")
hi
john

scala> echo("hi", 2)
<console>:9: error: type mismatch;
 found   : Int(2)
 required: String
              echo("hi", 2)

4. 柯里化(curring)

维基百科的解释:

柯里化[编辑]
求值策略
及早求值
惰性求值
部分求值
远程求值
短路求值
在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。这个技术由Christopher Strachey以逻辑学家哈斯凯尔·加里命名的,尽管它是Moses Schönfinkel和戈特洛布·弗雷格发明的。

在直觉上,柯里化声称“如果你固定某些参数,你将得到接受余下参数的一个函数”。所以对于有两个变量的函数y^x,如果固定了y=2,则得到有一个变量的函数2^x。

在理论计算机科学中,柯里化提供了在简单的理论模型中,比如:只接受一个单一参数的lambda演算中,研究带有多个参数的函数的方式。

函数柯里化的对偶是Uncurrying,一种使用匿名单参数函数来实现多参数函数的方法。例如:

var foo = function(a) {
  return function(b) {
    return a * a + b * b;
  }
}
这样调用上述函数:(foo(3))(4),或直接foo(3)(4)。

5. 高度抽象的控制结构,借贷模式(loan pattern)

看码:

object FileUtils {

  def withPrintWriter(file: File, op: PrintWriter => Unit) {
    val writer = new PrintWriter(file)
    try {
      op(writer)
    } finally {
      writer.close()
    }
  }

  def main(args: Array[String]) {
    FileUtils.withPrintWriter(new File("data.txt"), writer => writer.println(new java.util.Date))
  }
}

借贷模式的好处是client代码不用关系太多的事情,骨架的代码由框架或者框架方法来做,上述就是打开和关闭资源了,client只关注自己的业务部分。

下面的代码对上头的进行了currying化:

object FileUtils {

  def withPrintWriter(file: File, op: PrintWriter => Unit) {
    val writer = new PrintWriter(file)
    try {
      op(writer)
    } finally {
      writer.close()
    }
  }

  /**
   * curring function, just like lang lib support;
   */
  def curriedWithPrintWriter(file: File)(op: PrintWriter => Unit) {
    val writer = new PrintWriter(file)
    try {
      op(writer)
    } finally {
      writer.close()
    }
  }

  def main(args: Array[String]) {
    FileUtils.curriedWithPrintWriter(new File("data.txt")) {
      writer => writer.println(new java.util.Date)
    }
  }
}

加上scala对只有一个参数的的话可以用大括号来包围参数来调用,代码就会从:

def main(args: Array[String]) {
    FileUtils.withPrintWriter(new File("data.txt"), writer => writer.println(new java.util.Date))
}

变成:

def main(args: Array[String]) {
    FileUtils.curriedWithPrintWriter(new File("data.txt")) {
      writer => writer.println(new java.util.Date)
    }
}

看起来更屌?

6. 传名参数(by-name parameter)

看码:

object ByNameFunction {

  var assertEnabled = true
  /**
   * First version;
   */
  def myAssert(predicate: () => Boolean) {
    if (assertEnabled && !predicate()) {
      throw new AssertionError
    }
  }

  /**
   * By name assert;
   */
  def byNameAssert(predicate: => Boolean) {
    if (assertEnabled && !predicate) {
      throw new AssertionError
    }
  }

  /**
   * Boolean assert;
   */
  def boolAssert(predicate: Boolean) {
    if (assertEnabled && !predicate) {
      throw new AssertionError
    }
  }

  def main(args: Array[String]) {
    // ugly calling
    myAssert(() => 5 > 3)
    // better calling
    byNameAssert(5 > 3)
    // bool assert
    boolAssert(5 > 3)
    // switch
    assertEnabled = false
    // work well
    byNameAssert(1 / 0 == 0)
    // error
    boolAssert(1 / 0 == 0)
  }
}

在没有传名参数的时候,外部的断言调用要写成这样:

// ugly calling
myAssert(() => 5 > 3)

有了传名调用,就可以写成这样:

// better calling
byNameAssert(5 > 3)

而且对比bool断言,传名的好处是在传入参数的时候只是生成了一个函数值,而bool断言传入的是一个bool值,所以要优先被调用,当断言被禁用的时候:

// switch
assertEnabled = false
// work well
byNameAssert(1 / 0 == 0)
// error
boolAssert(1 / 0 == 0)

bool断言会报错,但是传名不会。

7. 命名空间

scala的字段和方法在同一个命名空间,可以相互覆盖,看码:

trait Rectangular {
  // just declare
  def topLeft: Point
  def bottomRight: Point

  def left = topLeft.x
  def right = bottomRight.x

  def width = right - left
}

上头的特质里头有两个方法没有实现,所以在类混入这个特质的时候编译器会提示你去实现它:

class Rectangle(val topLeft: Point, val bottomRight: Point) extends Rectangular {

}

你只要通过添加相同名字的类参数的方式就可以解决这个问题。

8. 特质(trait)实现可堆叠的改变

待完成

9. 保护的作用域

10. 伴生对象的可见性

11. 模式匹配和样本类

样本类在scala中的实现就是在class关键字之前加上case关键字,看码:

abstract class Expr

case class Var(name: String) extends Expr

case class Number(num: Double) extends Expr

case class UnOp(opertor: String, arg: Expr) extends Expr

case class BinOp(operator: String, left: Expr, right: Expr) extends Expr

scala编译器自动为你的类添加一些语法上的便捷设定:

这样在java中的null引起的编程错误编程饿了scala里的类型错误,如果变量是Option[String]类型的,当你打算用作String的时候,scala就不会编译通过。

crazyjohn commented 9 years ago

关于java和scala的命名空间

  1. java为定义准备了4个命名空间,分别是包,类型,字段,方法。
  2. scala只有两个命名空间。值(字段,方法,包和单例对象),类型(类和特质名)。