scala 30分钟极简入门

    #scala

    本文介绍下 scala 的入门知识,主要面向对象是像我这样没有任何 Java 经验的 C++ 程序员。

    1. Why?

    我的初衷就是想深入学习函数式编程的思想、可以写 Spark.

    跟最开始拿 go 写raft一样,还是抱着了解基础,随用随学的原则,因此这篇笔记会不断的更新。但是仍然保持极简的风格,希望整体能控制在 30 分钟以内。

    2. 环境

    需要下载 Java 及 scala 的安装包,网上教程很多,不再赘述,具体见参考资料1。

    2.1 REPL

    执行scala或者sbt console都可以到 scala 的交互界面(Read-Eval-Print Loop):

    scala> println("Hello World" substring(0, 3) toUpperCase() indexOf "E")
    1
    

    Eclipse 工程里新建 Scala Worksheet 的交互功能更完善一些。

    2.2 scala 文件

    新建文件 Hello.scala

    object Hello extends App {
      println("Hello, " + args(0) + "!")
    }
    

    执行以下命令:

    $ scalac Hello.scala
    $ scala Hello izualzhy
    Hello, izualzhy!
    

    或者带main的文件也可以,例如另外新建一个文件 Hi.scala

    object Hi {
      def main(args: Array[String]) {
        println("Hi, " + args(0) + "!")
      }
    }
    

    在 Eclipse 里新建工程和 scala 文件的方式,效率更高。

    因此,环境上推荐 Eclipse 的 IDE 来学习 scala。

    3. 基础类型

    跟 C++ 类似,有以下几种基础类型:

    Byte Short Int Long Float Double Char String Boolean
    

    你可以直接通过println打印:

    println(1, " I am a String", true, 2.3)         //> (1, I am a String,true,2.3)
    

    另外,特殊一点的:

    Unit类似于 void,在函数返回值时使用。

    Null Nothing Any AnyRef待补充

    4. 定义变量

    scala 支持自动推导,例如我们可以直接指定变量的值而不用指定类型:

    // var VariableName : DataType [=  Initial Value]
    scala> val i = 1
    i: Int = 1
    
    scala> val f = 1.1
    f: Double = 1.1
    
    scala> val s = "Welcome to scala's world."
    s: String = Welcome to scala's world.
    

    当然也可以手动指定:

    scala> val i:Double = 1
    i: Double = 1.0
    

    其中 val 表示不可变变量,初期可以理解为 C++ 的 const,定义后不可以修改,如果尝试修改则会报错:

    scala> i = 2
    <console>:12: error: reassignment to val
    

    定义可变变量使用 var,支持修改。

    5. if else

    if (expr) {
       s1
    } else {
       s2
    }
    

    例如:

      val i = 1                                 //> i  : Int = 1
      if (i == 1) {
          println("i is 1")
      } else {
          println("i is not 1")
      }                                         //> i is 1
    

    当然其实可以写的更简洁一些:

      val i = 1                                       //> i  : Int = 1
      if (i == 1) println("i is 1") else println("i is not 1")
                                                      //> i is 1
    

    也就是这种形式:

    if (expr) s1 else s2
    

    6. for && while

    for( var x <- Range ) {
        statement(s);
    }
    
    while(condition) {
        statement(s);
    }
    

    例如,我们定义一个列表,然后通过 for 遍历:

    val l = List("Baidu", "Google", "Facebook")     //> l  : List[String] = List(Baidu, Google, Facebook)
    for (
        s <- l
    ) println(s)                                    //> Baidu
                                                      //| Google
                                                      //| Facebook
    

    也可以在 for 里加入 if 判断

    for (
        s <- l
        if (s.length > 5)
    ) println(s)                                    //> Google
                                                      //| Facebook
    

    如果要打印[1, 10]所有的偶数:

    for (s <- 1 to 10 if (s % 2 ==0)) println(s)
                                                    //> 2
                                                    //| 4
                                                    //| 6
                                                    //| 8
                                                    //| 10
    

    也可以使用yield返回一个新的 List:

        val l = List("Baidu", "Google", "Facebook")     //> l  : List[String] = List(Baidu, Google, Facebook)
    
        var result_for = for {
            s <- l
            s1 = s.toUpperCase()
            if (s1 != "")
        } yield(s1)  //> result_for  : List[String] = List(BAIDU, GOOGLE, FACEBOOK)
    

    7. 函数

    7.1. 基本概念

    函数式编程里一直在说函数是一等公民,主要体现在支持:

    1. 把函数作为实参传递给另一个函数
    2. 把函数作为返回值
    3. 把函数赋值给变量
    4. 把函数存储在数据结构里

    其实 C++ 大部分也可以支持,但是使用的方便性上,跟函数式编程语言原生支持还是差了很多。

    函数的定义方式:

    def functionName(param: ParamType): ReturnType = {
        //function body: expressions
    }
    

    例如:

    def Hello(name: String): String = {
        s"My name is ${name}"
    }                        //> Hello: (name: String)String
    Hello("izualzhy")        //> res0: String = My name is izualzhy
    

    当然,也可以直接省略大括号

    def newHello(name: String): String = s"My name is ${name}"
                            //> newHello: (name: String)String
    newHello("izualzhy")    //> res1: String = My name is izualzhy
    

    7.2. 匿名函数

    Hello这个函数,也可以使用更简洁的方式定义在一行,这种形式称为匿名函数:

    // Anonymous Function:(形参列表) => {函数体}
    def simpleHello = (name: String) => s"My name is ${name}"
                             //> simpleHello: => String => String
    simpleHello("izualzhy")  //> res1: String = My name is izualzhy
    

    匿名函数除了定义更加简洁,还可以赋值给其他变量:

    val func = simpleHello
    

    感觉叫匿名函数不符合 C++ 的习惯(anonymous namespace)

    7.3. Evaluation Strategy

    scala 解析函数参数时有两种方式:

    1. Call By Value:形如def foo(x: Int) = x,x 只在函数入口时计算一次。
    2. Call By Name:形如def foo(x: => Int) = x,x 在函数每次具体使用时计算。

    其中 1 符合我们平时的理解,因此介绍下 2:

    def now_time() = System.nanoTime          //> now_time: ()Long
    now_time()                                //> res5: Long = 289060277656521
    
    def foo(t: => Long) = {
        println("first:", t)
        println("second:", t)
    }                                         //> foo: (t: => Long)Unit
    foo(now_time())                           //> (first:,289060278984412)
                                              //| (second:,289060279131130)
    

    可以看到在同一个函数内部,t 的结果发生了变化,这就是 => 产生的神奇效果。

    7.4. 柯里化函数(Curried Function)

    柯里化的作用就是将一个多参数的函数转为单参数的多个函数定义,例如:

    def curried_add(x:Int)(y:Int) = x + y     //> curried_add: (x: Int)(y: Int)Int
    curried_add(100)(11)                      //> res11: Int = 111
    val add_100 = curried_add(100)_           //> add_100  : Int => Int = test$$$Lambda$30/559670971@4439f31e
    add_100(11)                               //> res12: Int = 111
    

    类似于 C++ 的偏特化?

    7.5. 高阶函数

    用函数作为形参或者返回值的函数,称为高阶函数

    def two_elements(f: (Int, Int) => Int) {
        println(f(4, 4))
    }                                 //> two_elements: (f: (Int, Int) => Int)Unit
    two_elements(add)                 //> 8
    

    7.6. 递归函数

    def factorial(n: Int): Int = {
        if (n <= 0) 1
        else n * factorial(n - 1)
    }                                 //> factorial: (n: Int)Int
    println(factorial(10))            //> 3628800
    

    8. 集合

    类似 C++ stl 的容器:vector/list/map/set 等,当然要更加丰富一些。按照使用的场景,分为可变和不可变集合两种。分别位于scala.collection.mutable scala.collection.immutable,默认是immutable的,因此仅介绍下 immutable 的集合。

    8.1. immutable

    8.1.1. List

    定义或者修改一个链表

    //自动推导为T=Int
    scala> val a = List(1, 2, 3, 4)
    a: List[Int] = List(1, 2, 3, 4)
    
    //::连接操作符
    scala> val b = 0 :: a
    b: List[Int] = List(0, 1, 2, 3, 4)
    scala> val c = "x"::"y"::"z"::Nil
    c: List[String] = List(x, y, z)
    
    //连接时将List作为一个元素,还是连接List内的元素
    scala> a::c
    res0: List[Object] = List(List(1, 2, 3, 4), x, y, z)
    scala> a:::c
    res1: List[Any] = List(1, 2, 3, 4, x, y, z)
    

    List基础接口有head tail isEmpty,注意tail返回的不是最后一个元素,而是除了head外所有的元素,这个设计有利于递归的实现。

    例如:

        def walk_through(l: List[Int]): String = {
            if (l.isEmpty) ""
            else l.head.toString + " " + walk_through(l.tail)
            }                        //> walk_through: (l: List[Int])String
        val a = List(1, 2, 3)        //> a  : List[Int] = List(1, 2, 3)
        walk_through(a)              //> res14: String = "1 2 3 "
    

    此外还有各类修改的接口:

    // filter 函数返回值为true则保留元素,例如取奇数
    scala> a.filter(x => x % 2 == 1)
    res4: List[Int] = List(1, 3)
    // _ 可以用来表示任意值,这里用于简化输入的函数形式
    scala> a.filter(_ % 2 == 1)
    res12: List[Int] = List(1, 3)
    
    // String to List
    scala> "99 Red Balloons".toList
    res5: List[Char] = List(9, 9,  , R, e, d,  , B, a, l, l, o, o, n, s)
    
    scala> res5.filter(x => Character.isDigit(x))
    res6: List[Char] = List(9, 9)
    
    // takeWhile 函数返回值为true时则停止,否则一直取元素
    scala> res5.takeWhile(x => x != 'e')
    res7: List[Char] = List(9, 9,  , R)
    
    // map 对每个元素执行函数转化为新的元素值
    scala> val c = List("x", "y", "z")
    c: List[String] = List(x, y, z)
    scala> c.map(_.toUpperCase)
    res10: List[String] = List(X, Y, Z)
    
    // filter map 混用
    scala> a.filter(_ % 2 == 1).map(_ + 100)
    res13: List[Int] = List(101, 103)
    
    scala> val q = List(List(1, 2, 3), List(4, 5, 6))
    q: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6))
    scala> q.map(_.filter(_ % 2 == 0))
    res15: List[List[Int]] = List(List(2), List(4, 6))
    
    // flatMap 会把两层转为一层 map
    scala> q.flatMap(_.filter(_ % 2 == 0))
    res18: List[Int] = List(2, 4, 6)
    
    // reduceLeft 用于规约所有元素到一个值
    scala> val a = List(1, 2, 3)
    a: List[Int] = List(1, 2, 3)
    scala> a.reduceLeft((x, y) => x + y)
    res0: Int = 6
    scala> a.reduceLeft(_ + _)
    res1: Int = 6
    
    // foldLeft 与 reduceLeft 类似,支持传入初始值
    scala> a.foldLeft(0)(_ + _)
    res3: Int = 6
    scala> a.foldLeft(2)(_ + _)
    res4: Int = 8
    

    8.1.2. Range

    Range 在前面介绍 for 循环时已经提过:

    scala> 1 to 10
    res5: scala.collection.immutable.Range.Inclusive = Range 1 to 10
    
    // 也可以通过 by 指定 step
    scala> 1 to 10 by 2
    res6: scala.collection.immutable.Range = inexact Range 1 to 10 by 2
    scala> (1 to 10 by 2).toList
    res6: List[Int] = List(1, 3, 5, 7, 9)
    
    // toList转化为Llist类型
    scala> (1 to 10).toList
    res0: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    
    // until 与 to 的区别就是字面意思
    scala> 1 until 10
    res1: scala.collection.immutable.Range = Range 1 until 10
    scala> (1 until 10).toList
    res3: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
    

    8.1.3. Stream

    Stream is a lazy list.

    scala> 1 #:: 2 #:: 3 #:: Stream.empty
    res4: scala.collection.immutable.Stream[Int] = Stream(1, ?)
    scala> val stream = (1 to 100000000).toStream
    stream: scala.collection.immutable.Stream[Int] = Stream(1, ?)
    scala> stream.head
    res5: Int = 1
    
    scala> stream.tail
    res6: scala.collection.immutable.Stream[Int] = Stream(2, ?)
    

    ?表示一个 lazy 值,只有取值时才会计算。

    8.1.4. Tuple

    Tuple 就是多个元素的集合

    scala> (1, "Alice", "Math", 147)
    res10: (Int, String, String, Int) = (1,Alice,Math,147)
    

    _下标值用于取值:

    scala> res10._3
    res13: String = Math
    

    两个元素又称为 pair,可以用于Map(接下来介绍)

    scala> (1, 2)
    res8: (Int, Int) = (1,2)
    // 可以使用 -> 简写
    scala> 1 -> 2
    res9: (Int, Int) = (1,2)
    

    结合List我们看个例子:

        val a = List(1, 2, 3)
        def sumSq(in: List[Int]): (Int, Int, Int) = {
            in.foldLeft((0, 0, 0))((t, v) => (t._1 + 1, t._2 + v, t._3 + v * v))
        }                           //> sumSq: (in: List[Int])(Int, Int, Int)
        sumSq(a)                    //> res13: (Int, Int, Int) = (3,6,14)
    

    foldLeft的初始值传入一个 Tuple,包含 3 个元素,分别作为 元素个数,元素和,元素平方和的初始值。

    8.1.5. Map

    Map[K, V]由多个 pair 组成

    // 构造
    scala> val p = Map(1 -> "David", 9 -> "Elwood")
    p: scala.collection.immutable.Map[Int,String] = Map(1 -> David, 9 -> Elwood)
    scala> val p2 = Map((1, "David"), (9, "Elwood"))
    p2: scala.collection.immutable.Map[Int,String] = Map(1 -> David, 9 -> Elwood)
    
    // 取value,注释不是[]而是()
    scala> p(1)
    res16: String = David
    scala> p(9)
    res17: String = Elwood
    
    // 是否包含指定的key
    scala> p.contains(1)
    res18: Boolean = true
    scala> p.contains(2)
    res19: Boolean = false
    
    // keys values
    scala> p.keys
    res20: Iterable[Int] = Set(1, 9)
    scala> p.keys.foreach(key => println("key:" + key))
    key:1
    key:9
    scala> p.values
    res21: Iterable[String] = MapLike.DefaultValuesIterable(David, Elwood)
    
    // 增加单个 pair
    scala> p + (8 -> "Archer")
    res22: scala.collection.immutable.Map[Int,String] = Map(1 -> David, 9 -> Elwood, 8 -> Archer)
    // 删除 key
    scala> p - 1
    res23: scala.collection.immutable.Map[Int,String] = Map(9 -> Elwood)
    // 增加多个 pair
    scala> p ++ List(2 -> "Alice", 5 -> "Bob")
    res24: scala.collection.immutable.Map[Int,String] = Map(1 -> David, 9 -> Elwood, 2 -> Alice, 5 -> Bob)
    // 删除多个 key
    scala> p -- List(1, 9, 2)
    res25: scala.collection.immutable.Map[Int,String] = Map()
    // 混合操作
    scala> p ++ List(2 -> "Alice", 5 -> "Bob") -- List(1, 9, 2)
    res26: scala.collection.immutable.Map[Int,String] = Map(5 -> Bob)
    

    篇幅所限,仅介绍一些常用的接口,更多的可以参考 scala 官方文档,例如List

    9. 例子

    快排:

    def qSort(in: List[Int]): List[Int] =
        if (in.length < 2) in
        else
            qSort(in.filter(_ < in.head)) ++ in.filter(_ == in.head) ++ qSort(in.filter(_ > in.head))
        //> qSort: (in: List[Int])List[Int]
    qSort(List(5, 2, 9, 8, 0, 1, 3, 6, 7, 4))
        //> res16: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
    

    是不是很简洁?

    参考资料

    1. 慕课网:Scala程序设计—基础篇
    2. 菜鸟教程:Scala教程