深入解读Ruby DSL概念
对于一个初学Ruby语言的朋友来说,对于Ruby DSL的理解还不是很清晰。在这篇文章中我们将会为大家详细介绍有关Ruby DSL的一些使用方法。
DSL是一种专注于某一特定领域的语言,使用通用语言(如C或者Java)当然可以得到与DSL相同的功能。但是这样会产生大量繁琐的代码并导致大量的领域知识被隐藏在通用语言构造中(如for循环,if条件,方法调用,import声明等等)。
生成以及维护通用语言编写的代码本身也是问题所在:专家们必须将他们的知识变为代码。通常这些专家(销售人员,经理或者园丁)缺少编程知识,这意味着他们必须与程序员进行协作。当然,这也意味对代码的任何一次修改都包括许多繁琐的步骤:如果一个领域专家需要改变某些功能,她必须首先与程序员进行交流,由程序员实现这些修改,然后领域专家检查这些代码是否表现出期望的行为,诸如此类。
对于上述问题,其中一个可能的解决方案是开发一种适应特定环境的语言,非技术人员可以用它来解决问题。这种语言较通用语言更简洁并且仅仅提供目标领域所需要的类型以及特定的语言构造。
在访谈中,Obie将DSL类比为自然语言中的俚语或者行话。全世界喜爱咖啡的人对下面这句话一定非常熟悉:
Venti half-caf, non-fat, no foam, no whip latte
在正常的对话中,上述语言不能传递正确的语义。并且世界上大多数的咖啡馆里可能仅仅提供最普通的牛奶加咖啡。但是在合适的场所使用上述语言(例如,星巴克),它将让你使用最少的词语喝到想要的饮料并且很少会造成误解。
有多种方式可以实现DSL。其中一个选择是定义一种语法并使用解析器生成工具(如ANTLR或者YACC)来生成解析器。通过它可以将Ruby DSL代码转化为某种可以被解释的数据结构(语法树)。其中一个例子是Make文件,它被用来定义构建过程(编译,打包,部署)。
另一种方式是使用XML而不是解析器,它将对于解析器生成工具的依赖变为对于XML解析器的依赖,有许多工具可以用来支撑XML处理(使用DOM解析器可以得到类似语法树的数据结构,同时使用XPATH来提取数据,等等)。Ant,是一个基于XML的DSL, 构建过程被定义为XML格式。Make和Ant就是外部DSL最典型的实现。
另一个解决方案是内部Ruby DSL,它不需要使用任何解析器生成工具以及XML解析器,取而代之的是使用现有的合法的通用语言的构造,很明显,这种语言的构造必须非常灵活才可以容纳各种简练的DSL代码。
内部DSL的终极解决工具非LISP以及类LISP语言莫属,原因是LISP灵活的语法,它可以被概括为:
原子
几乎任何字符都可以成为原子,如foo,:::bar:::甚至加号都是合法的原子
零个或多个原子组成的序列
放入圆括号中
LISP宏特性使得开发者可以很轻易的在LISP中定义DSL并且将其解释或者展开为可以执行的通常的LISP代码。
另外一种适于使用内部DSL的语言是Ruby, 没什么可惊奇的,想想Ruby丰富的语法吧,它是Ruby得以支持内部Ruby DSL的部分特性。下面是Obie在PPT上所使用到的观点以及相应的示例代码:
方法调用中括号可以省略。这看起来没什么大不了的, 但是它使得下面的声明成为合法的Ruby代码:
order = latte venti, half_caf, non_fat, no_foam, no_whip在上面的例子中,latte(拿铁咖啡)以及我们所需要的特别的口味儿都是方法调用的一部分。latte方法返回一个将咖啡各个属性进行了初始化的对象。
类的定义在载入时可以被动态执行
class RuleSet < ActiveRecord::Base has_many :commends, :dependend => :delete_all # ... more... end这是一个内部DSL在Rails的ActiveRecord中的例子。has_many调用会在类第一次被载入时执行。这个调用被用来设定类的关联以及行为,例如,它可以通过define_method调用向类添加一些方法。这样这段Ruby DSL代码的用户可以使用非常简洁,描述性的方式来定义类某些方面的行为。事实上,这与LISP宏有很多相似的地方,他们都在代码被载入时进行工作。
简洁的代码块书写格式(Block)
块是自包含的Ruby代码。它可以被保存,当作参数传递并在以后执行,这个概念也被称做是匿名方法,lamda演算或者闭包(Closures),在Ruby中向方法传递块非常简洁紧凑,它使得实现特定语言构造变得非常容易,Rake是与Make和Ant非常相似的构建工具,我们来看一个例子: