Domain Specific Languages / 领域特定语言

Table of Contents

1

1.1 Meta Notes

1.2 Books

1.2.1 DONE Combo #1

  1. DONE 金字塔原理
  2. TODO Beast Machines: Transformers (cartoon)
  3. DONE 论持久战
  4. DONE 一看就懂的经济常识全图解
  5. DONE 刻意练习
  6. DONE 系统之美
  7. DONE 邓小平时代

1.2.2 INPROGRESS Combo #2

  1. DONE 新经济, 新规则 (Kevin Kelly)
  2. DONE 麦肯锡问题解决方法与技巧

1.2.5 INPROGRESS Effective Java

1.2.6 DONE Don't Make Me Think

1.2.7 DONE Clean Code

1.2.8 INPROGRESS Deep Learning

1.2.9 INPROGRESS Async JavaScript

2 译者序 & 前言

  • 这是一本用特定领域语言写就的关于特定领域语言的书
  • 澄清概念和发现模式上老马有超能力
  • XML 写的比代码还多, 尖括号刺伤了他们的双眼
  • DSL: 让一切更连贯, 更容易理解, 便于维护, 减少 bug, 非程序员也能看懂, 便于和领域专家/客户沟通
  • 解释与语言无关的通用原则和特性
  • Rudy, 老马最熟悉的动态语言
  • 模式结构的元素:
    • 名字
    • 意图和摘要
    • 工作原理和使用场景
    • 例子

3 第一部分 叙述   best

3.1 第 1 章入门例子

  • 什么是 DSL: domain specific language

3.1.1 哥特式建筑安全系统

3.1.2 状态机模型

  • Domain driven design 中的 ubiquitous language (speak the common language)
  • 分开了公共代码和可变代码

3.1.3 为格兰特小姐的控制器编写程序

  • XML: 控制器的行为的任何修改都无需发布新的 JAR
  • Extensive testing 广泛测试
  • 命令式模型: imperative
  • DSL: 可能没有控制结果, 没有其他东西, 图灵不完备, 用它不能编写整个应用, 只能用来描述应用的一个小小方面, 其 简单性意味着它易于编辑和处理.
  • 外部 DSL, 内部 DSL
  • 内部 DSL 中的一个概念是 fluent interface (连贯性接口), 和 DSL 联系紧密
  • 非连贯性接口: command-query API

3.1.4 语言和语义模型

  • almost always
  • 设计良好的 DSL, 语义模型至关重要
  • DSL 只是模型的一个薄薄的 "门面" (facade), (tip: SLFJ 是各种 logging 库的 facade)

3.1.5 用代码生成

  • interpretation 解释
  • code generation 代码生成, 笨拙: 需要额外的编译步骤; 优势: 解析器和生成代码可以用不同的语言

3.1.6 使用语言工作台

  • 语言工作台, 一个专用的 IDE, 可能带有图形界面 (程序员怀疑这种玩具式的工具)
  • 类似于语言工作台, 提供文本编辑器. 后 IntelliJ 的能力: 为基于文本的语言提供类似语法指导的编辑, 自动补全及其他类似的功能
  • 许多人不把电子表格当做编程环境: 把工具紧密地集成到了编程环境
  • 说明性编程 illustrative programming

3.1.7 可视化

  • 类似 dot (graphviz) 的界面

3.2 第 2 章 使用 DSL

3.2.1 定义 DSL   best

  • domain specific languages 的四个关键因素:
    • computer programming language (计算机程序设计语言), 可以执行
    • language nature (语言性), 连贯的表达能力
    • limited expressiveness (受限的表达), 只支持特定领域所需特性的最小集 (MVP: minimum viable product)
    • domain focus (针对领域), 明确性
  • 分类:
    • 外部 DSL
    • 内部 DSL
    • 语言工作台
  • command-query API 定义了抽象领域的词汇, 内部 DSL 在此基础上添加了语法
  • 内部 DSL 给人的感觉是一个整句, 而非无关命令的序列
  • 有限的表达性让 DSL 产生了独特性
  • 片段 DSL 和独立 DSL
    • 片段 DSL 是和其他语言整合在一起的, 比如 regex
    • sql 可以看成片段, 也可以看成独立 DSL

