Scala构建计算器,第1 部分(代码学习第8天)
特定于领域的语言
可能您无法(或没有时间)承受来自于您的项目经理给您的压力,那么让我直接了当地说吧:特定于领域的语言无非就是尝试(再一次)将一个应用程序的功能放在它该属于的地方 — 用户的手中。
通过定义一个新的用户可以理解并直接使用的文本语言,程序员成功摆脱了不停地处理 UI 请求和功能增强的麻烦,而且这样还可以使用户能够自己创建脚本以及其他的工具,用来给他们所构建的应用程序创建新的行为。虽然这个例子可能有点冒险(或许会惹来几封抱怨的电子邮件),但我还是要说,DSL 的最成功的例子就是 Microsoft? Office Excel “语言”,用于表达电子表格单元格的各种计算和内容。甚至有些人认为 SQL 本身就是 DSL,但这次是一个旨在与关系数据库相交互的语言(想象一下如果程序员要通过传统 API read()/write() 调用来从 Oracle 中获取数据的话,那将会是什么样子)。
这里构建的 DSL 是一个简单的计算器语言,用于获取并计算数学表达式。其实,这里的目标是要创建一个小型语言,这个语言能够允许用户来输入相对简单的代数表达式,然后这个代码来为它求值并产生结果。为了尽量简单明了,该语言不会支持很多功能完善的计算器所支持的特性,但我不也不想把它的用途限定在教学上 — 该语言一定要具备足够的可扩展性,以使读者无需彻底改变该语言就能够将它用作一个功能更强大的语言的核心。这意味着该语言一定要可以被轻易地扩展,并要尽量保持封装性,用起来不会有任何的阻碍。
关于 DSL 的更多信息DSL 这个主题的涉及面很广;它的丰富性和广泛性不是本文的一个段落可以描述得了的。想要了解更多 DSL 信息的读者可以查阅本文末尾列出的 Martin Fowler 的 “正在进展中的图书”;特别要注意关于 “内部” 和 “外部” DSL 之间的讨论。Scala 以其灵活的语法和强大的功能而成为最强有力的构建内部和外部 DSL 的语言。
.换句话说,(最终的)目标是要允许客户机编写代码,以达到如下的目的:
清单 1. 计算器 DSL:目标
// This is Java using the CalculatorString s = "((5 * 10) + 7)";double result = com.tedneward.calcdsl.Calculator.evaluate(s);System.out.println("We got " + result); // Should be 57
case class Person(first:String, last:String, age:Int){}
case class Person(first:String, last:String, age:Int)
C:\Projects\Exploration\Scala>javap PersonCompiled from "case.scala"public class Person extends java.lang.Object implements scala.ScalaObject,scala.Product,java.io.Serializable{ public Person(java.lang.String, java.lang.String, int); public java.lang.Object productElement(int); public int productArity(); public java.lang.String productPrefix(); public boolean equals(java.lang.Object); public java.lang.String toString(); public int hashCode(); public int $tag(); public int age(); public java.lang.String last(); public java.lang.String first();}
object App{ def main(args : Array[String]) : Unit = { val ted = Person("Ted", "Neward", 37) }}
object App{ def main(args : Array[String]) : Unit = { val ted = Person("Ted", "Neward", 37) val ted2 = Person("Ted", "Neward", 37) val amanda = Person("Amanda", "Laucher", 27) System.out.println("ted == amanda: " + (if (ted == amanda) "Yes" else "No")) System.out.println("ted == ted: " + (if (ted == ted) "Yes" else "No")) System.out.println("ted == ted2: " + (if (ted == ted2) "Yes" else "No")) }}
case class Person(first:String, last:String, age:Int);object App{ def main(args : Array[String]) : Unit = { val ted = Person("Ted", "Neward", 37) val amanda = Person("Amanda", "Laucher", 27) System.out.println(process(ted)) System.out.println(process(amanda)) } def process(p : Person) = { "Processing " + p + " reveals that" + (p match { case Person(_, _, a) if a > 30 => " they're certainly old." case Person(_, "Neward", _) => " they come from good genes...." case Person(first, last, ageInYears) if ageInYears > 17 => first + " " + last + " is " + ageInYears + " years old." case _ => " I have no idea what to do with this person" }) }}/*C:\Projects\Exploration\Scala>scala AppProcessing Person(Ted,Neward,37) reveals that they're certainly old.Processing Person(Amanda,Laucher,27) reveals that Amanda Laucher is 27 years old. */
package com.tedneward.calcdsl{ private[calcdsl] abstract class Expr private[calcdsl] case class Number(value : Double) extends Expr private[calcdsl] case class UnaryOp(operator : String, arg : Expr) extends Expr private[calcdsl] case class BinaryOp(operator : String, left : Expr, right : Expr) extends Expr}
package com.tedneward.calcdsl.test{ class CalcTest { import org.junit._, Assert._ @Test def ASTTest = { val n1 = Number(5) assertEquals(5, n1.value) } @Test def equalityTest = { val binop = BinaryOp("+", Number(5), Number(10)) assertEquals(Number(5), binop.left) assertEquals(Number(10), binop.right) assertEquals("+", binop.operator) } }}
package com.tedneward.calcdsl{ // ... object Calc { def evaluate(e : Expr) : Double = { e match { case Number(x) => x case UnaryOp("-", x) => -(evaluate(x)) case BinaryOp("+", x1, x2) => (evaluate(x1) + evaluate(x2)) case BinaryOp("-", x1, x2) => (evaluate(x1) - evaluate(x2)) case BinaryOp("*", x1, x2) => (evaluate(x1) * evaluate(x2)) case BinaryOp("/", x1, x2) => (evaluate(x1) / evaluate(x2)) } } }}
def simplify(e : Expr) : Expr = { e match { // Double negation returns the original value case UnaryOp("-", UnaryOp("-", x)) => x // Positive returns the original value case UnaryOp("+", x) => x // Multiplying x by 1 returns the original value case BinaryOp("*", x, Number(1)) => x // Multiplying 1 by x returns the original value case BinaryOp("*", Number(1), x) => x // Multiplying x by 0 returns zero case BinaryOp("*", x, Number(0)) => Number(0) // Multiplying 0 by x returns zero case BinaryOp("*", Number(0), x) => Number(0) // Dividing x by 1 returns the original value case BinaryOp("/", x, Number(1)) => x // Adding x to 0 returns the original value case BinaryOp("+", x, Number(0)) => x // Adding 0 to x returns the original value case BinaryOp("+", Number(0), x) => x // Anything else cannot (yet) be simplified case _ => e } }
def evaluate(e : Expr) : Double = { simplify(e) match { case Number(x) => x case UnaryOp("-", x) => -(evaluate(x)) case BinaryOp("+", x1, x2) => (evaluate(x1) + evaluate(x2)) case BinaryOp("-", x1, x2) => (evaluate(x1) - evaluate(x2)) case BinaryOp("*", x1, x2) => (evaluate(x1) * evaluate(x2)) case BinaryOp("/", x1, x2) => (evaluate(x1) / evaluate(x2)) } }