zoukankan      html  css  js  c++  java
  • 理解Scala

    看到这里有几个有意思的 规则,转载于此:

    Read Eval Print Loop (REPL)

    REPL在Scala里面指的是直接运行scala.exe进入的交互式命令行模式。广义上讲,也泛指那些在线编程工具。

    核心规则1:请使用REPL来熟悉Scala语言。

    Scala的REPL有个好处是能够将我们输入的每行代码的内部表示反馈出来。比如:

    scala> def add(a:Int, b:Int):Int = a + b

    add: (a: Int, b: Int)Int

    我们定义一个函数,完成两个数的加法。Scala回显给我们的内容可以帮助我们写代码。

    表达式与语句

    表达式与语句的区别是:语句是用来执行的,而表达式是用来求值的。在程序员的世界里,表达式就是返回值,语言就是没有返回值执行程序。

    Scala是表达式导向的编程语言。但并不是100%成立,Scala代码中还是有控制语块,毕竟我们写程序就是为了控制各种实体为我们服务的。

    核心规则2:使用表达式,而不是语句。

    这条规则主要是帮助我们简化代码,就像前面加法的例子,a+b就是一个表达式。相比于我们C语言写的相同实现,简单不好。代码里面,像这样的例子肯定还是存在很多的。

    不要使用Return

    当我们使用表达式的时候,就不需要Return了。因为表达式本身就是用来求值的,我们必要再去显式地说我现在要返回什么。Scala编译器自动使用最后一个表达式的返回值作为函数的返回值。

    我们应该记得一个编程指导意见就是函数在同一个地方返回。如果我们现在没有Return语句了,像在Scala中,有没有类似的编程指导呢?看个例子:

    object NoReturn extends scala.App {
      def createErrorMessage1(errorCode : Int) : String = {
        val result = errorCode match {
          case 1 => "Network Failure"
          case 2 => "I/O Failure"
          case 3 => "Unknown Error"
        }
        return result
      }
      def createErrorMessage2(errorCode: Int) : String = {
        var result : String = null            // not val
        errorCode match {
          case 1 =>
            result = "Network Failure"
          case 2 =>
            result = "I/O Failure"
          case _ =>
            result = "Unknown Error"
        }
        return result;
      }
      def createErrorMessage3(errorCode : Int) : String = {
        errorCode match {
          case 1 => "Network Failure"
          case 2 => "I/O Failure"
          case 3 => "Unknown Error"
        }
      }
      println(createErrorMessage1(1))
      println(createErrorMessage2(2))
      println(createErrorMessage3(3))
      println(1 match{case 1 => "Network Failure" case 2 => 3})
      println(2 match{case 1 => "Network Failure" case 2 => 3})
    }

    createErrorMessage2应该是我们以往的写法。定义一个局部变量,然后匹配errorCode,对其进行赋值。createErrorMessage1是Scala推荐的写法(虽然还不够简洁),它使用的是val而不是var,来声明临时变量。val表示值,赋值后就不允许再更改;var是变量,可以重复赋值。createErrorMessage1的的result之后是一个表达式。求值之后直接就赋值了。createErrorMessage3就更加简洁了,差不多到了终极形态了。函数直接就返回一个表达式,少了临时对象。

    注:match case支持每个分支返回的类型不同。这个特性在函数式编程中非常有用。

    Scala虽然支持所有的3中写法,但是推荐最后一种。因为它帮助简化了代码的复杂度,增加了程序的不可变性。不可变指的是程序在执行过程中,所有的状态(变量)都是常量。不可变的代码比可变代码更加容易理解、调试和维护。

    表达式导向的语言倾向与使用不可变的对象,能减少程序中的可变对象。

    使用不可变对象

    核心规则3:使用不可变对象可以大幅减少运行时故障。当面对可变与不可变的选择时,选择不可变对象无疑是最安全的。

    对象等价性

    Scala提供了##和==来判断对象是不是等价,它们可以作用于AnyRef(引用)和AnyVal(值)。

    对象的哈希值和equal应该成对出现。因为等价性经常使用到了hash值。

    import collection.immutable.HashMap
    class Point2(var x: Int, var y: Int) extends Equals {
      def move(mx: Int, my: Int) : Unit = {
        x = x + mx
        y = y + my
      }
      override def hashCode(): Int = y + (31*x)
      def canEqual(that: Any): Boolean = that match {
        case p: Point2 => true
        case _ => false
      }
      override def equals(that: Any): Boolean = {
        def strictEquals(other: Point2) =
          this.x == other.x && this.y == other.y
        that match {
          case a: AnyRef if this eq a => true
          case p: Point2 => (p canEqual this) && strictEquals(p)
          case _ => false
        }
      }
    }
    object ObjecteEquality extends scala.App
    {
      val x = new Point2(1,1)
      val y = new Point2(1,2)
      val z = new Point2(1,1)
      println(x == y) // false
      println(x == z) // true
      val map = HashMap(x -> "HAI", y -> "ZOMG")
      println(map(x)) // HAI
      println(map(y)) // ZOMG
      println(map(z)) // HAI, if we remove hashCode, there will be an exception
      x.move(1,1)
    // println(map(x)) //Exception in thread "main" java.util.NoSuchElementException: key not found: Point2@40
      println(map.find(_._1 == x))
    }

    3-22行定义了一个Point2类,它继承自Equals。

    trait Equals extends Any {

      def canEqual(that: Any): Boolean

      def equals(that: Any): Boolean

    }

    定义了自己的move方法和hashCode方法。canEqual用来判断是否可以在对象上应用equal方法,这里只是检查是否类型匹配。equal包含一个内部函数strictEquals用来判断对象的成员是否相等。equal首先检查是不是引用了同一个Point2对象,如果是,直接返回true。否则,检查类型是不是匹配,如果是,用strictEquals用来判断对象的成员是否相等。

    第36行:println(map(z)),它的正确执行依赖于hashCode是否定义。Map在寻找指定key的值的时候,会调用key.##。

    第38行,由于move改变了x的内部状态,hashCode计算出来的新值当做key去Map里面查找,找不到对应的值,就会报NoSuchElementException异常。

    第40行,比较奇特。看下find的定义:

    trait IterableLike:  

    override /*TraversableLike*/ def find(p: A => Boolean): Option[A] = iterator.find(p)

    object Iterator:

      def find(p: A => Boolean): Option[A] = {

        var res: Option[A] = None

        while (res.isEmpty && hasNext) {

          val e = next()

          if (p(e)) res = Some(e)

        }

        res

      }

    传给find的是一个predicate。迭代器遍历集合中的每个元素,并将该元素作为参数传给predicate。所有我们这里传给predicate的参数是一个键值对[A,B]。_就是传给predicate的参数。_1指的是键值对中的第一个元素(实际上是元组中的第一个元素),即A,也就是作为key的Point2。现在很容易明白这句的意思了,就是与x的hashCode一样的元素。_1的定义位于:

    trait Product2:

      /** A projection of element 1 of this Product.

       * @return A projection of element 1.

       */

      def _1: T1

    在我们实现对象的等价判断的时候,请遵循:

    • 如果两个对象相等,那它们应该有相同的hashCode。

    • 对象的hashCode在其生命周期内不会改变。

    • 如果将一个对象发送给其他的JVM,应该保证等价判断依赖于对象在两个JVM都可用的属性。主要用于序列化。

    如果我们的对象是不可变的,那么上面的条件2自行就满足了,这会简化等价判断。另外,不可变性不仅仅简化等价判断,也会简化并行数据的访问,因为不存在同步互斥。

    使用None而不是null

    null的使用还是很受大家诟病的。null迫使大家添加了额外的处理代码。Scala使用Option来包装了null的处理,我们不在需要去判断变量是否为空。我们可以将Option看成一个通用的容器,包含了一些对象的容器(Some),或者是空容器(None)。这两周容器都需要对象的类型。

    类似地,Scala还有空的列表Nil。

    核心规则4:使用None而不是null

    Java中,我们经常会碰到空异常。如果我们学会了正确使用Option,完全可以避免空异常的发生。

    Scala的Option伴生对象(companion object)包含一个工厂方法,将Java的null自动转换为None:var x : Option[String] = Option(null)。等价于var x : Option[String] = None。

    Scala更加高级的用法是把它当作一个集合。这意味着,你可以在Option上使用map、flatMap、foreach,甚至是for表达式。

    使用Null的一些高级实例:

    class HttpSession
    class Connection
    object DriverManager {
      def getConnection(url: String, user: String, pw: String): Connection = {
        println("getConnection")
        new Connection
      }
    }
    object AdvancedNull extends scala.App {
      //CREATE AN OBJECT OR RETURN A DEFAULT
      def getTemporaryDirectory(tmpArg: Option[String]): java.io.File = {
        tmpArg.map(name => new java.io.File(name)).
          filter(_.isDirectory).
          getOrElse(new java.io.File(
          System.getProperty("java.io.tmpdir")))
      }
      //EXECUTE BLOCK OF CODE IF VARIABLE IS INITIALIZED
      val username1: Option[String] = Option("Sulliy")
      for (uname <- username1) {
        println("User: " + uname)
      }
      val username2: Option[String] = None
      for (uname <- username2) {
        println("User: " + uname)
      }
      def canAuthenticate(username: String, password: Array[Char]): Boolean = {
        println("canAuthenticate")
        true
      }
      def privilegesFor(username: String): Int = {
        println("privilegesFor")
        0
      }
      def injectPrivilegesIntoSession(session: HttpSession, privileges: Int): Unit = {
        println("injectPrivilegesIntoSession")
      }
      def authenticateSession(session: HttpSession,
                              username: Option[String],
                              password: Option[Array[Char]]) = {
        for (u <- username;
             p <- password;
             if canAuthenticate(u, p)) {
          val privileges = privilegesFor(u)
          injectPrivilegesIntoSession(session, privileges)
        }
      }
      authenticateSession(new HttpSession, None, None)
      //USING POTENTIAL UNINITIALIZED VARIABLES TO CONSTRUCT ANOTHER VARIABLE
      def createConnection(conn_url: Option[String],
                           conn_user: Option[String],
                           conn_pw: Option[String]): Option[Connection] =
        for {
          url <- conn_url
          user <- conn_user
          pw <- conn_pw
        } yield DriverManager.getConnection(url, user, pw)
      createConnection(None, Option("sully"), None)
      def lift3[A, B, C, D](f: Function3[A, B, C, D]): Function3[Option[A], Option[B], Option[C], Option[D]] = {
        (oa: Option[A], ob: Option[B], oc: Option[C]) =>
          for (a <- oa; b <- ob; c <- oc) yield f(a, b, c)
      }
      lift3(DriverManager.getConnection)(Option("127.0.0.1"), Option("sulliy"), Option("sulliy"))
    }

    11行-16行,示例了通过一个文件名获取File对象。由于输入参数是Option[String],该函数可以接受None,既null。map、filter、getOrElse都是Option的成员函数:

    @inline final def map[B](f: A => B): Option[B] =

        if (isEmpty) None else Some(f(this.get))

    @inline final def filter(p: A => Boolean): Option[A] =

        if (isEmpty || p(this.get)) this else None

    @inline final def getOrElse[B >: A](default: => B): B =

        if (isEmpty) default else this.get

    前两个函数都又返回了Option[],所以我们可以进行级联的书写。map返回None(Option的子类),None的filter返回None,None的getOrElse返回default,即new java.io.File( System.getProperty("java.io.tmpdir")。

    我们在需要创建一个对象或者返回一个缺省对象的时候,可以使用这种方法。

    18行-21行,示例了将Option放在for循环里面。由于username1赋值了,username2为空,因此20行会被执行,24行不会被执行。37行-47行,给了一个更加复杂的例子。

    49行-56行,示例了通过一个可能未初始化的对象来创建新对象。用到了yield。

    58行-62行,提供了一个通用方法,将普通的Java方法封装为支持Option的新的Scala方法。这样,我们就不需要自己去处理所有参数的null检查。

    多态环境下的等价判断

    核心规则5:在使用多态情况下,使用scala.Equals提供的模板。

    前面已经提到了scala.Equals,需要注意的是:请同时重写canEqualequal。

  • 相关阅读:
    Encrypted Handshake Message
    RSAParameters Struct
    What if JWT is stolen?
    What's the difference between JWTs and Bearer Token?
    RSA Algorithm Example
    第18届Jolt大奖结果公布
    Ruby on rails开发从头来(windows)(三十六) 调试技巧
    Ruby on rails开发从头来(四十二) ActiveRecord基础(主键和ID)
    YouTube开放基础技术架构 让用户建自家YouTube
    Ruby on rails开发从头来(四十) ActiveRecord基础(Boolean属性)
  • 原文地址:https://www.cnblogs.com/nucdy/p/7505043.html
Copyright © 2011-2022 走看看