3.2.2 为何需要 DSL

  • 提高开发效率
  • 与领域专家的沟通
  • 执行环境的改变: 参考 XML 帮助 JAR 包配置不同的运行方式这一点
  • 提高开发效率
  • 其他计算模型 (比如状态机模型, 网络依赖模型, etc)

3.2.3 DSL 的问题

  • 语言噪音
  • 构建成本 (可维护性)
  • 集中营语言 (ghetto language): 小心 DSL 演变成通用语言
  • 一叶障目的抽象: 应当视 DSL 为一种 "不断演化, 尚未完结" 的事务

3.2.4 广义的语言处理

3.2.5 DSL 的生命周期

  • 先描述一个框架: command-query API -> 构建一层 DSL 来简化
  • 先定义 DSL: 从场景开始 (最好和领域专家 pair)
  • 从最简单的用例开始, 采用测试驱动的方式开发
  • 基于模型发展 DSL: 模型生长 (model-seeded), 语言生长 (language-seeded)

3.2.6 设计优良的 DSL 从何而来

  • DSL 的总体目标: 对读者要清晰
  • trick: 采用日常生活里的通用约定 (一个例子是 JSON 采用了程序员都见过的 [l,i,s,t], {object}, key:value 这几个符号标记法)

3.3 第 3 章实现 DSL

3.3.1 DSL 处理之架构

  • 语义模型和 DSL 的分离实际上反映了领域对象及其表现的分离
  • 内部 DSL 使用一种可以执行的语言编写

3.3.2 解析器的工作方式

3.3.3 文法、语法和语义

  • 文法与语义无关

3.3.4 解析中的数据

3.3.5

  • 文本宏与语法宏

3.3.6 测试 DSL

  • 语义模型的测试
  • 解析器的测试
  • 脚本的测试

3.3.7 错误处理

3.3.8 DSL 迁移

  • 增量迁移 (一个版本一个版本来)
  • 基于模型的迁移 (问题: 1) 可能语义模型并不稳定; 2) 一些注释会被去除.)

3.4 第 4 章实现内部 DSL

Introduction
  • 内部 DSL 比较容易实现
  • 同时, 宿主语言限制了我们

3.4.1 连贯 API 与命令–查询 API

  • 把函数序列进行组织和布局, 让它读起来像方法级联一样清晰明了
  • command-and-query interface
  • command 改变状态, 但是不返回值
  • query 返回值, 但是不改变状态

3.4.2 解析层的需求

3.4.3 使用函数

3.4.4 字面量集合

3.4.5 基于文法选择内部元素

3.4.6 闭包

3.4.7 解析树操作

3.4.8 标注

3.4.9 为字面量提供扩展

  • ruby: 5.days.ago

3.4.10 消除语法噪音

3.4.11 动态接收

3.4.12 提供类型检查

3.5 第 5 章实现外部 DSL

3.5.1 语法分析策略

3.5.2 输出生成策略

3.5.3 解析中的概念

3.5.4 混入另一种语言

3.5.5 XML DSL

3.6 第 6 章内部 DSL vs 外部 DSL

3.6.1 学习曲线

3.6.2 创建成本

3.6.3 程序员的熟悉度

  • 外部 DSL 可能没有编辑器支持, 没有高亮和智能不全

3.6.4 与领域专家沟通

3.6.5 与宿主语言混合

  • make 和 ant 作为外部 DSL 用来描述依赖网络
  • rake 作为内部 DSL 可以利用 ruby 语言和库

3.6.6 强边界

3.6.7 运行时配置

  • XML DSL 的流行一定程度就在于不用重新编译代码, 把代码逻辑从编译时期转换到运行时

3.6.8 趋于平庸

3.6.9 组合多种 DSL

  • DSL 应当是小巧, 受限的.

