Hello World
Spiga

编程语言的发展趋势及未来方向(2):声明式编程与DSL

2010-04-14 21:24 by 老赵, 17821 visits

这是Anders Hejlsberg(不用介绍这是谁了吧)在比利时TechDays 2010所做的开场演讲。由于最近我在博客上关于语言的讨论比较多,出于应景,也打算将Anders的演讲完整地听写出来。在上一部分中,Anders指出语言本身在过去的数十年里并没有明显的发展,并给出了他眼中编程语言发展趋势的预测。在现在的第2部分中,Anders将阐述声明式编程的理念及DSL,并演示C#中一种内部DSL的形式:LINQ。

如果没有特别说明,所有的文字都直接翻译自Anders的演讲,并使用我自己的口语习惯表达出来,对于Anders的口误及反复等情况,必要时在译文中自然也会进行忽略。为了方便理解,我也会将视频中关键部分进行截图,而某些代码演示则会直接作为文章内容发表。

(听写开始,接上篇

这里先从声明式(Declarative)编程谈起。

目前我们在编写软件时大量使用的是命令式(Imperative)编程语言,例如C#,Java或是C++等等。这些语言的特征在于,写出的代码除了表现出“什么(What)”是你想做的事情之外,更多的代码则表现出实现的细节,也就是“如何(How)”完成工作。这部分代码有时候多到掩盖了我们原来问题的解决方案。比如,你会在代码里写for循环,if语句,a等于b,i加一等等,这体现出机器是如何处理数据。首先,这种做法让代码变得冗余,而且它也很难让执行代码的基础设施更聪明地判断该如何去执行代码。当你写出这样的命令是代码,然后把编译后的中间语言交给虚拟机去执行,此时虚拟机并没有多少空间可以影响代码的执行方式,它只能根据指令一条一条老老实实地去执行。例如,我们现在想要并行地执行程序就很困难了,因为更高层次的一些信息已经丢失了。这样,我们只能在代码里给出“How”,而不能体现出“What”的信息。

有多种方式可以将“What”转化为更为“声明式”的编程风格,我们只要能够在代码中体现出更多“What”,而不是“How”的信息,这样执行环境便可以更加聪明地去适应当前的执行要求。例如,它可以决定投入多少CPU进行计算,你的当前硬件是什么样的,等等。

我之前提到过,现在有两种比较重要的成果,一是DSL(Domain Specific Language,领域特定语言),另一个则是函数式编程

其实DSL不是什么新鲜的玩意儿,我们平时一直在用类似的东西,比如,SQL,CSS,正则表达式,有的可能更加专注于一个方面,例如MathematicaLOGO等等。这些语言的目标都是特定的领域,与之相对的则是GPPL(General Purpose Programming Language,通用目的编程语言)。

对于DSL而言其实并没有一个明确的定义,在这里我也不打算为它下个定义,例如UML甚至根本没有特定的语法。不过我这里会谈一些我觉得比较重要的东西。

Martin Fowler提出DSL应该分为外部DSL及内部DSL两种,我认为这种划分方式还是比较有意义的。外部DSL是自我包含的语言,它们有自己特定语法、解析器和词法分析器等等,它往往是一种小型的编程语言,甚至不会像GPPL那样需要源文件。与之相对的则是内部DSL。内部DSL其实更像是种别称,它代表一类特别API及使用模式。这里我会给你们看一些示例。

这些是我们平时会遇到的一些外部DSL,如这张幻灯片上表现的XSLT,SQL或是Unix脚本。外部DSL的特点是,你在构建这种DSL时,其实扮演的是编程语言设计者的角色,这个工作并不会交给普通人去做。外部DSL一般会直接针对特定的领域设计,而不考虑其他东西。James Gosling曾经说过这样的话,每个配置文件最终都会变成一门编程语言。你一开始可能只会用它表示一点点东西,然后慢慢你便会想要一些规则,而这些规则则变成了表达式,可能你还会定义变量,进行条件判断等等。而最终它就变成了一种奇怪的编程语言,这样的情况屡见不鲜。

事实上,现在有一些公司也在关注DSL的开发。例如以前在微软工作的Charles Simonyi提出了Intentional Programming的概念,还有一个叫做JetBrains的公司提供一个叫做MPS(Meta Programming System)的产品。最近微软也提出了自己的Oslo项目,而在Eclipse世界里也有个叫做Xtext的东西,所以其实在这方面现在也有不少人在尝试。

我在观察外部DSL时,往往会关注它的语法到底提供了多少空间,例如一种XML的方言,利用XML方言的好处在于有不少现成的工具可用,这样可以更快地定义自己的语法。

而内部DSL,正像我之前说的那样,它其实只是一系列特别的API及使用模式的别称。这里则是一些LINQ查询语句,Ruby on Rails以及jQuery代码。内部DSL的特点是,它其实只是一系列API,但是你可以“假装”它们一种DSL。内部DSL往往会利用一些“流畅化”的技巧,例如像这里的LINQ或jQuery那样把一些方法通过“点”连接起来。有些则利用了元编程的方式,如这里的Ruby on Rails就涉及到了一些元编程。这种DSL可以访问语言中的代码或变量,以及利用如代码补全,重构等母语言的所有特性。

现在我会花几分钟时间演示一下我所创建的DSL,也就是LINQ。我相信你们也已经用过不少LINQ了,不过这里我还是快速的展示一下我所表达的更为“声明式”的编程方式。

public class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public string CategoryName { get; set; }
    public int UnitPrice { get; set; }

    public static List<Product> GetProducts() { /* ... */ }
}

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        List<Product> products = Product.GetProducts();

        List<Product> result = new List<Product>();
        foreach (Product p in products)
        {
            if (p.UnitPrice > 20) result.Add(p);
        }

        GridView1.DataSource = result;
        GridView1.DataBind();
    }
}

