starwing83 发表于 2012-02-29 08:59

回复 39# OwnWaterloo


    提一点,就历史来看,ruby(严格意义上的)是perl的后继,从名字和各种蛛丝马迹就可以看出来,似乎作者也承认了,不过我找不到引用。ruby最开始的目的是为了写perl代码更方便。所以ruby提供了很多的功能,省略括号是perl的语法,不过perl有必须的分号可以很好的解析;block也是模拟perl的“匿名子过程”(就是如同map $_+$_, ,这种,注意第一个参数是表达式,而此表达式是作为匿名的lambda函数存在的),ruby身上可以看出大部分的perl的影子,甚至ruby的网络库就是比照perl的来实现的。其他语言对ruby的影响反而比较少。

starwing83 发表于 2012-02-29 09:19

回复 40# lcqtdwj


    lisp复杂程度一点都不高。我一句话就能解释明白,所谓“lisp复杂程度高”是因为lisp太灵活,你想做啥就做啥,而对于你想做的,有各种各样已经规定好的实现(包括if和lambda),而这些东西太多了,你才会觉得复杂。

lisp的本质,一句话就能说明白:程序是以S-exp表示的广义表,对程序的求值(运算、执行),本质上是对广义表的求值,这是一个递归过程:对广义表的第一个元素求值,并决定如何对其他元素进行求值。完了。

比如(display (if (< a 1) 'a 'b)),先求值(display ...),首先根据描述对display求值:他是个函数,那么函数的求值方式是对其所有后继子项求值,那么开始对(if ...)求值,if是个special-form,他的求值方式是对第二项进行求值,如果是真就对第三项进行求值,否则就对第四项进行求值………………

这个过程说起来复杂,实际上就是一个多叉树的遍历而已,任何语言都可以很简单地做到(所以说ruby能简单实现scheme真不是什么很自豪的事情),只是lisp特别灵活,它通过第一个元素对这个遍历过程进行了控制。你可以认为lisp特别适合于dfs搜索功能,当然这就是“lisp适合人工智能”这种论断的原因。

那lisp的问题在哪儿呢?在于效率。如上所说,要根据定义去执行lisp程序出乎人意料的简单,问题是性能就不好说了。ruby本来就慢,用这种方式求值lisp没什么问题,但是如果你是正儿八经去考虑高效地执行lisp程序,你不得不考虑很多问题:lisp到字节码甚至机器码的翻译(为了jit或者效率)、cons的高效存储、轻量级编译器的实现、垃圾回收的效率等等等等,这才是lisp的难点所在,为了克服这些难点,实现要么减少了lisp的灵活性(不允许修改+-*/神码的),要么体积会很大(以同时支持灵活性和效率),这才是lisp不容易实用的原因。

但是你必须承认,真正做到了这点的lisp效率是ruby的300多倍(参看programming language benchmark game),而做不到这点的lisp(如用ruby实现的lisp)本质上就是个toy而已。

最后说说ruby吧。这是我第一门脚本语言。最开始对它是很有好感的,但是因为了解到它是perl的后继,我就开始学习perl,感觉perl太难学就转了python,后来我常用perl(工作中)和python(私下里),就对ruby生疏了。作为一门perl的后继语言,ruby无疑是目标明确的——它拉低了编程的门槛(至于这个目标是不是有益去问OwnWaterloo吧),然而ruby自身也有很多问题,第一个就是翻译的问题了,第二个就是效率问题。ruby的效率低一方面用的是yacc而不是自行实现的翻译器,另一方面它的执行方式和朴素的lisp非常像——它也是执行语法树的!而且是以一种写死的遍历方式进行执行,这种方式我看是扬短避长了。它没得到lisp的灵活性,反而损失了性能,这是得不偿失的。

由此可以看出ruby对真正元编程的支持了,目前是很弱的,然而应该有足够的潜力达到很好的支持水平。而这本书里面提到的“元编程”,我觉得实在是不能被称为元编程——这种元编程不是通过生成代码来实现的,对效率没有任何额外的好处,只是“运行时获取程序所有信息”而已,最多算是“元执行”或者“元计算”。所谓元编程,即“生成程序的程序”,最初是为了可维护性(如检查)和效率(如直接生成复杂代码的执行结果),而这些在单趟的ruby模型下是做不到的,除非你用ruby去生成ruby代码:用ruby写DSL来描述需求、DSL产生数据结构、数据结构再产生ruby代码,再执行产生的ruby代码。如果是这样那还有点看头,不过ruby做到这个也就平淡无奇了,看看lua的一个绑定库的DSL吧:
require 'lbind'.export(_ENV)
require 'lbind.types'.export(_ENV)

