Coding Poet, Coding Science

Scala编程概要(一):文档,注释,面向对象

虽然每次都是从头开始看Java与Scala的初级的教程,但是每次看的时候重点不一样,对于语言的理解也不一样。这一次的时候,强调的是,我们如何从初学者成为能够熟练运用一门语言的开发者?为了做到这一点,我们需要付出哪些努力?

现在的看法是这样的。第一,我们应该按照写书的方式写Scala的程序。所以,如何从应用程序中生成文档,以及介绍这个文档是最基本的。所以,在初学者的第一章的时候就应该学习使用scaladoc来写文档,以便能够注释出来自己所写的东西,表达出来自己所写的东西是什么样的意思。第二,或许是对于某些语言,使用REPL环境。但是学习一个语言可能更关键的是学习它适应的设计模式,与常见的实践,就像写学术文档一样写程序。

Scala的一些灵活的约定

Scala的语法是高度自动化的。第一是,方法与成员的用法看起来没有什么区别。尤其是,如果方法没有区别,那么方法可以不用带圆括号。这种情况下,与编程语言的文法本质更接近了一步:所有的出现的都是一个token,只是不同的token有不同的含义。比如res2.toUpperCase虽然看起来调用的是成员,但是实际上是一个方法。

第二是变量与常量的自动的声明。比如使用valvar,而不用带有复杂的类型声明,甚至不用知道类型是什么。不过,Scala与Python之类的还有区别。因为在Python中,运行期间可以得到一个成员的类型,并且把类型当成是一个变量。在命令式的语言中,使用变量自然是很正常的,因为使用常量,看起来也做不了什么有意义的事情(其实这种观念很可能是不正常的,因为在一定作用域的限制下,不使用变量也能够做很多的事情,特别是很多的数学计算,其实根本不需要使用变量)。不过,其实不使用变量而使用常量val也能完成很多的编程任务,而且似乎是更优雅。具体我们可以参考Scott的《程序设计语言实践之路》中对于名子、变量、作用域的介绍。

在Scala中,当然也可以强制变量类型声明。比如说,我们显然可以在变量后使用val a : Int的形式限定一个整型参数。另外,Scala中一行中存在多条语句的时候才需要使用分号隔开。

现在看来,学习Scala之前,确实是需要学习一些基础的知识。比如数据类型中的基本类型与引用类型。通过Scott的《程序设计语言实践之路》我们看到Java的数据类型的世界中,基本类型与其它类型的区别是明显的,因为基本类型返回的都是值方式,而用户自定义的类型,默认返回的都是一个引用。(这种规定不那么直观,但是在Java编程中却有着举足轻重的地位,决定了我们如何使用Java中的不同的数据类型)。此外,数组在Java中比较麻烦。但是Scala就不用考虑这一点。

总结:总而言之,学习一门编程语言的基础的时候,大概在学习一门语言的设计模式之外,第一步是了解编程风格。比如Scala,既然支持无参的方法不用括号,那么在Scala中,最好就不使用括号。这种风格更为简洁。在数据类型中,既然没有值类型与引用类型的复杂的区分,那么就不用考虑复制的问题,以一种更为函数式的风格思考问题。除此之外,还有一些复杂的问题,比如使用map-reduce风格。

总结:评价与了解一个编程语言,可能不再只是使用简单的类型的静态与动态、函数式编程或者其它的风格。其实,一般而言,评价一个东西,我们可以说一句话,可以说十句话,可以说一百句,可以说成一本书,也可以说成一套全集。关键是看我们描述的细的程度,仅一句话当然可以那么描写。但是要达到比较理想的层次,可能还需要一套详细的评价的方法。更进一步地,我们需要了解一门编程语言的特性可能包括以下的几个方面:最基础的方面,无论是函数式语言还是其它的语言,都要考虑抽象的问题,因为抽象是高级编程语言的共同的特性。这里面的主题就有名子,作用域,数据抽象,控制抽象等。第二步是所谓的编程语言的类型的系统。第三步是编程范式。然后是设计模式。经过这四个步骤,对于一个编程语言的理解或许就比较深了。至于前端的语法的知识,其实是比较形而下学的。

Scala文档风格[07-01-2015 08:40:18]

在没有考虑编程语言的任何特点的情况下写代码并不好,这个时候我们可以写注释。Scala的第一种的注释风格是这样的:

/** This is a brief description of what's being documented.
  *
  * This is further documentation of what we're documenting.  It should
  * provide more details as to how this works and what it does. 
  */
def myMethod = {}

明显地,这种方法下,注释的整体就是一段文字,编程语言只知道注释是由几个段落构成的,而没有感知到其它的成份,所以生成的文档也很笨拙。虽然学术级别的写作要求非常高,以致于不可能单独在编程语言中讲到,但是其实注释是一种更深的艺术。只有明确地表达了自己的思维,所做的编程的工作才有意义。

如果是比较简单的类型,可以使用单行注释,示例如下

/** Does something very simple */

Scaladoc本身是一个完善的标记的语言。由于注释本身就是介绍文档的。所以,一方面,注释介绍了相应的数据结构的接口特性,另一方面是实现的特性。而且一般在文档当中,要把相应的编程结构的接口特性描述的更清楚一点。特别是把接口列出来。在Scala中,可以使用@return这样的标记来处理返回的值。doc文档的编写规范,可以参考http://docs.scala-lang.org/style/scaladoc.html

使用package的注释

在绝大多数的编程语言中,模块或者包或者库都是比面向对象更为高级的抽象。所以对模块的注释也显得格外地重要。在Scala中,包往往需要比较长的注释。一种方式是放在package的后面。

package parent.package.name
/** This is the ScalaDoc for the package. */
package object mypackage {
}

另外一种方式是放在package当中。大块的注释,好像演化的趋势正是文学编程,以及使用markdown或者latex的风格。特别是在一些定理证明语言与Haskell这样的新的语言中,出现的在注释中插入代码的情况更加普遍。

package my.package
/** Provides classes for dealing with complex numbers.  Also provides
  * implicits for converting to and from `Int`.
  *
  * ==Overview==
  * The main class to use is [[my.package.complex.Complex]], as so
  * { { {
  * scala> val complex = Complex(4,3)
  * complex: my.package.complex.Complex = 4 + 3i
  * } } }
  *
  * If you include [[my.package.complex.ComplexConversions]], you can 
  * convert numbers more directly
  * { { {
  * scala> import my.package.complex.ComplexConversions._
  * scala> val complex = 4 + 3.i
  * complex: my.package.complex.Complex = 4 + 3i
  * } } } 
  */
package complex {}

类的注释可以参考

/** A person who uses our application.
  *
  * @constructor create a new person with a name and age.
  * @param name the person's name
  * @param age the person's age in years 
  */
class Person(name: String, age: Int) {
}

不过,类的注释的内容与类的构造器的内容的注释如何安排其实也不是那么容易的。对象可以参考

/** Factory for [[mypackage.Person]] instances. */
object Person {
  /** Creates a person with a given name and age.
    *
    * @param name their name
    * @param age the age of the person to create 
    */
  def apply(name: String, age: Int) = {}
  /** Creates a person with a given name and birthdate
    *
    * @param name their name
    * @param birthDate the person's birthdate
    * @return a new Person instance with the age determined by the 
    *         birthdate and current date. 
    */
  def apply(name: String, birthDate: java.util.Date) = {}
}

Scaladoc的注释规范,可以参考Scala lang的维基

Scala的优势在哪里

首先,创建一个Person类,在Java中,代码是这样的:

class Person {
    private String firstName;
    private String lastName;
    private int    age;

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName  = lastName;
        this.age  = age; }
    public void setFirstName(String firstName) { 
        this.firstName = firstName; }
    public void String getFirstName() { return this.firstName; }
    public void setLastName(String lastName) { this.lastName = lastName; }
    public void String getLastName() { return this.lastName; }
    public void setAge(int age) { this.age = age; }
    public void int getAge() { return this.age; }
}

但是在Scala中对于简单的getter与setter的支持下,简单的形式就是:

class Person(var firstName: String, var lastName: String, var age: Int)

当然,这种方法添加的构造函数可以通过直接访问Person.lastName得到,看起来是直接访问了成员,但是其实是访问的构造器。不过,要想在构造器中添加一些日志的功能,还是得另外的配置。

要注意的是,Scala的类的实现中,不使用静态方法,取而代之的是可以直接定义一个相关的对象,通过这个对象直接产生一个相当于静态成员的东西。