这里有许多Product对象,那么现在我要筛选出所有单价大于20的那些, 再把他们显示在一个GridView中。传统的做法就是这样,我先得到所有的Product对象,然后foreach遍历每个对象,再判断每个对象的单价,最终把数据绑定到GridView里。运行这个程序……(打开页面)这就是就能得到结果。

好,那么现在我要做一些稍微复杂的事情。可能我不是要展示单价超过20的Product对象,而是要查看每个分类中究竟有多少个单价超过20的对象,然后根据数量进行排序。如果不用DSL完成这个工作,那么我可能会先定义一个对象来表示结果:

class Grouping
{
    public string CategoryName { get; set; }
    public int ProductCount { get; set; }
}

这是个表示分组的对象,用于保存分类的名称和产品数量。然后我们就会写一些十分丑陋的代码:

Dictionary<string, Grouping> groups = new Dictionary<string, Grouping>();
foreach (Product p in products)
{
    if (p.UnitPrice >= 20)
    {
        if (!groups.ContainsKey(p.CategoryName))
        {
            Grouping r = new Grouping();
            r.CategoryName = p.CategoryName;
            r.ProductCount = 0;
            groups[p.CategoryName] = r;
        }
        groups[p.CategoryName].ProductCount++;
    }
}

List<Grouping> result = new List<Grouping>(groups.Values);
result.Sort(delegate(Grouping x, Grouping y)
{
    return
        x.ProductCount > y.ProductCount ? -1 :
        x.ProductCount < y.ProductCount ? 1 :
        0;
});

我先创建一个新的字典,用于保存分类名称到分组的对应关系。然后我遍历每个Product对象,对于每个单价大于20的对象,如果字典中还没有保存对应的分组则创建一个,然后将数量加一。然后为了排序,我调用Sort方法,于是我要提供一个委托作为排序方法,然后blablablabla……执行之后……(打开页面)我自然可以得到想要的结果。

但是,首先这些代码写起来需要花费一些时间,很显然。然后仔细观察,你会发现这写代码几乎都是在表示“How”,而“What”基本已经丢失了。假设我离开了,现在新来了一个程序员要维护这段代码,他会需要一点时间才能完整理解这段代码,因为他无法直接看清代码的目标。

不过如果这里我们使用DSL,也就是LINQ,就像这样:

var result = products
    .Where(p => p.UnitPrice >= 20)
    .GroupBy(p => p.CategoryName)
    .OrderByDescending(g => g.Count())
    .Select(g => new { CategoryName = g.Key, ProductCount = g.Count() });

products……先调用Where……blablabla……再GroupBy等等。由于我们这里可以使用DSL来表示高阶的术语,用以体现我们想做的事情。于是这段代码则更加关注于“What”而不是“How”。我这里不会明确地指示我想要过滤的方式,我也不会明确地说我要建立字典和分类,这样基础结构就可以聪明地,或者说更加聪明地去确定具体的执行方式。你可能比较容易想到我们可以并行地执行这段代码,因为我没有显式地指定做事方式,我只是表示出我的意图。

我们打开页面……(打开页面)很显然我们得到了相同的结果。