module 'gd' {
    export = true,
    include "gd.h";
    subfiles {
      --"gdImage.bind.lua";
    };

    object "gdImage" {
      method "new" :cname "gdImageCreate"
            (int "sx", int "sy") :rets(selfType:ptr());
      method "newTrueColor" :cname "gdImageCreateTrueColor"
            (int "sx", int "sy") :rets(selfType:ptr());
      method "delete" () :alias "close" :cname "gdImageDestroy";
      method "color_allocate" :cname "gdImageColorAllocate"
            (int "r", int "g", int "b");
      method "line" :cname "gdImageLine"
            (int "x1", int "y1", int "x2", int "y2", int "color");

      include "errno.h";
      include "string.h";
      method "toPNG" (char:const():ptr "name") :body [[
            FILE *pngout = fopen(name, "wb");
            if (pngout == NULL) {
                lua_pushnil(L);
                lua_pushstring(L, strerror(errno));
                return 2;
            }
            gdImagePng(self, pngout);
            fclose(pngout);
            lua_pushboolean(L, 1);
            return 1;
      ]];
    };
};

ruby能产生这么紧凑自然的DSL么?

OwnWaterloo 发表于 2012-02-29 13:38

回复 41# starwing83

松本与Larry的基情貌似听说过……
但block一开始似乎真的只是loop construct……稍后说……

1. 语法设计很难……一个地方小赢可以导致其他地方大输……
2. 我不知道smalltalk是不是这样,但ruby也许就类似你说的那种纯粹的OO……
只有消息,木有函数……想传入一个可调用的东西必须创建一个对象,产生者与使用者约定使用call消息……


关于block与%_+$_……我问你,后者里面可不可以有return?
如果我告诉你前者不但可以有return,而且return不但可以跳出block,还可以跳出block的调用者…… 你会不会惊掉下巴……
n.times do |i|
...
return
end
或者
n.times { |i|
...
return
}
不仅仅是跳出block,还可以跳出times……   所以我说block一开始似乎真的只是为了loop……

do |i| ... end, {|i| ... } 我不知道谁先谁后……

同时,上面说了,block是一种语法:
1. 所以compose(f,g)(x)不行…… 因为后面是为block预留的……
2. block是一个隐式"参数"……又隐式…… 咋就这么喜欢隐式……一旦隐式就丢失了范性好吗……
3. 不是1st class…… 如果不是传递给那个隐式参数…… 需要某种东西将其转为1st……
于是就有Proc.new/lambda, 前者return跳出block的调用者, 后者仅跳出block……还有个proc, 在一些版本里是lambda,在后续版本里是Proc.new……

lambda {...} 在1.9里又有一种新语法……

故事没完……proc是将block包裹为一个1st class value…… 可以传递给显式参数。
还有一种语法向那个隐式参数传递……map(&...)

我还没有研究嵌套函数……变量作用域等东西……不知道会不会闹出更多喜剧……



我宁愿相信发展过程是这样……
1. do |i| ... end 为了将循环解耦合,也就是松本自己提到的loop abstraction
2. {|i| ... } 作为上面的语法糖
3. 需要不止一个可调用体了, 于是出现proc与它产生的包裹对象
4. "借鉴"lisp, 让lambda变成proc的同义词……
5. 为了面向对象…… 于是出现了 Proc class, 让Proc.new 完成原始的loop return, 让lambda完成匿名函数, 将proc从lambda改为Proc.new并废弃之……
6. 不知道1.9引入lambda的新语法后, 会不会再将 lambda给废弃掉……因为, 这可不是面向对象的东西……
7. map(&...) 这个我也不知道应该插入到上面哪个地方……

你能体会我看到松本的那个interview中那段话的心情吗……basically, block is ... loop abstraction……

宁愿相信这是因为松本没能一次性地发觉1st class function是更范化的抽象机制,配合exception也可以完成 loop return。
这最多只能说他笨。

