2007-03-04

D语言与tpl之编译期动作

关键字: 设计理论
   最近D语言发布了1.0版,这是一个由编译器开发者所设计的编译语言,语法类似C++, 但是针对C++的弊病作了大量修正,并增加了很多现代特征,其中还是有一些新意在其中的。http://www.digitalmars.com/d/overview.html 我对其比较感兴趣的部分是D语言明确提出的编译期运行的概念。虽然C++让大众了解了meta programming技术,很多人因此认为meta programming的威力在于类型演算,但是在我看来meta programming的真正作用在于在编译期可以“动态”产生或调整代码结构。它依赖于类型是因为我们只能利用类型来承载一些额外信息, 这无疑也是对于类型的一种滥用。在D语言中template所接受的不仅仅是类型, 或者类型的类型,它可以是任何符号.
  template MixInAttr(T, char[] name){ 
     mixin(" T _" ~ name ~";");
     mixin(" T "~name~"(){ return _"~name~"; }");
     mixin(" void "~name~"(T v){ _"~name~" = v;}");
  } 
 
  template MixInOtherAttr(T, T v){
    T _otherAttr = v;
   
    int otherAttr(){ return _otherAttr; }
  }
 
  const int myValue = 1;
 
  int addFunc(int x){
    return x + 1;
  }
  
  class MyTest{ 
     mixin MixInAttr!(int, "myAttr"); 
     mixin MixInOtherAttr!(int,4);
    
     static if(addFunc(myValue) 〉 0){
        int testFunc(){ return 5;}
     }
  }
 
  void main(){ 
     auto t = new MyTest;
     t.myAttr = 3;
     int v = t.myAttr;   
     v = t.otherAttr;
     t.testFunc();
  }  
  不过现在编译期运行无疑是一个正在探索的方向, 在D语言中的使用方式并不是非常理想的, 在形式和功能实现上都存在着重大改进的可能. 
 
  在witrix平台的tpl模板语言中,我们也引入了编译期运行的概念,只是tpl的语言特征非常简单再加上xml格式特殊的规范性,编译期运行在tpl中的实现是非常直接和明确的.在tpl中定义了<cp:run〉标签,它的内容在编译期运行, 输出结果(xml字符串)将被继续编译. 在<cp:run〉中可以执行任何有效的tpl代码, 它们在编译期状态空间中运行. 例如
 〈cp:run〉
   〈c:forEach var="_x" items="${tagBody.children()}"〉
       bla bla bla...
   〈/c:forEach〉
 〈/cp:run〉
在EL表达式中我们通过cp前缀实现普通调用和编译期调用的混杂.
  〈cp:const class="mypkg.MyConstantClass"/〉
  〈c:eval expr="${myFunc(cp:const.MY_CONST, commonVar, cp:funcInCompileTime())}" /〉
  〈cp:compile test="${exprInCompileTime}"〉
     any tpl ...
  〈/cp:compile〉

 其实当我们把改进的目光开始放到编译期的结构方面的时候, 可以做的工作是非常多的. 例如从结构上看, 继承策略相当是类结构的一种一阶替换策略.
  类B(methodB) extends 类A(methodA, methodB)  ==〉 类B(methodA[inA], methodB[inB])
如果我们放弃概念层面上的纠缠,而如同template那样只关注语法结构, 则可以发展出更加复杂的替换操作.
  NODE_B(                              NODE_A(                                    NODE_B(
    SUB_NODE_A1        〉〉    SUB_NODE_A1             ==〉    SUB_NODE_A1
      SUB_NODE_B11                   SUB_NODE_A11                         SUB_NODE_B11
                                                         SUB_NODE_A12                          SUB_NODE_A12
  )                                                  )                                                  )
C++中及D语言中的template技术在某种意义上相当于是在代码结构中预留了空洞, 可以实现跨越类结构的替换填充. 只是D语言挖洞的能力无疑比C++强大的多. 
  如果我们考察的再细致一些, 就会发现两个语法节点的融合也是存在多种策略的.
  B 〉〉 A ==〉 (remove_A, replace_A, insert_B_before_A, insert_B_after_A, insert_B_into_A, insert_A_into_B)
而通过insert_A_into_B/insert_B_into_A策略既可实现所谓的AOP intercept操作http://canonical.javaeye.com/blog/34941 在witrix平台的BizFlow技术中, 我们通过高级的结构融合策略实现为任意实体引入流程支持. 最简单的情况下我们所需要做的工作只是增加一个语法元素
 〈bizflow extends="myflow"〉
