首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Scala 領域特定語言實戰:從 Java 開發者視角構建計算器 DSL(上)

     編輯:Java 相關知識

領域特定語言(DSL)近年來成為熱門話題,許多函數式語言受青睞正是因為它們擅長構建 DSL。在「面向 Java 開發人員的 Scala 指南」系列第八篇中,Ted Neward 將示範如何利用 Scala 的函數式特性構建一個簡單的計算器 DSL,重點介紹 case class 與模式匹配這兩大核心機制。

為什麼需要 DSL?

DSL 的本質是將應用程式的功能交到使用者手中。透過定義一個使用者能理解、直接操作的文字語言,開發者可擺脫不斷處理 UI 需求變更的困擾,同時賦予使用者編寫腳本、擴展應用行為的能力。最成功的 DSL 範例莫過於 Excel 的公式語言,甚至 SQL 也可視為針對關聯式資料庫的 DSL。

計算器 DSL 的設計目標

目標是讓用戶端程式碼能像這樣運作:

// Java 端呼叫
String s = "((5 * 10) + 7)";
double result = com.tedneward.calcdsl.Calculator.evaluate(s);
System.out.println("We got " + result); // 輸出 57

為了擴展性與封裝性,不採用簡單的字串解析器,而是遵循編譯器經典的兩階段架構:

  1. 解析器:將輸入文字轉換為抽象語法樹(AST)
  2. 求值器:遍歷 AST 計算結果

Scala 的 case class:構建 AST 的利器

case class 是 Scala 提供的便利機制,編譯器自動產生建構子、equals()、hashCode()、toString() 等方法,並支援模式匹配。例如:

case class Person(first: String, last: String, age: Int)

建立實例時無需 new:val ted = Person("Ted", "Neward", 37)。更重要的是,case class 採用結構相等性(bitwise equality),兩個欄位相同的實例視為相等。

模式匹配:不只是強化版 switch

模式匹配可解構物件、綁定變數、加入守衛條件(if),甚至匹配類型。結合 case class,可輕鬆處理樹狀結構:

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"
}

計算器 AST 定義

數學表達式遞迴結構天然對應樹狀 AST:

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
}

四行程式碼即完成任意深度算術表達式的型別階層。private[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)
  }
}

每個分支回傳 Double,運算元先遞迴求值再執行運算。雖然違反傳統 OO 封裝觀念,但 AST 於包外不可見,客戶端僅傳入字串,這種「函數式風格」在封裝邊界內完全合理。

優化階段:simplify() 簡化 AST

利用模式匹配的常數匹配與變數綁定,可在求值前進行代數簡化:

def simplify(e: Expr): Expr = e match {
  case UnaryOp("-", UnaryOp("-", x)) => x
  case UnaryOp("+", x) => x
  case BinaryOp("*", x, Number(1)) => x
  case BinaryOp("*", Number(1), x) => x
  case BinaryOp("*", x, Number(0)) => Number(0)
  case BinaryOp("*", Number(0), x) => Number(0)
  case BinaryOp("/", x, Number(1)) => x
  case BinaryOp("+", x, Number(0)) => x
  case BinaryOp("+", Number(0), x) => x
  case _ => e
}

求值器只需在匹配前呼叫 simplify(e) 即可。更進階的遞迴簡化(如巢狀結構的連鎖簡化)留待下一篇探討。

小結

本文展示了 case class 與模式匹配如何協作,輕鬆構建並操作 AST。這是 Scala 乃至函數式程式設計的核心模式,也是構建外部 DSL 的強大基礎。下一篇將完成解析器,實現從字串到 AST 的轉換。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved