浏览 9236 次
锁定老帖子 主题:消息传递:从风格到机制
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2007-08-18
引用 这是最终确定的 JavaScript 基于消息传递编程风格的文章“OOP 诡异教程(上)”的下篇,它的 Python 改写版本就是 尝试用Python实现消息传递编程风格。原文地址:(豆瓣:http://www.douban.com/group/topic/1669427/ 博客:http://let-in.blogspot.com/2007/06/oop.html)。原来的想法是以风格开头,谈到 JavaScript 的内部机制,但作者 lichray 迟迟没有动键盘,认为不如利用已有的风格做一套机制出来,这样可能更有意义。于是,就有了这个更加“诡异”的下篇,展示了一个更加“诡异”的招数。
引用 这篇文章的宗旨是利用我们仅有的“宾谓”语法构造出完整的一套面向对象机制,所以更多代码在更多的时候是不应在实际工作中使用的(也算一种元语言抽象),所以类似效率、代码风格之类的问题反对回帖质疑。
四. 扩展的实现 上篇最后给出了一个“看上去很美”的基于消息传递的编程风格,比如构造一个 People 类的代码类似: function People () { var money = 0 function setMoney (dollars) { money = dollars } function pay (dollars) { money -= dollars } return (function (verb) { return eval(verb) }) } 有了这样的语法我们就可以描述不少句子了。但是存在一个问题:现实中的 Objects 之间是存在关系的——比如,forrest 是个 IQ 为 75 的傻子,傻子是 People 的一种。而我们仅仅是生搬硬套了一种语法而割裂了这种 "is-a" 关系。现在我们的工作,目的之一就是让这样一个“真切”的世界从我们已有的编程风格的地基上拔地而起。 到底应该怎样做才能使 Fool 产生的对象都能响应 People 的消息呢?我们要给 Fool 产生的对象(也就是返回的那个匿名函数啦)都添加这样一种能力:如果在 Fool 中响应不了消息,那就反馈给 People 响应。 function Fool (iq) { var IQ = iq || 0 function init (iq) { IQ = iq } return (function (verb) { try { return eval(verb) } catch (e) { return People()(verb) } }) } js> forrest = Fool() js> forrest('init')(75) js> forrest('IQ') 75 js> forrest('money') 0 五. 语法扩展和代码生成 这下代码量增加了很多,强迫潜在的使用者们在创建每个类时都这样写那实在是令人抓狂。本来这篇文章应该不提此类问题的解决,但考虑到有益于读者理解“机制”这个抽象概念,这里给出一个可行的方案——把普通的类代码用 Function() 函数重编译为可用的 JavaScript 函数。也就是说,我们能给出类扩展的代码并指定被扩展的类来获取类似上文的代码: Fool = extend('People()', function (iq){ var IQ = iq || 0 function init (iq) { IQ = iq } }) 为了方便字符串操作,我们希望编译后的代码的参数部分(如 People())都集中出现在一个位置且尽可能便于定位。在函数头添加一句 var origin = People() 当然是可行的,这样还能使 Fool 内部显式引用到其超类。但这样还不够漂亮。我们修改编译后的样例代码为: function () { return (function (origin) { var IQ = 0 function init (iq) { IQ = iq } return (function (verb) { try { return eval(verb) } catch (e) { return origin(verb) } }) })(People()) } 这个利用参数传递变量的小技巧不值得学习,实际效率不高。但在这篇文章中,这样绑定特殊变量的技术是标准方案。 那么,我们的语法扩展兼代码生成函数 extend() 的实现为: function extend (originc, code) { function argsArea (code) { // 题外话,正则表达式也有不值得一用的时候 return code.slice(code.indexOf('(')+1, code.indexOf(')')) } function bodyCode (code) { // 不用 trim() 了,别没事儿找事儿 return code.slice(code.indexOf('{')+1, code.lastIndexOf('}')) } function format (body) { var objc = bodyCode(function () { return (function (verb) { try { return eval(verb) } catch (e) { return origin(verb) } }) }.toString()) return 'return (function (origin) {'+body+objc+'})('+originc+')' } var $ = code.toString() return Function(argsArea($), format(bodyCode($))) } 这样前文提到过的 extend 的实例代码就可以正常运行了,测试代码不再重复。 六. 机制完备化 这样,我们的基于消息传递编程风格的一套面向对象机制就确定下来了。机制是宪法,是语言的根本大法,有了它,我们就可以通过修改代码生成器,很快地给这套机制进行完备化。 想法有很多,例子只举两个。 第一个例子:类的定义中应该能直接引用到将产生的对象 self。答案只有一句话:把返回的那个作为对象的匿名函数命名为 self。 第二个例子:既然是单继承模式,应当存在一个顶层类 AbsObj,使没有指定继承的类自动继承它。答案是:在 extend 函数体第一行添加代码: if (arguments.length == 1) { code = originc originc = 'AbsObj()' } 然后手工构造设计 AbsObj 类,为空也无所谓。不过当然了,一般都会给顶层类添加一些全局性质的消息绑定。由于是“底层操作”,基本上都需要修改 extend 函数。做了一个简单的: function AbsObj () { //检测是否能响应此 verb,要再用一次异常处理 function canHandle(verb){ try { // 别担心这里的 self 会传递不过去 self(verb) } catch (e) { return false } return true } function toString() {} // 这个搞起来其实很麻烦~` var self = function (verb) { return eval(verb) } return self } js> Obj=extend(function(){x=5}) js> o=Obj() js> o('canHandle')('x') true js> o('canHandle')('y') false 文章写完了,小结一下。消息传递的编程不仅仅是一种代码风格,还可以成长为一种完备的机制。这种完备性远不只是这两篇加起来不到300行的文章所能覆盖的(例如非常彻底的“万物皆对象”,因为只要是能响应消息的函数,连接一下 AbsObj 就是合法对象了;类,函数都可以),大家可以试着玩一玩,顺便体会一下这个计算模型的透明和强大。 另外,熟悉函数式编程的朋友可以帮忙思考一下:这样一个基于闭包变换的计算模型实质上是函数式的,再配合动态的函数式的对象级继承(用一个匿名类代换一下)就能在纯 FP 真正下实现 OOP 了。可惜的是每一次更新操作都要重新生成对象,性能代价大了点,yet another question. 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2007-08-19
嗯,'return (function (origin) {'+body+objc+'})('+originc+')' 这一句可能还是换成
['return (function (origin) {', body, objc, '})(', originc, ')'].join('') 比较好吧,快一点。 我测试了一下,发现消息转发的时候特别慢,因为要刻意地使用异常处理。不过好像也没别的办法,eval() 不到肯定抛异常。 算了,不讨论这些了,LZ 说了“反对回帖质疑”“效率”什么的呢,还是想想有关“消息传递”思想本身,嗯。 |
|
返回顶楼 | |
发表时间:2007-09-21
哈哈,我帮你顶, 楼主, 悄悄地告诉你, 我也是个变态的js爱好者, 那玩意儿的语法成份少, 可描述能力巨强。
|
|
返回顶楼 | |
发表时间:2007-09-22
哦对了,我还用 Scheme 宏写了一个类似的系统,没人提醒我都给忘了,拿来晒晒。
;; 这里只有部分定义,快速排序和二分法查找就不帖在这儿了 ;; 这里是 slot 类型的定义,Scheme 中不能动态 eval,只能用点别的招数 (define (make-slot name act) (cons name act)) (define (slot-name s) (car s)) (define (slot-act s) (cdr s)) (define (slot<? s1 s2) (symbol<? (slot-name s1) (slot-name s2))) (define (symbol<? a b) (string<? (symbol->string a) (symbol->string b))) ;; 主要的转换宏,语法比较丰富,但至少带有一个 that 块 ;; 语法可以只给出 that 块:(class (that (slot-name1 slot-act1) ...)) ;; 也可以指定继承:(class extended-obj (that (slot-name1 slot-act1) ...)) ;; 在 that 块之后还可以加入 where 块,这样可以指定不对外可见的类属性 ;; 需要注意的是,where 块中的定义不能引用 self 等特殊命名,that 才行 ;; 另外支持外部引用 origin, has-slot?, slot-names 3个特殊命名,余者被隐匿 (define-syntax class (syntax-rules (that where) ((_ that-block) (class (absobj) that-block)) ((_ org that-block (where def ...)) (letrec (def ...) (class (absobj) that-block))) ((_ org (that (slot val) ...)) (lambda () (letrec ((slot val) ...) (let* ((origin org) (slots (list-qsort slot<? (list (make-slot (quote slot) slot) ...))) (slot-names (list->vector (map slot-name slots))) (slot-acts (list->vector (map slot-act slots))) (has-slot? (lambda (v) (vector-bsearch symbol<? v slot-names))) (self (lambda (verb) (let ((index (has-slot? verb))) (if (> index -1) (vector-ref slot-acts index) (case verb ('origin origin) ('slot-names slot-names) ('has-slot? has-slot?) (else (origin verb)))))))) self)))) ((_ that-block where-block) (class (absobj) that-block where-block)))) ;; 这里需要注意的是,因为嫌烦,absobj 被实现为了一个空壳子 (define (absobj) (lambda (verb) (display "This object can't handle ") (display verb) (newline))) 测试一下: > (define c1 (class (that (x 10)))) ; no values returned > (define c2 (class (c1) (that (z 16) (y 12)))) ; no values returned > (define o2 (c2)) ; no values returned > (o2 'x) 10 > (o2 'non-slot) This object can't handle non-slot #{Unspecific} > (o2 'slot-names) '#(y z) > (o2 'origin) #{Procedure 8647 (self##497 in c1)} 顺便提一句,我用的是 Scheme 48 虚拟机。完整的可执行版本在附件中有。 消息传递真的很有意思。留个小题目,增加一个特殊方法 clone,把继承机制调整为差异继承+纯基于对象,类似 IO 语言。 PS: 有变态的 Scheme 爱好者也多交流! |
|
返回顶楼 | |
发表时间:2007-09-25
用Lysee 1.1.0实现一下,条条大陆通罗马:
class people // Lysee 1.1.0 { private string _name; private money _money; private hashed _hs; new(string name, money m) { this._name = name; this._money = m; this._hs = hashed(); } public variant ___GETPV(string ID) { return this._hs[ID]; } public void ___SETPV(string ID, variant value) { this._hs[ID] = value; } } // 可能的操作 public people run(people p) { = p._name + ": I am running\n"; return p; } public people jump(people p) { = p._name + ": I am jumping\n"; return p; } public people gain(people p, money m) { p._money = p._money + m; = p._name + ": I gained", m , "dollors and have", p._money, "dollors now\n"; return p; } public people pay(people p, money m) { m = p._money - m; p._money = m < 0 ? 0 : m; = p._name + ": I payed", m , "dollors and ", p._money, "dollors left now\n"; return p; } // 初始化函数 public people People(string name, money m) { people p = people(name, m); p.run = curry(run, [p]); p.jump = curry(jump, [p]); p.gain = curry(gain, [p]); p.pay = curry(pay, [p]); return p; } // Clone 一下 public people Clone(people who, string name) { return People(name ?? who._name, who._money); } main: people forest = People("forest", 100); forest.run().jump().gain(1000).pay(33); Clone(forest, "amin").run().jump().gain(40).pay(999); // 打印内容 forest: I am running forest: I am jumping forest: I gained 1000 dollors and have 1100 dollors now forest: I payed 33 dollors and 1067 dollors left now amin: I am running amin: I am jumping amin: I gained 40 dollors and have 1107 dollors now amin: I payed 999 dollors and 108 dollors left now 当然run, jump, gain等这些函数也可以作为People()的闭包注册到people对象中,但使用curry()可以形成更好的机制 |
|
返回顶楼 | |
发表时间:2007-09-26
晕了...楼主发帖的意思是怎样设计一个面向对象系统(而且代码还是函数式风格的),想说明消息、智能对象的本质是什么。那否则最后补发 Scheme 宏的帖子干吗,现在你们常见的语言不都有 class 嘛。而楼上除了用一个 curry() 函数把顺次调用玩儿成了之外其它全在秀 Lysee Script 自有的面向对象机制。怎么,想就此开个语言入门讲座吗?
|
|
返回顶楼 | |
发表时间:2007-09-26
Beag.Ye 写道 晕了...楼主发帖的意思是怎样设计一个面向对象系统(而且代码还是函数式风格的),想说明消息、智能对象的本质是什么。那否则最后补发 Scheme 宏的帖子干吗,现在你们常见的语言不都有 class 嘛。而楼上除了用一个 curry() 函数把顺次调用玩儿成了之外其它全在秀 Lysee Script 自有的面向对象机制。怎么,想就此开个语言入门讲座吗? 伙计,不用太认真。Lysee是我开发的,我也从不指望用它挣钱,只是因为个人爱好一直开发到现在。Lysee差不多一天一个版本,每次都实现一个小的概念,目前的方向是:1、通过开发脚本引擎验证我对FP编程和Script运行架构的理解。 2、对照编程,找到Lysee还不能实现的编程模式。 3、收集反馈信息,改进Lysee设计,有时不妨班门弄斧一把。 ***** 希望你能对Lysee的开发提出意见,谢谢! 今天释出的Lysee实现了一个通过闭包修改函数局部变量的概念: public void box(variant proc) { int b = 10; proc(); = b, eol; } public void change() { setsv("b", 1000); } main: box { b = b * b }; box ("b=b+1"); box (change); // 输出 100 11 1000 这个操作在Ruby中是通过传递代码块实现的,Lysee模仿了一下,同时提供了显示操作的API。 |
|
返回顶楼 | |
发表时间:2007-09-26
一个简单的消息实现:
public hashed objects = hashed(); class Object { private int _handle; public void run() { = this._handle, "- I am RUNNING\n" } public void jump() { = this._handle, "- I am jumping\n" } public void process(varpair msg) { switch(msg.last) { case "run": this.run(); case "jump": this.jump(); default: = "unknow message\n" } } new(int handle) { this._handle = handle; objects[handle] = this; // 注册消息接收者 } } public void dispatch(varpair msg) // (first=handle, last=message) { objects[msg.first].process(msg); } main: // 注册对象 10.times {|int index| Object(index + 1000)}; // 发布消息 10.times { int handle = sys::random(10) + 1000; dispatch((handle, (handle % 10 == 1) ? "run" : "jump")); }; // 输出 1000 - I am jumping 1001 - I am RUNNING 1005 - I am jumping 1006 - I am jumping 1005 - I am jumping 1003 - I am jumping 1004 - I am jumping 1001 - I am RUNNING 1004 - I am jumping 1009 - I am jumping 感觉还是越简单越好 |
|
返回顶楼 | |
发表时间:2007-09-26
楼上是来推销新语言的鉴定完毕。
楼主最后留的那个 clone 的问题太简单了吧?想一想,一个产生类本身的函数,不就是类自己吗? (define-syntax class (syntax-rules (that where) ((_ that-block) (class (absobj) that-block)) ((_ org that-block (where def ...)) (letrec (def ...) (class (absobj) that-block))) ((_ org (that (slot val) ...)) ; 把类自己,也就是表示类的这个函数命个名 clone (let ((clone (lambda () (letrec ((slot val) ...) (let* ((origin org) (slots (list-qsort slot<? (list (make-slot (quote slot) slot) ...))) (slot-names (list->vector (map slot-name slots))) (slot-acts (list->vector (map slot-act slots))) (has-slot? (lambda (v) (vector-bsearch symbol<? v slot-names))) (self (lambda (verb) (let ((index (has-slot? verb))) (if (> index -1) (vector-ref slot-acts index) (case verb ('origin origin) ('slot-names slot-names) ('has-slot? has-slot?) ; 再把 clone 加入可响应的特殊命名列表里 ('clone clone) (else (origin verb)))))))) self))))) ; 最后把它返回出来 clone)) ((_ that-block where-block) (class (absobj) that-block where-block)))) 这样就行啦! 另外,我还发现一个很有意思的事情。因为类可以是匿名的,所以可以通过虚拟的类直接产生对象: (define-syntax object (syntax-rules () ((_ ...) ((class ...))))) 这样一来,基于 prototype 的类+对象的模式有了,基于 clone +差异继承的方式也有了,这个面向对象系统的表达能力相当于 JavaScript+Lua(或者Io)。双料的,有意思。。。。把 mixin (不要多继承了,反正差不多)再加上的话,3模式系统,嗯。。 |
|
返回顶楼 | |
发表时间:2007-09-29
LS 的想法是对的,就是这么回事,只是宏写错了,应该是:
(define-syntax object (syntax-rules () ((_ that ...) ((class () that ...))))) 我写了一个那样的“三模式系统”。 ; 在一个正常的 Scheme 实现中,undefined 都是一个特殊的符号 ; 但因为在 R5RS 中没有特别指明,不如把它提取出来再用 (define undef (if #f #f)) (define (absobj) (define (clone verb) (define mixin undef) (case verb ('slot-names '()) ('clone clone) ('oncur (lambda (me) (set! mixee me))) ('has-slot? (lambda (n) -1)) ; 这个长长的条件判断说明,这里的 mixin 是把 mixee 当作 mixer 的元表处理的 ; 也就是说,mixin 绝不是继承的变形,它没有 mixin 链,更不会顺着继承链向上找 (else (if (and (not (eq? mixee undef)) (> ((mixee 'has-slot?) verv) -1)) (mixee verb) undef)))) clone) 完整代码太长,见于附件。以下是测试示例: > ; 这一行说明了一个小小变革,就是 class 必须带参数了,整个 clone 函数作为构造函数 > (define c1 (class () (that (x 13)))) ; no values returned > (define o1 (c1)) ; no values returned > (o1 'x) 13 > (define c2 (class () (that (y 15)))) ; no values returned > ; 这里的革新是,设置 mixin 对象的方法名为 oncur > ((o1 'oncur) (c2)) #{Unspecific} > ; 这是 mixin 的效果;这也是我不喜欢 mixin 的原因,因为代码不是函数式的了 > ; 不过并不是说 mixin 一定要实现为命令式风格的,只是这样比较高效而已 > (o1 'y) 15 > 另附:我在写升级版本的时候发现 self 有问题,根本不能使用,正在全力解决中! |
|
返回顶楼 | |