引入流程意味着对界面的一系列修正, 例如增加,删除某些菜单, 同时要增加很多流程相关函数, 对某些函数实施AOP操作, 在各处引入权限控制等等, 这些都可以通过extends操作引入.

  在传统上, 编译器结构是固化的, 它所编译的代码结构是固化的, 而它所编译出的代码也是固化的, 但是现代语言的各种发展正致力于在各个层面引入动态性. 编译期运行抑或是编译期结构操纵技术的引入是在一个层面上打开了潘多拉魔盒. 它让我们看到了前所未有的技术可能性, 但它无疑也是危险的,难以控制的. 我们目前的做法是尽量克制,只在局部使用, 但我相信它在很多语言中都是可以在理论上严格定义,并精确实现的, 我们最终也能够找到合适的形式来约束它的破坏性. 在这个过程中我们需要引入更多的物理化的视角。
评论
hyf 2007-05-08
一种方法是忘掉他,
在你觉得很需要他的时候,一般都是恰当时机。
weixiao 2007-05-08
hyf 写道
七猫 写道
hyf 写道
七猫 写道
我只是认为C++开发者更需要的是一大堆库,而不是一些语法上的改变。

两者都需要。
象c++Ox中的一条关于引入lambda的建议,如果不接受。像boost这样搞下去也没有活路
语法有没有,大家可以绕道走。
但没有库,就得自己写了。

其实现有的语法已经能满足大部分人需要了,对于所谓的原编程之类的,我很少看到大量使用,而且这种风格的代码的阅读性和可维护性我想大家心里都有数的。

对于要完成一个项目,库当然重要。
但依靠语法的逻辑抽象能力,我可以做到更细致的复用设计,有些是功能库做不到的。

MP之所没有大量使用,
一来是没有这种习惯,所以就没有需要
二来是现在的语法导致这样做 复杂、鸡肋

其实,现在的C++只要适当使用,不会降低可读性和可维护性。

