Domain Specific Languages / 领域特定语言
Table of Contents
- 1. ☰
- 1.1. Meta Notes
- 1.2. Books
- 1.2.1. DONE Combo #1
- 1.2.2. INPROGRESS Combo #2
- 1.2.3. DONE JavaScript: The Good Parts
- 1.2.4. DONE Domain Specific Languages
- 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
- 1.2.10. INPROGRESS ng-book2
- 1.2.11. INPROGRESS Combo #3: SLAM
- 2. 译者序 & 前言
- 3. 第一部分 叙述 best
- 4. 第二部分 通用主题
- 5. 第三部分 外部 DSL 主题
- 6. 第四部分 内部 DSL 主题
- 7. 第五部分 其他计算模型
- 8. 第六部分 代码生成
- 9. 参考文献
1 ☰
1.1 Meta Notes
1.2 Books
1.2.1 DONE Combo #1
1.2.3 DONE JavaScript: The Good Parts
1.2.4 DONE Domain Specific Languages
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
1.2.10 INPROGRESS ng-book2
1.2.11 INPROGRESS Combo #3: SLAM
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