Scala中手动模拟实现基础类型

2021-01-15

< view all posts

Scala不仅是一个函数式语言,也是一个面向对象的语言。实际上Scala在OOP特点方面和Java是保持一致的:万物皆对象,包括基础类型。而Scala在语法上还提供了一个方便的特性,就是可以用纯符号作为函数名(方法名)的关键字,从而使我们可以方便地在Scala去实现自定义的类型,包括各种基础类型本身。

模拟布尔类型

首先来看对布尔类型的实现,这里一个值得注意的地方是取非的符号"!"。其它的逻辑运算符,后面都是跟着一个参数的,比如a.||(b),在Scala中可以写成a || b,这实际上也是一个语法糖了。那么像取非这样后面没有参数的前缀(prefix)运算符,在定义的时候就必须在前面加上"unary_"作为标注,也就是写成def unary_! : Boolean。还有一个注意点,这里冒号的前面必须要有一个空格,否则编译器会以为冒号也是方法名的一部分。

package basetype.simulation

abstract class Boolean {
  def conditional(x: Boolean, y: Boolean): Boolean
  def &&(that: Boolean): Boolean = conditional(that, _false)
  def ||(that: Boolean): Boolean = conditional(_true, that)
  // prefix必须有unary_前缀,且和冒号之间必须有一个空格
  def unary_! : Boolean = conditional(_false, _true)
  def ==(that: Boolean): Boolean = conditional(that, !that)
  def !=(that: Boolean): Boolean = conditional(!that, that)
  def >(that: Boolean): Boolean = conditional(!that, _false)
  def <(that: Boolean): Boolean = conditional(_false, that)
  def toString: String
}

object _true extends Boolean {
  def conditional(x: Boolean, y: Boolean): Boolean = x
  override def toString: String = "true"
}

object _false extends Boolean {
  def conditional(x: Boolean, y: Boolean): Boolean = y
  override def toString: String = "false"
}

来做一些简单的测试:

package basetype.simulation

object entry extends App{
  println(_true && _false)
  println(_true || _false)
  println(_false && _true)
  println(!_true)
  println(!_false == _true)
  println(_false != _true)
  println(_false > _true)
  println(_true > _false)
}

结果是:

false
true
false
false
true
true
false
true

其实实现布尔类型本质上就是做了一个枚举,因为True和False在运算符的两边总共也只有4种排列组合的方式。一个比较巧妙的地方是把conditional()给改成泛型和call by name之后,它实际上可以起到一个模拟if/else的作用:

abstract class Boolean {
  def conditional[T](x: => T, y: => T): T
  def toString: String
}

object _true extends Boolean {
  def conditional[T](x: => T, y: => T): T = x
  override def toString: String = "true"
}

object _false extends Boolean {
  def conditional[T](x: => T, y: => T): T = y
  override def toString: String = "false"
}

用法是 条件.conditional(为true时的执行内容, 为false时的执行内容),例如:

object entry extends App{
  _false conditional(println("is true"), println("is false"))
}

首先这里为什么需要改成泛型,因为conditional方法本身不关心它接受和返回的值,只是从两者中选择一个去执行。第二,为什么需要改成CBN,因为Scala中函数的调用默认是CBV的,因此默认情况下,在conditional能做出判断前,两个参数里的内容都会被执行。改成CBN就只会执行一个。实际上上面的与和或运算符也应该改成CBN的调用,提高效率。关于CBN和CBV,这篇笔记有详细的解释。

模拟自然数类型

接下来模拟自然数类型(大于等于0的整数),为简单起见,当数值小于0时会直接抛出异常。首先定义下面的抽象类:

package nat.simulation

abstract class Nat {
  def isZero: Boolean
  def predecessor: Nat
  def successor: Nat
  def + (that: Nat): Nat
  def - (that: Nat): Nat
  def toString: String
}

给这个抽象类设计两个子类,其中一个是Object 0,另一个是给输入加1的累加器Class。0很好实现,任何数和0相加都是它本身,而0减去非0的数直接抛出异常即可。0递减1也是异常,而加1则可以返回一个累加器对象。

object Zero extends Nat{
  override def isZero: Boolean = true
  override def predecessor: Nat = throw new RuntimeException("negative number")
  override def successor: Nat = new Succ(Zero)
  override def +(that: Nat): Nat = that
  override def -(that: Nat): Nat = if (that.isZero) Zero else
    throw new RuntimeException("negative number")
  override def toString: String = "."
}

那么接下来就是累加器了(每次加1),这个其实结合数据结构的知识也是容易想到的,尤其是我们已经知道递归函数的执行是转换为调用栈。累加器的作用是输入对象n,返回对象n+1,所以这个累加器对象的递减直接返回n就行。累加器对象的递增则是递归调用自己。加法和减法可以通过代换模型的表达式推导,例如:

3+2
= 4+1
= 5+0
3-2
=2-1
=1-0

将右侧表达式最终转化为0,达到停止条件,直接返回左侧即可:

class Succ(n: Nat) extends Nat {
  override def isZero: Boolean = false
  override def predecessor: Nat = n
  override def successor: Nat = new Succ(this)
  override def +(that: Nat): Nat = if (that.isZero) this else
    this.successor + that.predecessor
  override def -(that: Nat): Nat = if (that.isZero) this else
    this.predecessor + that.predecessor
  override def toString: String = "|" + n.toString
}

做这样的测试:

object entry extends App{
  println(Zero.successor.successor+Zero.successor-Zero.successor)
}

输出为"||.",正确。