这里比较有趣的是,内部DSL是如何设计进C#语法中的,为此我们为C# 3.0添加了一系列的特性,例如Lambda表达式,扩展方法,类型推断等等。这些特性统一起来之后,我们就可以设计出更为丰富的API,组合之后便成为一种内部DSL,就像这里的LINQ查询语言。

除了使用API的形式之外,我们还可以这样做:

var result =
    from p in products
    where p.UnitPrice >= 20
    group p by p.CategoryName into g
    orderby g.Count() descending
    select new { CategoryName = g.Key, ProductCount = g.Count() };

编译器会简单地将这种形式转化为前一种形式。不过,这里我认为有意思的地方在于,你完全可以创建一门和领域编程语言完全无关的语法,然后等这种语法和API变得流行且丰富起来之后,再来创一种新的表现形式,就如这里的LINQ查询语法。我颇为中意这种语言设计的交流方式。

OK,现在我们回到下面的内容。

(未完待续)

相关文章

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

36 条回复

  1. KevinXi
    117.82.99.*
    链接

    KevinXi 2010-04-14 21:32:02

    传说中的沙发!

  2. lparam
    116.30.197.*
    链接

    lparam 2010-04-14 21:53:07

    太短了,能不能一下发完

  3. 老赵
    admin
    链接

    老赵 2010-04-14 21:58:57

    @lparam

    三千多字了,说短也不短了。

  4. 潜水冠军
    111.165.27.*
    链接

    潜水冠军 2010-04-14 22:52:35

    很馋人,期待后文~。~

  5. 链接

    Ivony 2010-04-14 23:09:06

    James Gosling曾经说过这样的话,每个配置文件最终都会变成一门编程语言。

    原来已经被老大讲过了。。。。

    殊途同归,当我在研究复杂的配置文件时,我发现配置文件和程序设计语言是一样的东西。

    例如这样一个XML:

    <configuration>
      <Book Author="Ivony">
        <Title>Nothing of nothing</Title>
        <Content>Nothing</Content>
      </Book>
      ...
    </configuration>
    

    它其实等同于:

    var configuration = new Book[]
    {
      new Book()
      {
        Title = "Nothing of nothing",
        Content = new TextContent( "Nothing" )
      },
      ...
    }
    

    当然,也等同于:

    var configuration = 
    [
      {
        Title : "Nothing of nothing",
        Content : "Nothing"
      },
      ...
    ];
    
  6. pixysoft
    121.32.25.*
    链接

    pixysoft 2010-04-14 23:31:57

    看意思,DSL 应该是规则引擎的意思。不知道这样理解是否正确。

    例如SQL、BPEL等,都是一种规则引擎,还有正则表达式等等。

    如果软件发展到一定规模,有很多业务逻辑能被有限规则描述,那么DSL会有用的。不过貌似现在还没有到达这个阶段。

    UML算是比较伟大的一步了。可以把复杂的需求退化成为有限的图形符号。如果能再进一步,有可能会成为主流。

  7. 链接

    kuo 2010-04-14 23:33:50

    这好像不是博客园了,老赵搬家了

  8. zzfff
    222.53.141.*
    链接

    zzfff 2010-04-14 23:57:59

    DSL、声明式...太泛太没感觉了,技术人员需要实打实的感觉,研究Axum语言的设计理念及编译器的实现更让人踏实。 reflector Axum及Oslo的DLL挺有趣的,可惜我认知太肤浅,吹不上牛。 强烈等待Oslo的下一个CTP。

  9. 老赵
    admin
    链接

    老赵 2010-04-15 00:02:09

    @zzfff

    分享一下你的体会吧:)

    我现在觉得,Axum似乎很难产品化,它是一个基于C#语法的Messaging Passing的语言,但是微软现在也已经提出了F#这个在这方面表现很好的通用编程语言,那么Axum的定位又是什么呢?

  10. zzfff
    222.53.141.*
    链接

    zzfff 2010-04-15 00:15:24

    @老赵

    我并不关心Axum到底domain-specific了什么(尽管并发这个domain非常有趣),我关心的是:domian的解决方案如何能与C#/CLR完美无缝的结合在一起,你可以去看下Axum的programmers guide,然后冥思背后是怎么实现的。实现Axum的compiler实际上需要实现大半个C# compiler。

  11. zzfff
    222.53.141.*
    链接

    zzfff 2010-04-15 00:34:25

    Expression Trees v1实现了expressions的semantic model,ET v2实现了statements的semantic model,据说,ET next version将实现declarations(class,method...)的semantic model,那时再叫ET不太合适了。

    另:白手实现C# compiler基本上是mission impossible,我读C# spec读得脑壳晕....R大大呢? 再另:Axum好像不是meta-programming的,有了ET v3,meta-programming不是梦,关于meta-programming,我的理解是:code as data,data as code。ET就是data,我设计的语言就是前一个code,生成的C#/Java/Python/ruby...就是后一个code。

  12. zzfff
    222.53.141.*
    链接

    zzfff 2010-04-15 01:03:48

    “domain的解决方案与C#/CLR完美无缝的结合在一起”的原因是显而易见的:建设这个世界的方法是对它添砖添瓦,而不是在一块荒野上白手起家建成22世纪的文明世界,我承认自己不是上帝,那些自认为(或潜意识里)是上帝的人,通常是xx:)

  13. 老赵的粉丝
    60.183.71.*
    链接

    老赵的粉丝 2010-04-15 08:45:37

    好文, 拜读了,期待下文……

  14. 暗香浮动
    61.148.100.*
    链接

    暗香浮动 2010-04-15 09:24:40

    其实 wf 也可以看成是dsl的一种应用.

  15. xiaoetmac
    222.66.14.*
    链接

    xiaoetmac 2010-04-15 09:34:47

    还是直接看video更有感觉,啊哈~

    就是在讨论internal DSLS和external DSLS的时候没太听懂~

    然后就赶紧跑过来看你的文章有没有续集了~

    支持支持~

  16. kula
    211.101.48.*
    链接

    kula 2010-04-15 09:59:53

    这就是在挤牙膏阿 能不能一次出完..

  17. aesenc
    122.84.124.*
    链接

    aesenc 2010-04-15 11:10:25

    嗯,程序员用起来很方便。希望你可以做一下和传统写法之间的性能比较。

    用LINQ作性能分析,会不会比较困难?而且即使查到了性能瓶颈在用LINQ的这一段,也能难深入分析原因。

    最后,LINQ显然不鼓励系统相关的编程,利用我对硬件特点方面的知识,进行特定性能优化,是不可能的吧。

  18. 老赵
    admin
    链接

    老赵 2010-04-15 11:30:11

    @aesenc

    很早就比较过过了,性能和自己写for没有什么太大差别,只是额外分配了一些小对象,多了几次call而已,在实际应用中几乎占不了份额,更别说成为性能瓶颈了(其实比如GroupBy操作甚至性能更好,除非自己用麻烦的方式写)。

    其实你在托管环境下编程,在一定程度上已经对硬件特点回避了,这点由虚拟机负责了。当然也不能说是完全和硬件无关,也可以把握的了一部分。还有就是,我可能不知道你说的“系统相关”编程是什么,其实也已经有了使用LINQ进行并行计算或利用显卡计算的项目和产品。要说起来LINQ倒真没有“影响”托管项目和系统编程的“相关程度”。

  19. zzfff
    222.53.140.*
    链接

    zzfff 2010-04-15 11:42:54

    我并不关心Axum是做什么的,我关心的是它是怎么做的。如果说Axum = C# + parallel's semantics,那么MyLanguage = C# + some domain's semantics。如果说Axum这种添砖添瓦法是未来之路,作为一个实在且有理想的人,先啃C# spec,再深入some domain。

    1000个观众眼中有1000个哈姆雷特,我在煽情的兜售我看到的哈姆雷特。

  20. 北方的独狼
    203.110.178.*
    链接

    北方的独狼 2010-04-15 12:02:34

    有点意思。感觉自己落伍了很多。。。。

  21. 老赵
    admin
    链接

    老赵 2010-04-15 13:07:09

    @zzfff

    我感觉其实应该是能够自由扩展语法的语言,一个补充包,就变成了一种新的领域语言。并发也好,其他什么也罢。

  22. 戏水
    124.207.144.*
    链接

    戏水 2010-04-15 14:20:55

    老赵: 你用的博客程序 有个错字 :

    “登陆后便可删除或修改已发表的评论” 登录写成 登陆了哦 ,改改

  23. Ivony
    124.193.150.*
    链接

    Ivony 2010-04-15 16:19:58

    我觉得要发明一种语法,提供充分的隔离,使得语言的各个部分可以独立发展。

    譬如说:

    public class Test<这里面是另一种语言>
    

    VB.NET的XML嵌入的确是值得关注的,它将两种完全不同的语言混在一起了,这比LINQ的步子迈得更大。

    考虑下面的语法,我会觉得很舒服:

    public class Test< as DuckType >
    {
      dynamic property
      {
        set { ... }
        get { ... }
      }
    }
    
  24. 链接

    ZJH135 2010-04-15 18:33:01

    好象Ander要在宣传他的C#... 太抽象的语言远离底层会让人一头雾水。。。现在就有人说C#比C还难学,还难理解,只有强制地记忆它。 太抽象的语言只能向智能方向发展,而C/C++类的语言会向控制硬件方向发展,也可以控制抽象语言的发展方向。。。,而成为不是语言的语言 计算机程序语言(真正的计算机语言)是一种回归,回到与我们朝夕相处的硬件上来。。。,就象都市人回归大自然一样。。

  25. chenruibuaa
    123.120.18.*
    链接

    chenruibuaa 2010-04-15 21:39:13

    DSL本质上就是模型驱动的开发方式 在不同的领域模型不同,因此需要Domain-specific,但是DSL也脱离不了程序设计语言,只是计算机科学的更高层次的抽象(计算机科学其实本质上就是各种抽象)。 目前一些领域已经是模型驱动的开发方式占据主流了,比如嵌入式领域,NASA的很多航天软件50%都是这样开发的,是用DSL(Simulink等)先建模,然后自动生成代码,安全性可控,并能够自动验证

  26. 老赵
    admin
    链接

    老赵 2010-04-15 22:06:15

    @ZJH135

    Anders倒不是宣传他的C#,只是因为C#本就是他的理想而已。关于语言我倒正是和您的看法相反,这里没有所谓“回归”,因为硬件本身其实是独立发展的,C自然比C#语言,因为硬件本身的能力很简单,C语言只是为了和CPU本身做好简单的映射而已。可以说,世界上任何一种可用的高级语言,都会比C语言来的复杂,但是掌握后的生产力比C语言高出许多,呵呵。

  27. zzfff
    222.53.140.*
    链接

    zzfff 2010-04-15 22:38:16

    @老赵

    这包含语法及语义两部分,很复杂,我也在黑暗中摸索,总之,编译原理是逃不掉的,以及对host language(我杜撰的词)的深入理解。等C# 5.0吧,或者F#?我不熟悉。

    现在玩玩Oslo中的M语言到是挺有趣的,只要对编译原理有点点谱,造们配置语言或小玩具语言是易如反掌,推荐这个例子。

    ANTLR肯定也行,但我钟情made in ms:)

  28. 老赵
    admin
    链接

    老赵 2010-04-15 22:47:21

    @zzfff

    呵呵,有想法就及时分享吧,写点博客下来。

    话说host language不是你杜撰的,正好Anders在这个演讲中也用过。他所指的就是内部DSL所用的语言,例如LINQ这种DSL的Hosting Language就是C#。

  29. zzfff
    222.53.140.*
    链接

    zzfff 2010-04-15 22:51:44

    @老赵

    我重头到尾都隐含指的是External DSL。太懒,不想写,主要的原因是高不成低不就,来灌灌水就够了

  30. 链接

    宁静 2010-04-16 19:07:41

    接不上话,mark一下

  31. qiaojie
    116.227.166.*
    链接

    qiaojie 2010-04-17 02:16:59

    WPF中大量的使用声明式编程,XAML是WPF的DSL,但是现在发现声明式编程其实是把双刃剑,过量的使用会从另一方面带来不必要的复杂性,你会搞不清程序到底是怎么工作的,知其然不知其所以然的问题会很突出,不出问题还好,一旦出了问题,要定位和除错就很痛苦。

  32. 1-2-3
    123.191.111.*
    链接

    1-2-3 2010-04-26 09:04:09

    3、3、3、3、3、3!

  33. 老赵
    admin
    链接

    老赵 2010-04-26 09:44:19

    @1-2-3

    唉,那个Why Java Sucks也是件大事……

  34. 1-2-3
    123.191.105.*
    链接

    1-2-3 2010-05-10 11:35:21

    @老赵

    那个Why Java Sucks很不错,只是我都替你感到寂寞,这么久都没见着有力的反击。只恨我Java不熟,不然我都想扮演回反派了。

  35. 三
    110.96.152.*
    链接

    2010-05-13 12:30:09

    Ivony 又讲的和我想得一样,哈哈。

    不仅仅是想,lisp早就可以这么做了,一个lisp程序完全可以用另一个lisp程序做为配置文件。

  36. peach5460
    119.96.121.*
    链接

    peach5460 2013-05-15 13:30:06

    学习受教了...目前还只会C++,呵呵

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我