而不是因为这就是松本真实的意图……就是要让1st class function+exception可以完成的事变得这么复杂…… 因为这样才语法"自然", 才user friendly……
我能说他是变态吗……

OwnWaterloo 发表于 2012-02-29 14:04

回复 42# starwing83

语言能改变程序员的思想……学了lisp后我发现一种新的改变…… lisp能改变程序员对语法的态度……
我现在看到各种纷乱的语法就%@#$@#$……对C++11兴趣不大也有这个原因……
我直到现在都没去研究haskell的do notation,打算对monad有更好的理解之后再说……
而且haskell社区也有人认为do notation对理解monad是有害的。因为它让描述逻辑上是sequential的东西看上去像命令式语言……

lambda {|a,b| a+b} vs ->(a, b){a + b} 语意一点没变…… 连typing都不能节省多少…… 仅仅是因为它看上去好一些……


重度依赖语法的语言会让程序员想要更多的语法,为了让代码看上去更"natural",却不能简化任何语意……
但要解释、理解这些natural代码时,终结还是得回到语意上去……

语法繁多的代价前面已经指出不少了,只是受重语法语言影响的程序员不会察觉、承认,或者根本不在乎这点,甚至引以为豪……

嗯,其实我想说,你那段lua的DSL也许根本就不会被ruby程序员接受…… 因为这些代码看上去不像一些熟悉的东西……

starwing83 发表于 2012-02-29 14:15

回复 44# OwnWaterloo


    那你觉得有语法好不好呢?反正我看见一堆括号就头疼

OwnWaterloo 发表于 2012-02-29 14:25

回复 45# starwing83

你知道的,我就是因为lisp语法怪,才选emacs而非vim的……被人捧上天了也许有其原因……
于是我得到了……
haskell我也是去找自虐的……

ruby也被人追捧…… 但我还没得到……

OwnWaterloo 发表于 2012-02-29 14:35

回复 42# starwing83

>> 实现要么减少了lisp的灵活性(不允许修改+-*/神码的),要么体积会很大(以同时支持灵活性和效率),这才是lisp不容易实用的原因。

no。 需要某种灵活性就必须得承受相应的代价,时间或空间效率。

只要允许修改+,-,那所有语言在这里都会有效率损失。
只是一些语言不允许。
cl语言允许,但cl实现可以不允许。 以前给你的那个add反汇编, 确实只有4-5条机器指令, 一些操作调用栈, 还剩一条addl, 但肯定没有call/jmp。
就像其他语言里inline的效率与灵活性一样, g中的f调用被inline, f的改变就无法影响到g, 除非重新编译g。


同样,体积大也是实现的事而不是语言的事。
cl是将编程分为develop与deliver,前者为了开发方便会在程序中加入很多很多东西。
有些实现不关注体积(比如sbcl),其他也许默认是插入了全功能的开发用的信息,你得找到将这些信息去掉的编译参数。
你想要的是默认行为是让开发功能尽可能少,让这些功能显示传入?目前没找到这样的实现, 不能证明这样的实现没有对不对?


BTW: lua里,你以前说 a+b 就会翻译为number +? 编译器怎么知道a,b没有元表?

starwing83 发表于 2012-02-29 17:08

回复 47# OwnWaterloo


    lua如果发现+-×/的两个操作数如果全都是number,则执行默认操作,不管有没有__add神马的。也就是说,数字的+-×/不允许替换掉。

starwing83 发表于 2012-02-29 17:09

回复 47# OwnWaterloo


    我就有个小问题:luaJIT的效率已经堪比大多数的lisp实现了,而且相差不大,但是luaJIT的大小是500K,你找个跟luaJIT一样大一样快的实现?我就是在说lisp这种概念阻止了很好的优化。

OwnWaterloo 发表于 2012-02-29 19:16

回复 48# starwing83

a+b, 编译 —— 而且你说是一趟编译,一边parse一边就compile了 —— 这行代码时,该产生怎样的代码?
怎么知道a,b一定是number?

或者这么说: function(a,b) return a+b end,编译后是否仅支持传入number? 是否支持传入带元表的对象?
页: 1 2 3 4 [5] 6 7 8 9 10 11
查看完整版本: 《ruby元编程》有奖试读中!(获奖名单已公布)