脚本的未来
脚本的未来
1. 编程模型的比较
下面是一些关于多态、灵活性的简单例子。假设我们有如下类型定义。
class Dog {
run();
};
class Car {
run();
};
class Chair{
stand();
};
dog, car, chair 分别是这些类型的实例 instance.
我们有个公用函数。
makeItRun( param){
param.run();
}
分别调用这些实例的Run方法
A. 解释脚本,如JavaScript, Python, Ruby, FP
makeItRun( dog); // OK
makeItRun( car); // Ok
makeItRun( chair); // Runtime Error
特点: 根据符号、名字进行绑定, 运行期松耦合. 相当于语法直接支持Reflection。
B. 虚拟机编译语言,如 Java, C#
makeItRun(param){
param.run();
}
makeItRun( dog); // Compilation Error
makeItRun( car); // Compilation Error
makeItRun( chair); // Compilation Error
我们必须 让Dog 和 Car 实现相同的接口比如Runnable.
interface Runnable{
run();
}
并且这么定义
makeItRun(Runnable param){
param.run();
}
这样.
makeItRun( dog); // Ok
makeItRun( car); // Ok
makeItRun( chair); // Compilation Error
特点: 根据类型契约绑定. 编译期类型检查
C. Reflection API
我们可以使用 Reflection API 达到类似于解释脚本的效果.
makeItRun(Object param){
methodInvoke(param, “run”);
}
makeItRun( dog); // Ok
makeItRun( car); // Ok
makeItRun( chair); // Runtime Error
特点: 语法本身不支持名称符号绑定,需要使用Reflection API, 这些API由Class 结构元数据支持.
D. C++ Template
<class T>
makeItRun(T param){
param.run();
}
makeItRun( dog); // Ok
makeItRun( car); // Ok
makeItRun( chair); // Compilation Error
特点: 编译期名称、符号绑定. Very cool.
注意: Java范型不支持这种方式。Java范型进行编译期类型检查。也是类型绑定的。C++ Template并不是真正的范型编程,只是一种代码生成机制。
总结:
运行期名称符号绑定,并不适合大规模复杂系统开发,因为没有编译期类型检查,不支持Java Interface那样的契约编程。
至于Ruby on Rails, 很像一个简单的代码生成器.
对于大型系统,比起运行期名字绑定,C++ Template 的编译期绑定也许更适合,至少有编译期检查。
解释脚本最适合哪些领域?
解释脚本是运行期名称绑定,松耦合。
什么系统需要这样的特性?
对了. SOA, Web Services, such buzzwords 定位服务名称,调用注册的对象方法.
2. 脚本的可能未来
(1) 服务器段流程控制
JavaScript, Ruby, Python, FP 支持Continuation. 有时用作状态机流程控制。
Continuation 甚至比状态机. 可以跳到运行栈的任何一层。 (类似于Exception Throw/Handling).
个人观点,并不欣赏这种使用方法。因为web-based services应该设计为尽量无状态的。状态应该尽量保持在客户端,比如AJAX/Flex. 有人说,如果有人刷新了URL,客户端保存的所有的中间状态都会丢失,最好服务器段记录所有的中间状态,这样用户下次可以回到上次的断点。对于long-session 的应用,这是对的. 但是对于long-session 的应用来说,为了均衡负载,系统更应该被设计为无状态的。所以,使用脚本的Continuation特性,仍然没有太大的意义。
(2) Web Service 客户端
AJAX/Flex 也可以看作Web Service 客户端.
脚本调用Web Service,我欣赏这种用法。用脚本表达Web服务调用非常方便清楚,不需要Reflection API 和 Code 生成。
(3) 把Mobile Code 从 客户端迁移到 服务端
假设我们有如下的Web Service客户端脚本,
If(service.do() == ok) {
nextService.do();
nextService2.do();
}else{
backService.do();
backService2.do();
}
这通常意味着要访问三次Web Service Server. 每一次需要组装一次SOAP 或者XML-RPC 消息.
把整个Script一次发送过去如何?
服务器解释整个脚本,一次执行所有的服务请求,一次返回最终结果。
这涉及到安全问题。幸好脚本比机器码或者VM指令更容易管理和控制,解决这个问题应该不难。
(4) DSL, LOP ?
Domain Specific Languages 如Business Rule, Workflow Definition 要求用户友好,更加像英语,而不是编程语言。
普通用户喜欢 关键字,而不是 API。
If a greater-than b then do … // keywords way
If(greater(a, b)) { … // API way
脚本是更高级的语言,通常比编译语言更加友好。一些脚本(如 Lisp, Scheme, etc)可以定义更多的关键字。//HashTable datas = new Hashtable();;datas.Add("价格", 120);;datas.Add("数量", "5");;//Intents its = new Intents();;its.datas = datas;its.Add("总额 = 价格 * 数量");;//Executor exe = new Executor();;exe.intents = its;//Result ret = exe.Execute();;decimal total = 0;if(ret.ret == true);total = Convert.ToDecimal(datas.Get("总额"););;elseTellMeWhy(ret.error);; 18 楼 Elminster 2005-11-25 Trustno1 写道对于大量操纵数据的应用 静态类型无疑是非常合适的,但是需要操纵逻辑本身的应用,就要让位于动态语言。
大量操纵数据的程序,一般来说,这些数据的类型都是确定的
而程序逻辑,本身就是一种抽象行为,抽象行为要付足实施,必须要具显化
在抽象行为和具显化之间,动态性是不可缺少的
这个很难说。程序即数据,如果我们把包含代码的函数当作一种数据结构,那么也是可以给它指定类型的,而且也可以推算它们之间组合的类型。不过缺点是做不出象 lisp 中的 eval 这样的东西 …… 19 楼 Elminster 2005-11-25 age0 写道C++之所以被称为“坚硬而脆弱”,就是因为静态类型的缘故。由静态类型创建的结构确实是非常安全,只是这种静态结构是可以说是anti adaptive的,对于大部分的商业系统来说,对adaptive的要求远比静态安全来的高,而且从逻辑检查方面去完善安全机制会比依赖静态安全更加有保障。
“把逻辑错误转换成类型错误,保证有错误的代码不可能通过编译”这种想法不错,只是逻辑是复杂而变化无常的,因此再怎么努力也只能将有限的逻辑错误转换成类型错误,最终还是要依赖逻辑检查才能发现全部的逻辑错误。
无论如何,adaptive和静态安全是对立的两面,如何取舍相信也不难作出判断。
……
我倒是认为,根据逻辑的类型来分类就可以了:稳定的逻辑(比如物理计算),不稳定的逻辑(各种各样的商业规则)。如果逻辑非常稳定,例如科学计算或者有固定协议之类的场合,适合使用静态类型,可以创建非常稳健而高效的系统。对于复杂多变的商业规则,动态语言则是当仁不让,否则一个小变动都可能让静态系统变得支离破碎。
商业规则多变是多变,复杂我是觉得谈不上。这里的关键是对于你要表达的逻辑,你是否存在一个清晰、稳定的基础框架。比方说前面物理计算的例子,物理计算可能千变万化,但都可以用几个基本因素表达出来,所以建立几个基本类型之后,就能很容易地组合演化 —— 这个有点象前些日子 ajoo 鼓吹的 CO 。 20 楼 age0 2005-11-25 Elminster 写道
商业规则多变是多变,复杂我是觉得谈不上。这里的关键是对于你要表达的逻辑,你是否存在一个清晰、稳定的基础框架。比方说前面物理计算的例子,物理计算可能千变万化,但都可以用几个基本因素表达出来,所以建立几个基本类型之后,就能很容易地组合演化 —— 这个有点象前些日子 ajoo 鼓吹的 CO 。
你所说的情况其实还是属于稳定逻辑的范围之内,物理规则是非常稳定的,而ajoo鼓吹CO时引用的也仅仅是log例子,还没有涉及Bussiness Domain。Bussiness Doamin里面的逻辑基本上是很难预测的,举个例子:一个业务系统刚开始的时候由3个业务对象组成,我们根据现有需求合理的构造了它们之间的关系。随着潜在需求的不断发掘,后来发现需要引入第4个业务对象,这个对象打破了原有对象之间的均衡关系,最后不得不重新调整对象之间的关系以维持结构的合理性。除非在需求阶段可以捕捉所有需求,否则这种情况难以避免,使用静态系统只会雪上加霜。 21 楼 ajoo 2005-11-25 Elminster 写道
这两个问题,前一个是语言设施的问题:C++ 的 TMP 是人们逐渐挖掘出来的能力,可以说设计之初并没有有意识地在这里提供支持,用起来确实不方便,不仅有你说的调试查错信息的问题,也有难于书写和理解的问题。所以我一直是盼着有人可以在这里做点工作的 —— 原本我还寄希望于你的 C Generic ,没想到啊没想到 ……
我这人比较喜欢完美。要是做的东西避免不了一堆缺陷,就宁可不做。
CG我原来的设想是利用protocol来完全地用tp代替OO的多态,这对类型系统的要求其实很强。
但是经过很久的构思后,我发现我没有能力构造一个足够简单,灵活,并且安全的系统。我原先的设想太理想化了。很多问题不好解决。
也许,放弃对类型安全的执着转而提供一个好用的调试器是个更可行的思路,可惜,这已经完全和我最初的设想背道而驰了。
不知道D在这方面是否符合你的期待?
Elminster 写道
至于后一个,实际上就是之前提过的静态类型安全和灵活性之间冲突的问题。这个可以通过设计来部分避免,比如你的 lowerCase / Filter / Parse 在处理的时候,都让它们处理一个“字符的序列”而不是一个 string 的对象 —— 这在 C++ 的 STL 里面已经是标准的设计了。如果你没那么幸运,一大堆现有代码已经是那个样子了,那么也可以用 immutable 手法,把基本的 string 当作 unsafe_string ,然后 safe_string 设计成 immutable 的。
说实话,不懂你说的是什么意思。我相信,即使在c++中,你也做不到这个“类型安全”。你那个安全的代价几乎就是“什么也不能做”的别称。和pascal的数组有的一拚。嘿嘿。 22 楼 ajoo 2005-11-25 Elminster 写道Trustno1 写道对于大量操纵数据的应用 静态类型无疑是非常合适的,但是需要操纵逻辑本身的应用,就要让位于动态语言。
大量操纵数据的程序,一般来说,这些数据的类型都是确定的
而程序逻辑,本身就是一种抽象行为,抽象行为要付足实施,必须要具显化
在抽象行为和具显化之间,动态性是不可缺少的
这个很难说。程序即数据,如果我们把包含代码的函数当作一种数据结构,那么也是可以给它指定类型的,而且也可以推算它们之间组合的类型。不过缺点是做不出象 lisp 中的 eval 这样的东西 ……
haskell就是一个典型了。只不过,haskell的类型系统也相当复杂。不仅有type, 还有type class,居然还有kind。不优雅呀。 23 楼 ajoo 2005-11-25 age0 写道Elminster 写道
商业规则多变是多变,复杂我是觉得谈不上。这里的关键是对于你要表达的逻辑,你是否存在一个清晰、稳定的基础框架。比方说前面物理计算的例子,物理计算可能千变万化,但都可以用几个基本因素表达出来,所以建立几个基本类型之后,就能很容易地组合演化 —— 这个有点象前些日子 ajoo 鼓吹的 CO 。
你所说的情况其实还是属于稳定逻辑的范围之内,物理规则是非常稳定的,而ajoo鼓吹CO时引用的也仅仅是log例子,还没有涉及Bussiness Domain。Bussiness Doamin里面的逻辑基本上是很难预测的,举个例子:一个业务系统刚开始的时候由3个业务对象组成,我们根据现有需求合理的构造了它们之间的关系。随着潜在需求的不断发掘,后来发现需要引入第4个业务对象,这个对象打破了原有对象之间的均衡关系,最后不得不重新调整对象之间的关系以维持结构的合理性。除非在需求阶段可以捕捉所有需求,否则这种情况难以避免,使用静态系统只会雪上加霜。
co的设计不是完全根据现有需求。而是根据一些概念的内在属性。虽然它也有自身的局限性,但是你说的仍然是OO的问题,和CO靠不上边。 24 楼 Elminster 2005-11-25 ajoo 写道我这人比较喜欢完美。要是做的东西避免不了一堆缺陷,就宁可不做。
CG我原来的设想是利用protocol来完全地用tp代替OO的多态,这对类型系统的要求其实很强。
但是经过很久的构思后,我发现我没有能力构造一个足够简单,灵活,并且安全的系统。我原先的设想太理想化了。很多问题不好解决。
也许,放弃对类型安全的执着转而提供一个好用的调试器是个更可行的思路,可惜,这已经完全和我最初的设想背道而驰了。
不知道D在这方面是否符合你的期待?
不知道 D 怎么样,很久没在这个上面花过功夫了。
ajoo 写道说实话,不懂你说的是什么意思。我相信,即使在c++中,你也做不到这个“类型安全”。你那个安全的代价几乎就是“什么也不能做”的别称。和pascal的数组有的一拚。嘿嘿。
不懂?我觉得很容易理解呀,在输出之前,你就直接用基本的 string ,爱用什么用什么 —— 这个总没有问题吧?这里基本的 string 实际上扮演了 unsafe_string 的角色。然后输出的地方只接受 safe_string 为参数,而它只能通过 Encode 从基本 string 构造,没有其他任何构造方式 —— 极端地说,你不可能通过 Encode 以外的任何函数来得到一个新的 safe_string 对象 —— 这样就能保证安全性。最后,我们加一个函数,可以从一个 safe_string 对象拷贝得到一个 string 对象,完工。
你接着挑刺吧。 25 楼 ajoo 2005-11-26 就是说,encode一旦调用,就啥也不许干了,只能输出。因为你拿到的safe_string就是一个单行线,只有输出一条路。
其实,如果这样,为什么不更简单一点?直接让write()函数内部自己encode。效果一样,而用户再也不用关心safe_string的问题了。 26 楼 Elminster 2005-11-26 ajoo 写道就是说,encode一旦调用,就啥也不许干了,只能输出。因为你拿到的safe_string就是一个单行线,只有输出一条路。
其实,如果这样,为什么不更简单一点?直接让write()函数内部自己encode。效果一样,而用户再也不用关心safe_string的问题了。
嘿嘿,我看到这个例子的第一感觉就是这么做,不过为了拿它来说明静态类型安全的用法,呵呵,当然不会提这个啦 …… 咳咳。不过 ajoo 你不觉得让 write() 自己来 encode 有点 quick & dirty 么?效率太差了,而且味道上也不好。 27 楼 ajoo 2005-11-26 倒是看不出来效率有什么差别.你那个safe_string构造之后也只能write,这和在write内部encode效率是一样的。
唯一的区别就是你可能重用某一个safe_string重复调用write,不过不知道为什么会需要这么做。
其实这个safe_string最大的问题就是灵活性。比如,如果是string,我完全可以做个decorator,搞些buffer之类的东西。而如果safe_string只能调用write,就没这个希望了。 28 楼 age0 2005-12-02 ajoo 写道co的设计不是完全根据现有需求。而是根据一些概念的内在属性。虽然它也有自身的局限性,但是你说的仍然是OO的问题,和CO靠不上边。
确实,这是面向Domain的OO设计所引发的问题,本来面向Domain设计的卖点是可以直观的反映Domain Model,但采用这种直接映射方式的结果是导致Domain Model的变化将毫无阻隔的转化为对象系统的变化。举个例子:有个销售模型开始的时候仅由customer、product和order构成,无论我们是否将其设计为对象,这三个Bussiness Concept都是客观存在。接下来,由于需要对客户进行分类,鉴别客户忠诚度以提供不同等级的服务,引入了member的Concept。这意味着Domain Model发生了变化,Biussiness Concept之间的关系必须作出相应调整,对于面向Domain的设计来说,这是一个大麻烦。那么CO如何解决这个问题呢?在理想情况下,无论Domain Model如何改变,CO系统都应该不会产生结构性的变化,也就是说不会有新的interface或者class产生并加入系统,要做到这一点,意味着CO的抽象高度必须超越Domain这一层次,你的CO理论有没有考虑过这方面的可行性呢? 29 楼 frankensteinlin 2005-12-02 ajoo 写道在理想情况下,无论Domain Model如何改变,CO系统都应该不会产生结构性的变化,也就是说不会有新的interface或者class产生并加入系统,要做到这一点,意味着CO的抽象高度必须超越Domain这一层次,你的CO理论有没有考虑过这方面的可行性呢?
上面ajoo说到的例子,我们可以先考虑一下现实世界,加入member之后,处理order product customer 这些业务的人,他们是否需要重新了解member(暂时把member看成现实世界的表格)表格的作用,和它的存在对原有 处理order product customer业务的影响,操作流程是否有影响?订单表格是不是要多填一点,。。。。
不应该要求业务变化了,软件系统不变化,软件的就像将小说改编成拍摄电影,要忠于原著。
什么是忠于原著?
当现实中order变化了,那么软件中业只有order对象发生变化。如果它要影响到product那么product组件肯定要发生变化。
软件适应变化的能力不是当业务变化了它能保持不变。而是他变化的范围要和现实结构中的变化的范围一致,不要现实中变化了一点,程序确要改一堆!
强类型就意味这定义明确的合同(相对弱类型来说)合同中说明:未成年人不得进入,那么只要你是未成年人就不得进入,而弱类型有时候只有当发现未成年人受了伤害,才发现进来了个未成年人。
软件的灵活性不是看它改动了多少,而是看在可预见的风险范围内能作出的有效改动,脚本的修改带来的风险一遍比教大 30 楼 ajoo 2005-12-07 frank...
你引用的好象不是我说的呀。认错人啦————
举个例子,规则引擎,大家知道吧?
开发规则引擎的人需要知道某个项目的具体需求么?关心你的终端客户提出的一个古怪需求么?为什么规则引擎可以灵活地处理很多事先不需要知道的需求?其实和co是一个道理。
你关注的是在一个更高的抽象层次上提供足够小巧灵活的基础设施以及组合能力,这种组合能力会自动适应需求变化。
co,你可以把它看作是直接写在宿主语言(java)里面的简易规则引擎。 31 楼 age0 2005-12-07 ajoo 写道frank...
你引用的好象不是我说的呀。认错人啦————
举个例子,规则引擎,大家知道吧?
开发规则引擎的人需要知道某个项目的具体需求么?关心你的终端客户提出的一个古怪需求么?为什么规则引擎可以灵活地处理很多事先不需要知道的需求?其实和co是一个道理。
你关注的是在一个更高的抽象层次上提供足够小巧灵活的基础设施以及组合能力,这种组合能力会自动适应需求变化。
co,你可以把它看作是直接写在宿主语言(java)里面的简易规则引擎。
不如就以我说的例子来演示一下CO或者规则引擎的概念吧,最终目的应该是要将业务逻辑转化为标准逻辑对象的实例,然后由规则引擎来解释运行。
面向domain的OO设计通常需要分析、捕捉domain object,然后定义职责关系,这些domain object之间可能会构成复杂的对象体系,一个完整的bussiness intent经常会牵涉到多个domain object。如果业务发生变化,就不得不调整domain object的属性、接口及行为,甚至重新分配职责,最终导致了系统结构的不稳定性。
如果能够设计出一套稳健的规则引擎,那么业务变化对系统的影响将会降至最低,DSL的目的其实也一样,不过CO建立在现有系统架构之上,因此会更加容易实现并且可以和现有系统结合的更加紧密。