3.6.10 总结

  • 各有优势

3.7 第 7 章其他计算模型概述

3.7.1 7.1 几种计算模型

  • 决策表
  • 产生式规则系统: 条件 + 行为
  • 状态机: 以变动的状态响应各种事件
  • 依赖网络

3.7.2 7.2 选择模型

  • 看情况

3.8 第 8 章代码生成

3.8.1 选择生成什么

  • 基于模板的代码生成
  • 不基于模板的代码生成 (基于转化器)

3.8.2 如何生成

3.8.3 混合生成代码和手写代码

3.8.4 生成可读的代码

3.8.5 解析之前的代码生成

3.8.6 延伸阅读

3.9 第 9 章语言工作台

3.9.1 语言工作台之要素

  • language workbench
  • 语义模型
  • 编辑体验: 直接编辑源码, 或者投射编辑 (projectional edting)
  • 代码生成

3.9.2 模式定义语言和元模型

3.9.3 源码编辑和投射编辑

  • JetBrains 的 MPS (Meta-Programming System), 未来许多语言的基石 (那时候就这么叼了…)

3.9.4 说明性编程

3.9.5 工具之旅

3.9.6 语言工作台和 CASE 工具

  • 这种工具的绑定 (lock-in) 问题很严重, 可以考虑视之为一个 DSL 解析器, 而不是完整的 DSL 环境.

4 第二部分 通用主题

4.1 第 10 章各种 DSL

4.1.1 Graphviz

  • 节点用 node 标识
  • 弧用箭头 ("->" 等) 标识
  • 方括号内是属性

4.1.2 JMock

  • 渐进式接口, with 只能用在 method 之后

4.1.3 CSS

  • 很棒的 DSL
  • 很多写 CSS 的程序员觉得自己是设计师和不是程序员
  • 领域专家不仅能读, 还能写

4.1.4 HQL

  • hibernate 的查询语言, 映射 Java 类到数据库的表上
  • steps
    • 用语法指导翻译/树的构建讲 HQL 转化为 AST (abstract syntax tree)
    • HQL AST 转化为 SQL AST
    • 代码生成器根据 SQL AST 生成 SQL
  • ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files.

4.1.5 XAML

  • 组合式 DSL

4.1.6 FIT

  • framework for integration test

4.1.7 Make 等

4.2 第 11 章语义模型

4.2.1 工作原理

  • 语义模型先于 DSL 产生
  • 语义模型: operational interface, population interface

4.2.2 使用场景

4.2.3 入门例子(Java)

4.3 第 12 章符号表

4.3.1 工作原理

4.3.2 使用场景

4.3.3 参考文献

4.3.4 以外部 DSL 实现的依赖网络(Java 和 ANTLR)

4.3.5 在一个内部 DSL 中使用符号键(Ruby)

4.3.6 用枚举作为静态类型符号(Java)

4.4 第 13 章语境变量

4.4.1 工作原理

4.4.2 使用场景

4.4.3 读取 INI 文件(C#)

4.5 第 14 章构造型生成器

4.5.1 工作原理

4.5.2 使用场景

4.5.3 构建简单的航班信息(C#)

4.6 第 15 章宏

4.6.1 工作原理

4.6.2 使用场景

4.7 第 16 章通知

4.7.1 工作原理

4.7.2 使用场景

4.7.3 一个非常简单的通知(C#)

4.7.4 解析中的通知(Java)

5 第三部分 外部 DSL 主题

5.1 第 17 章分隔符指导翻译

5.1.1 工作原理

  • 每一行都是自治的, 不会相互影响, 而且形式相同, 各种语言都方便处理

5.1.2 使用场景

  • 简单, 但是难以处理复杂逻辑

5.1.3 常客记分(C#)

5.1.4 使用格兰特小姐的控制器解析非自治语句 (Java)

5.2 第 18 章语法指导翻译

5.2.1 工作原理

5.2.2 使用场景

5.2.3 参考文献

5.3 第 19 章 BNF

5.3.1 工作原理

  • a | b: alternative
  • kleene star, Kleene 星号: 正则表达式的 * 和 +
  • 大多数 BNF 都是 CFG (context-free grammar)
  • PEG (parsing expression grammar) 不是 CFG, 支持有序的选择 (避免了歧义)

5.3.2 使用场景

5.4 第 20 章基于正则表达式表的词法分析器

5.4.1 工作原理

5.4.2 使用场景

5.4.3 格兰特小姐控制器的词法处理(Java)

5.5 第 21 章递归下降法语法解析器

吐槽
  • 和 BNF 似乎没啥区别, 我说的是组织形式

5.5.1 工作原理

  • 实现起来容易

5.5.2 使用场景

5.5.3 参考文献

5.5.4 递归下降和格兰特小姐的控制器(Java)

5.6 第 22 章解析器组合子

5.6.1 工作原理

5.6.2 使用场景

5.6.3 解析器组合子和格兰特小姐的控制器(Java)

5.7 第 23 章解析器生成器

5.7.1 工作原理

5.7.2 使用场景

23.3Hello World(Java 和 ANTLR)

5.8 第 24 章树的构建

5.8.1 工作原理

5.8.2 使用场景

5.8.3 使用 ANTLR 的树构建语法(Java 和 ANTLR)

5.8.4 使用代码动作进行树的构建(Java 和 ANTLR)

5.9 第 25 章嵌入式语法翻译

5.9.1 工作原理

5.9.2 使用场景

5.9.3 格兰特小姐的控制器(Java 和 ANTLR)

5.10 第 26 章内嵌解释器

5.10.1 工作原理

5.10.2 使用场景

5.10.3 计算器(ANTLR 和 Java)

5.11 第 27 章外加代码

5.11.1 工作原理

5.11.2 使用场景

5.11.3 嵌入动态代码(ANTLR、Java 和 JavaScript)

5.12 第 28 章可变分词方式

5.12.1 工作原理

5.12.2 使用场景

5.13 第 29 章嵌套的运算符表达式

5.13.1 工作原理

5.13.2 使用场景

5.14 第 30 章以换行符作为分隔符

5.14.1 工作原理

5.14.2 使用场景

5.15 第 31 章外部 DSL 拾遗

5.15.1 语法缩进

5.15.2 模块化文法

6 第四部分 内部 DSL 主题

6.1 第 32 章表达式生成器

6.1.1 工作原理

6.1.2 使用场景

6.1.3 具有和没有生成器的连贯日历(Java)

6.1.4 对于日历使用多个生成器(Java)

6.2 第 33 章函数序列

6.2.1 工作原理

6.2.2 使用场景

6.2.3 简单的计算机配置(Java)

6.3 第 34 章嵌套函数

6.3.1 工作原理

6.3.2 使用场景

6.3.3 简单计算机配置范例(Java)

6.3.4 用标记处理多个不同的参数(C#)

6.3.5 针对 IDE 支持使用子类型标记(Java)

6.3.6 使用对象初始化器(C#)

6.3.7 周期性事件(C#)

6.4 第 35 章方法级联

6.4.1 工作原理

6.4.2 使用场景

6.4.3 简单的计算机配置范例(Java)

6.4.4 带有属性的方法级联(C#)

6.4.5 渐进式接口(C#)

6.5 第 36 章对象范围

6.5.1 工作原理

6.5.2 使用场景

6.5.3 安全代码(C#)

6.5.4 使用实例求值(Ruby)

6.5.5 使用实例初始化器(Java)

6.6 第 37 章闭包

6.6.1 工作原理

  • lambda 表达式, 代码块, 和匿名函数都可是 closure
  • 这个名词不太统一
  • 指的其实就是一种可以被赋值, 拷贝的代码逻辑 (不同于一般函数那种在编译时期就已经确定了, 代码运行的时候会被加载到内存固定位置的普通函数)

6.6.2 使用场景

6.7 第 38 章嵌套闭包

6.7.1 工作原理

6.7.2 使用场景

6.7.3 用嵌套闭包来包装函数序列(Ruby)

6.7.4 简单的 C# 示例(C#)

6.7.5 使用方法级联(Ruby)

6.7.6 带显式闭包参数的函数序列(Ruby)

6.7.7 采用实例级求值(Ruby)

6.8 第 39 章列表的字面构造

6.8.1 工作原理

6.8.2 使用场景

6.9 第 40 章 Literal Map

6.9.1 工作原理

6.9.2 使用场景

6.9.3 使用 List 和 Map 表达计算机的配置信息(Ruby)

6.9.4 演化为 Greenspun 式(Ruby)

6.10 第 41 章动态接收

6.10.1 工作原理

6.10.2 使用场景

6.10.3 积分——使用方法名解析(Ruby)

6.10.4 积分——使用方法级联(Ruby)

6.10.5 去掉安全仪表盘控制器中的引用(JRuby)

6.11 第 42 章标注

6.11.1 工作原理

6.11.2 使用场景

6.11.3 用于运行时处理的特定语法(Java)

6.11.4 使用类方法(Ruby)

6.11.5 动态代码生成(Ruby)

6.12 第 43 章解析树操作

6.12.1 工作原理

6.12.2 使用场景

6.12.3 由 C# 条件生成 IMAP 查询(C#)

6.13 第 44 章类符号表

44.1 工作原理

6.13.1 使用场景

6.13.2 在静态类型中实现类符号表(Java)

6.14 第 45 章文本润色

6.14.1 工作原理

6.14.2 使用场景

6.14.3 使用润色的折扣规则(Ruby)

6.15 第 46 章为字面量提供扩展

6.15.1 工作原理

6.15.2 使用场景

6.15.3 食谱配料(C#)

7 第五部分 其他计算模型

7.1 第 47 章适应性模型

7.1.1 工作原理

  • 命令式模型, 按照面向对象的方式组织代码

7.1.2 使用场景

7.2 第 48 章决策表

7.2.1 工作原理

7.2.2 使用场景

7.2.3 为一个订单计算费用(C#)

7.3 第 49 章依赖网络

7.3.1 工作原理

7.3.2 使用场景

7.3.3 分析饮料(C#)

7.4 第 50 章产生式规则系统

7.4.1 工作原理

7.4.2 使用场景

7.4.3 俱乐部会员校验(C#)

7.4.4 适任资格的规则:扩展俱乐部成员(C#)

7.5 第 51 章状态机

7.5.1 工作原理

  • DAG (有向无环图) 来组织依赖关系
  • dessicated class

7.5.2 使用场景

7.5.3 安全面板控制器(Java)

8 第六部分 代码生成

8.1 第 52 章基于转换器的代码生成

8.1.1 工作原理

8.1.2 使用场景

8.1.3 安全面板控制器(Java 生成的 C)

8.2 第 53 章模板化的生成器

8.2.1 工作原理

8.2.2 使用场景

8.2.3 生成带有嵌套条件的安全控制面板状态机(Velocity 和 Java 生成的 C)

8.3 第 54 章嵌入助手

8.3.1 工作原理

8.3.2 使用场景

8.3.3 安全控制面板的状态(Java 和 ANTLR)

8.3.4 助手类应该生成 HTML 吗(Java 和 Velocity)

8.4 第 55 章基于模型的代码生成

8.4.1 工作原理

8.4.2 使用场景

8.4.3 安全控制面板的状态机(C)

8.4.4 动态载入状态机(C)

8.5 第 56 章无视模型的代码生成

8.5.1 工作原理

8.5.2 使用场景

8.5.3 使用嵌套条件的安全面板状态机(C)

8.6 第 57 章代沟

8.6.1 工作原理

8.6.2 使用场景

8.6.3 根据数据结构生成类(Java 和一些 Ruby)

9 参考文献

Author: TANG ZhiXiong

Created: 2018-01-25 Thu 12:45

Emacs 25.3.1 (Org mode 8.2.10)

Validate