Ruby 语言的一大特色就是其非常强大 DSL 能力。
Ruby 语言的一大特色就是其非常强大 DSL 能力。
那什么是 DSL 呢?英文全称为 "Domain-Specific Language", Martin Fowler 对其的定义为:
Domain-specific language (noun): a computer programming language of limited expressiveness focused on a particular domain.
大家可以想象一下,一门用来解决特定领域问题的语言,它相对于通用领域的语言,所面对的问题是有限的,因此相对而言,领域特定语言的表达是更简洁的,因为使用领域特定语言就相当于默认已经给定了上下文。
领域特定语言分为外部 DSL 和内部 DSL,我们要讲解的 Ruby 语言编写的 DSL 就属于内部 DSL。
上面讲过了,DSL 是用于解决特定领域问题的语言,那什么是特定领域呢?举个例子,Rails 可以说就是一门 DSL , 因为它就是 Web 开发这一特定领域的一套解决方案。
如果按照 Rails 给定的思路写程序,那么就能用较少的代码,完成更多的工作。
下面我会以命令行应用的 UI 为例来介绍一下 DSL 的用法。
要使用领域特定语言, 就一定要知道 领域问题 是什么。
以构建命令行应用为例,要面对的一个领域问题就是 UI 的构建。
命令行的表现能力是有限的,因此对于命令行的 UI, 基本也有一套固定的思路。
和 RESTFul 有一套固定格式类似,命令行应用也是有一套自己的参数格式的。
一个命令后应用可以被分为 Executable
Options
Arguments
这么几个部分。
先以下面这个命令行应用为例子:
grep --ignore-case -r "some string" /tmp
grep
也就是被执行的程序
--ignore-case
和 -r
,前者是 long-form
,后者是 short-form
。同一个 option 可以同时拥有 long-form 和 short-form 两种写法。
some string
和 /tmp
实际上更复杂的命令行程序还有 Command 这个概念,不过这里不作讨论。
解决命令行应用的 help UI 的构建问题,设定好 options 后,可以方便的打印出各项 options 的用法。
这里不做具体实现了,就写点伪代码。
一般的思路无非就是:
# mygrep
options = {
ignore_case: {"-i", "--ignore-case", true, "Perform case insensitive matching."}
recurse: {"-r", "--recursive", false, "Recursively search subdirectories listed."}
}
class OptionsParser
def initialize(options)
# ... parse
end
def to_s
puts "Usage: mygrep [options]"
options.values.each do |opts|
puts "\t #{opts[0]}, #{opts[1]} \t #{opts[2]}"
end
end
end
创建一个散列表来储存 options 的信息,然后解析之,最后打印出来。
ruby mygrep.rb
Usage: mygrep [options]
-i, --ignore-case Recursively search subdirectories listed.
-r, --recursive Perform case insensitive matching.
先看看 Ruby 标准库自带的 OptionParser 是怎么做的
# mygrep.rb
# !/usr/bin/env ruby
require 'optparse'
options = {}
option_parser = OptionParser.new do |opts|
options[:ignore_case] = false
opts.on('-i', '--ignore-case', 'Recursively search subdirectories listed.') do
options[:ignore_case] = true
end
options[:recursive] = false
opts.on('-r', '--recursive', 'Perform case insensitive matching.') do
options[:recursive] = true
end
end.parse!
运行结果:
ruby mygrep.rb -h
Usage: mygrep [options]
-i, --ignore-case Recursively search subdirectories listed.
-r, --recursive Perform case insensitive matching.
怎么样,是不是可读性上了一个档次。
在下面这个例子中, 我们要创建一个用于 "模拟" git 提交记录的 DSL, 类似于 git 中的 Lorem Ipsum.
比如说为了讲解 git 的分支使用方式, 我可能需要一个用于教学的 git 仓库, 如果真的自己手动一次一次地提交, 或者写成脚本都是很枯燥乏味的. 如果有如下的方式来方便地构建一组提交记录, 那想必是极好的,DSL 的写法如下:
# git-dsl.rb
GitLorem.new do
master do
mentor "init rails project"
student "update README"
layout do
mentor "import reactjs"
student "import bootstrap"
end
devise do
student "init devise"
mentor "add devise user model"
end
mentor "add heroku config"
end
end
可以看到 DSL 的表达力非常强,一个产品的开发流程就这样展示出来了。
下面就是具体的实现,这一定不是最好的实现,但足够理解了
class GitLorem
attr_accessor :branch_names
def initialize(&block)
@branch_names = []
instance_eval(&block)
end
private
def current_branch_name
@branch_names.last
end
def method_missing(name, *args, &block)
if block_given?
@branch_names << name
branch_name = name
instance_eval(&block)
else
commit(name, current_branch_name, args.first)
end
@branch_names.pop if branch_name == current_branch_name
end
def commit(committer, branch, message)
puts "提交者: #{committer} 在分支 #{branch} 创建了提交: #{message}"
end
end
运行结果如下:
ruby git-dsl.rb
提交者: mentor 在分支 master 创建了提交: init rails project
提交者: student 在分支 master 创建了提交: update README
提交者: mentor 在分支 layout 创建了提交: import reactjs
提交者: student 在分支 layout 创建了提交: import bootstrap
提交者: student 在分支 devise 创建了提交: init devise
提交者: mentor 在分支 devise 创建了提交: add devise user model
提交者: mentor 在分支 master 创建了提交: add heroku config
限于篇幅,本文到此为止,有兴趣的同学可以自己实现 commit 方法,让这段代码可以变成真正有用的脚本。 最后引用《Ruby元编程》中的一句话:
根本没有什么元编程,从来只有编程而已。
异步社区是一个有料、有货,又专业的IT专业图书社区,在这里可以读到最新、最热的IT类图书!我想要社区的《JavaScript框架设计(第2版)》 这本书,这本书是深度学习领域奠基性的经典图书,请大家帮我点赞!