“只要适当使用”,几个字,包含了多少血和泪。一个资质中上的程序员,没个三几年时间,如何能够“适当使用”?
qiezi 2007-03-09
qiezi 写道
hyf 写道
假设变参模板用TArgs表示
除非你有一种方法能把类型转化成字符串,实现
mixin(str(head(TArgs)) ~ " item" ~ i;

你说D语言的mixin表达式要求参数字符串必须是一个完整的表达式
head(TArgs) mixin("item" ~ i); 应该不行吧


等待你的例子。

出差在外,上网不方便,这边网吧连USB也禁了。已经完成了,加上空行和整个文件大概60行左右,包括一个简单的使用测试。

完整代码:
import std.stdio;
import std.metastrings;

private template attr_accessor(T, char[] name){
	mixin("
		private T _" ~ name ~ ";
		public T " ~ name ~ "(){
			return _" ~ name ~ ";
		}
		public void " ~ name ~ "(T v){
			_" ~ name ~ " = v;
		}"
	);
}

template attrs(T){
	mixin attr_accessor!(T, "item1");
}

template attrs(T, Args ...){
	mixin attrs_without_names!(0, T, Args);
}

template attrs(T, char[] s, Args ...){
	mixin attrs_with_names!(0, T, s, Args);
}

// 这个模板参数n是为了避免mixin递归错误。
private template attrs_with_names(int n, T, Args ...){
	static if (Args.length){
		mixin attr_accessor!(T, Args[0]);
		mixin attrs_with_names!(n + 1, T, Args[1..$]);
	}
}

private template attrs_without_names(int n, Args ...){
	static if (Args.length){
		mixin attr_accessor!(Args[0], "item" ~ ToString!(n + 1));
		mixin attrs_without_names!(n + 1, Args[1..$]);
	}
}

void main(){
	class Foo{
		mixin attrs!(int, "aaa");
		mixin attrs!(int, float, char[]);
	}

	auto foo = new Foo;
	foo.aaa = 1;
	foo.item1 = 3;
	foo.item2 = 5;
	foo.item3 = "abc";

	writefln(foo.aaa);
	writefln(foo.item1);
	writefln(foo.item2);
	writefln(foo.item3);
	writefln(typeid(typeof(foo.tupleof)));
}
canonical 2007-03-07
to Elminster:
meta programming对于C++而言是基于类型运算的,或者你把meta programming等价于C++中的类型运算。而在我的概念中,meta programming意味着对编译期运算能力的开发。在tpl语言中,编译期运算是个明确的概念,而它是符合一定的形式要求的,但是并不是类型运算,实际上在tpl中通过其他渠道来传递信息。
hyf 2007-03-07
看了你attr_accessor模板,明白你的意思了
qiezi 2007-03-07
hyf 写道

我还以为可以做到 mixin(typeof(T).fullname() ~ ...
qiezi 写道
template foo(Args ...){
    static if (is(Args[0] == char[]))
        ...
}

这个判断总是不能为真,不知是D的问题还是我使用上的问题,我改用匹配的方法:
template attrs(T, Args ...){
}

template attrs(T, char[] s, Args ...){
}

如果是手工枚举所有类型的话就没没法用于自定义类型了。


这个并不是手工枚举所有类型,而是判断第2个模板参数,如果是字符串值,就把它和后面的参数作为属性的名称。否则就把所有模板参数当作类型,依次定义item1, item2 ...

全部用字符串来生成是最差的做法,即便能够实现,实现起来也不方便。
hyf 2007-03-07
我还以为可以做到 mixin(typeof(T).fullname() ~ ...
qiezi 写道
template foo(Args ...){
    static if (is(Args[0] == char[]))
        ...
}

这个判断总是不能为真,不知是D的问题还是我使用上的问题,我改用匹配的方法:
template attrs(T, Args ...){
}

template attrs(T, char[] s, Args ...){
}

如果是手工枚举所有类型的话就没没法用于自定义类型了。
qiezi 2007-03-07
hyf 写道
假设变参模板用TArgs表示
除非你有一种方法能把类型转化成字符串,实现
mixin(str(head(TArgs)) ~ " item" ~ i;

你说D语言的mixin表达式要求参数字符串必须是一个完整的表达式
head(TArgs) mixin("item" ~ i); 应该不行吧


等待你的例子。

出差在外,上网不方便,这边网吧连USB也禁了。已经完成了,加上空行和整个文件大概60行左右,包括一个简单的使用测试。

对字符串的判断似乎有问题:
template foo(Args ...){
    static if (is(Args[0] == char[]))
        ...
}

这个判断总是不能为真,不知是D的问题还是我使用上的问题,我改用匹配的方法:
template attrs(T, Args ...){
}

template attrs(T, char[] s, Args ...){
}

来进行区分,在里面调用一个递归的mixin模板,把各个成员生成出来。在这个mixin模板中,不断调用attr_accessor模板(我的blog中有),并递归地调用同一个模板,把剩余参数传递过去,递归是模板中使用非常频繁的技术。有一点要注意的是,递归mixin在D中是不允许的,但可以传递一个多余的整数模板参数避开这个问题。

并不需要把类型转成字符串,当然如果想做也能做成,参考pyd里面的实现代码,我的blog中有一篇讲了这个,只是不知道是否适用所有类型。

最后的调用方式如下:

class Foo{
  mixin attrs!(int, "foo");
  mixin attrs!(float, "bar");
  mixin attrs!(char, char[], float, short);
}

auto foo = new Foo;
foo.foo = 1;
foo.bar = 3.0f;
foo.item1 = 'c';
foo.item2 = "cc";
foo.item3 = 5.0f;
foo.item4 = 7;
Elminster 2007-03-07

canonical 写道:
   最近D语言发布了1.0版,这是一个由编译器开发者所设计的编译语言,语法类似C++, 但是针对C++的弊病作了大量修正,并增加了很多现代特征,其中还是有一些新意在其中的。http://www.digitalmars.com/d/overview.html 我对其比较感兴趣的部分是D语言明确提出的编译期运行的概念。虽然C++让大众了解了meta programming技术,很多人因此认为meta programming的威力在于类型演算,但是在我看来meta programming的真正作用在于在编译期可以“动态”产生或调整代码结构。它依赖于类型是因为我们只能利用类型来承载一些额外信息, 这无疑也是对于类型的一种滥用。在D语言中template所接受的不仅仅是类型, 或者类型的类型,它可以是任何符号.
...

我很奇怪你为什么认为你下面列举的这些不是对类型的计算。meta-programming 所带来的新东西,就是我们可以通过一些类型或是编译时常量为参数,让编译器在编译的时候生成某些我们想要的类型。D 的 mixin 也好,C++ 的 Policy Based Design 和 Expression-Template 也好,都是这种新的表达能力的体现。

hyf 2007-03-06
假设变参模板用TArgs表示
除非你有一种方法能把类型转化成字符串,实现
mixin(str(head(TArgs)) ~ " item" ~ i;

你说D语言的mixin表达式要求参数字符串必须是一个完整的表达式
head(TArgs) mixin("item" ~ i); 应该不行吧


等待你的例子。
qiezi 2007-03-06
hyf 写道
  template MixInAttr(T, char[] name){ 
     mixin(" T _" ~ name ~";");
     mixin(" T "~name~"(){ return _"~name~"; }");
     mixin(" void "~name~"(T v){ _"~name~" = v;}");
  }  


问几个问题,
我要MixInAttr!(int, ["myAttr1", "myAttr2", ..]);
可以?

MixInAttr!(int, string, float);
得到 int item1; string item2; float item3;
可以?


完全可以,这有两个方面的问题,一是变参模板,二是对于模板参数是类型还是字符串的判断,这在D里面都是轻而易举。现在没有时间,有空我写个简单的例子。

hyf 写道

要真是那么追求code generate的能力,那就索性把整个D语言弄进编译时执行算了.

D语言目前正在把更多的东西加进编译时执行。D语言模板到现在已经和C++大不相同了,编译期计算也是为了解决以前编译时和执行时要写两套代码而作的。
hyf 写道

况且, 你展示的这些代码, 让我想起MACRO, C++不推荐使用MACRO就是因为它可以随意产生代码, 类型不安全, 可见性(可读)差.

D语言的mixin表达式要求参数字符串必须是一个完整的表达式,这和MACRO完全不同。MACRO也不是C++语言的一部分,如果你愿意,你也可以把C宏用在任何语言中,调用一个预处理器程序进行处理。这些东西都是强大的工具,会用就能用好,不会用不用它就是了。


hyf 写道

我觉得C++的MP设计非常适合,
在C++0x出来之后, 就可以更好的应付MP的复杂性. (现在已经够复杂了)
你可以去看看OpenC++, 他的元编程在语法树上做文章, 灵活性最强了. 是啥下场?

在这个世界上有好多工具可以做代码生成, (生成我还可以看到整个结果, 怎么不对一目了然)
在语言中做这种事情, 一来IDE智能提示废了, 而且没有编译期调试还玩不转.

D并不是做了这个就扔了那个,你不使用编译期执行,它还是一种类似C++的语言,一个项目中编译期执行的部分也不会太多。IDE的智能提示和调试这两种东西并不是人人都必须要用,编译期执行也还是可以写单元测试的。
canonical 2007-03-05
实际上因为C++的复杂性,以至于设计师很难有余裕在语法上再做创新。C++对于计算是够用的,但是对于描述性内容并不适当。MP其实很有用,只是受技术限制一般人想不到它的用处而已。
hyf 2007-03-05
注意,某些观点我跟你一致。
我不是说库不重要,也不是想证明Boost可读性好。

我想说语法的改进一样很重要,他的能力直接影响我们如何设计库,如何复用代码。
而这次C++改良正是要降低这些复杂性。
hyf 2007-03-05
我是说适当使用,而不是所有。

我在项目中对引入外部的模板库很谨慎,即使是STL也不是全部用上。

Boost来和Qt这些应用库很难比较,大家的方向不一样。
比方,Qt为了可用性,signal/solt是外部编译器做代码生成的。
七猫 2007-03-05
我觉得比较boost和qt的原代码可以得到一些启示。

到底哪种代码可读性和可维护性更好。
hyf 2007-03-05
七猫 写道
hyf 写道
七猫 写道
我只是认为C++开发者更需要的是一大堆库,而不是一些语法上的改变。

两者都需要。
象c++Ox中的一条关于引入lambda的建议,如果不接受。像boost这样搞下去也没有活路
语法有没有,大家可以绕道走。
但没有库,就得自己写了。

其实现有的语法已经能满足大部分人需要了,对于所谓的原编程之类的,我很少看到大量使用,而且这种风格的代码的阅读性和可维护性我想大家心里都有数的。

对于要完成一个项目,库当然重要。
但依靠语法的逻辑抽象能力,我可以做到更细致的复用设计,有些是功能库做不到的。

MP之所没有大量使用,
一来是没有这种习惯,所以就没有需要
二来是现在的语法导致这样做 复杂、鸡肋

其实,现在的C++只要适当使用,不会降低可读性和可维护性。
七猫 2007-03-05
hyf 写道
七猫 写道
我只是认为C++开发者更需要的是一大堆库,而不是一些语法上的改变。

两者都需要。
象c++Ox中的一条关于引入lambda的建议,如果不接受。像boost这样搞下去也没有活路
语法有没有,大家可以绕道走。
但没有库,就得自己写了。

其实现有的语法已经能满足大部分人需要了,对于所谓的原编程之类的,我很少看到大量使用,而且这种风格的代码的阅读性和可维护性我想大家心里都有数的。
hyf 2007-03-05
七猫 写道
我只是认为C++开发者更需要的是一大堆库,而不是一些语法上的改变。

两者都需要。
象c++Ox中的一条关于引入lambda的建议,如果不接受。像boost这样搞下去也没有活路
七猫 2007-03-05
我只是认为C++开发者更需要的是一大堆库,而不是一些语法上的改变。
canonical 2007-03-04
关于D语言的语法问题建议自己去试验, 我并不精通。

关于tpl的编译期运行能力问题,我只能说我们需要这些能力。我们也基于这些能力作了很多工作。 并不是简单的“把整个D语言弄进编译时执行"或者 使用 MACRO就可以了。 这里的关键是如何定义合适的形式约束。OpenC++的失败是它自己的问题,而不是meta programming的问题。 就如同EJB的失败不意味着它所对应的理想的失败。
canonical
搜索本博客
最近加入圈子
存档
最新评论