Archive

Archive for March, 2009

Tail Recursion in C [一]

March 27th, 2009 :: ph4nut

看了《写出正确的尾递归代码》一文,对尾递归的作用有了表面上的理解,但是关于编译器如何优化尾递归为一条跳转指令以及对递归堆栈的处理还不是很清楚。所以有了这篇小文,有什么错误的地方还请各位看官指出。

先来看一下在C语言里一个尾递归的例子:

  1. #include <stdio.h>
  2.  
  3. void add( int  n)
  4. {
  5.     printf( " %d\n",n);
  6.     add(n + 1);
  7. }
  8.  
  9. int main()
  10. {
  11.     add(0);
  12.     return 0;
  13. }

编译这段代码:gcc -o  add.exe add.c

如果运行这个程序的话,当n增加到一定值的话,程序就会推出,因为栈空间不够。

再来用gcc的O2选项(这个选项增加了对尾递归的优化)来优化这段代码并运行:gcc -o add.exe add.c  -O2

如果你运行这个程序,你会发现一直打印递增的n,而没有停止。

接下来我们来比较一下没有优化尾递归和优化后的汇编代码。

未优化尾递归的汇编代码:gcc -S add.c

  1.  .file "add.c"
  2.  .section .rdata,"dr"
  3. LC0:
  4.  .ascii " %d\12"
  5.  .text
  6. .globl _add
  7.  .def _add; .scl 2; .type 32; .endef
  8. _add:
  9.  pushl %ebp
  10.  movl %esp, %ebp
  11.  subl $8, %esp
  12.  movl 8(%ebp), %eax
  13.  movl %eax, 4(%esp)
  14.  movl $LC0, (%esp)
  15.  call _printf
  16.  movl 8(%ebp), %eax
  17.  incl %eax
  18.  movl %eax, (%esp)
  19.  call _add  ;注意这里,add函数的调用
  20.  leave
  21.  ret
  22.  .def ___main; .scl 2; .type 32; .endef
  23. .globl _main
  24.  .def _main; .scl 2; .type 32; .endef
  25. _main:
  26.  pushl %ebp
  27.  movl %esp, %ebp
  28.  subl $8, %esp
  29.  andl $-16, %esp
  30.  movl $0, %eax
  31.  addl $15, %eax
  32.  addl $15, %eax
  33.  shrl $4, %eax
  34.  sall $4, %eax
  35.  movl %eax, -4(%ebp)
  36.  movl -4(%ebp), %eax
  37.  call __alloca
  38.  call ___main
  39.  movl $0, (%esp)
  40.  call _add
  41.  movl $0, %eax
  42.  leave
  43.  ret
  44.  .def _printf; .scl 3; .type 32; .endef

优化了尾递归的汇编代码:gcc -O2 add.c

  1.  .file "add.c"
  2.  .section .rdata,"dr"
  3. LC0:
  4.  .ascii " %d\12 "
  5.  .text
  6.  .p2align 4,,15
  7. .globl _add
  8.  .def _add; .scl 2; .type 32; .endef
  9. _add:
  10.  pushl %ebp
  11.  movl %esp, %ebp
  12.  pushl %ebx
  13.  subl $20, %esp
  14.  movl 8(%ebp), %ebx
  15.  .p2align 4,,15
  16. L2:
  17.  movl %ebx, 4(%esp)
  18.  incl %ebx
  19.  movl $LC0, (%esp)
  20.  call _printf
  21.  jmp L2  ;注意这里,尾递归被优化成了一条跳转指令。
  22.  .def ___main; .scl 2; .type 32; .endef
  23.  .p2align 4,,15
  24. .globl _main
  25.  .def _main; .scl 2; .type 32; .endef
  26. _main:
  27.  pushl %ebp
  28.  movl $16, %eax
  29.  movl %esp, %ebp
  30.  subl $8, %esp
  31.  andl $-16, %esp
  32.  call __alloca
  33.  call ___main
  34.  movl $0, (%esp)
  35.  call _add
  36.  
  37. leave
  38.  xorl %eax, %eax
  39.  ret
  40.  .def _printf; .scl 3; .type 32; .endef

从上面两段代码看来,add函数的确有差别,但是重点在于,优化后对堆栈的处理。还是在来看优化后的代码片段(跟上面的一样,只不过我摘取了重点)

  1. _add:
  2.  pushl %ebp  ;标记1
  3.  movl %esp, %ebp
  4.  pushl %ebx
  5.  subl $20, %esp
  6.  movl 8(%ebp), %ebx
  7.  .p2align 4,,15  ;标记2
  8.  
  9. L2:
  10.  movl %ebx, 4(%esp)
  11.  incl %ebx
  12.  movl $LC0, (%esp)
  13.  call _printf
  14.  jmp L2
  15.  .def ___main; .scl 2; .type 32; .endef
  16.  .p2align 4,,15

从标记1 – 标记2,这段代码在add函数递归的过程只执行了一遍,这就说明了,优化后的代码,在堆栈上分配参数的时候,只分配了一次,也就是在第一次调用add函数的时候,现在我们终于明白了尾递归为什么不消耗栈的原因了。同样的,如果在add函数里定义了一些变量(当然要在递归调用的前面),变量在堆栈上也只是分配了一次。

到此,尾递归这个问题也应该告一段落了,但是还有一个问题值得一提,上面的add函数是个无限的递归,永不返回,如果再加一个递归终结的条件,当add函数(也就是最深一层的add函数)返回的时候,它不是向上一层递归地返回,而是直接跳到第一个add调用者(在这个例子应该是main函数)的下一个语句。这个下一篇文章在详细解释吧。

misc, study

八卦时间: ErlangInside 对 Joe Armstrong 的访问

March 24th, 2009 :: jackyz

开场白:

Writing Ruby code for a living, building a company in a foreign country, learning a human foreign language (Spanish) and a computer foreign language (Erlang) on the side hasn’t left a lot of time for blogging about Erlang. But in the past month we were able to spend a few minutes w/Joe Armstrong, creator of Erlang and certainly the language’s most famous ambassador.

译为白话:

erlanginside 的 Chad DePue 同学,平日里用 Ruby 编点小程序讨生活。他在外国( “东方山寨之国”(中国) ? “垃圾代码之乡”(印度) ?Kenny Yuan 同学对此亦有贡献)搞了个公司,同时还在学习两门外语,一门是人类语言(西班牙语),另一门是计算机语言(Erlang)。照理说像他这样的大忙人是不会有太多闲暇可以写博客吹水 Erlang 的。但这几个月来,他有幸和 Joe Armstong 这位 Erlang 界的神人有过数面之缘,故有此博。

对这段简短访谈进行 “超强脱水处理” 得到:

  • Joe 老头怒有性格,对 reia 和 f# 不留情面
  • Joe 老头对 record 语法看不顺眼了,开始考虑引入 hashmap 设施(最好搞成象 javascript 那样)
  • Joe 老头看来还搞了好几种其他的语言,可惜咱们无缘得见
  • Joe 老头写书上瘾了

访谈原文照贴如下:

Erlang Inside Interviews Joe Armstrong
Mar 23rd, 2009
by Chad DePue.

In the past year Erlang has started to become much more popular – how has this affected you?

JA: I get invited to a lot of conferences – I used to say yes to every invitation I got, but this got silly. So I now only accept a few per year.

Do you get asked for autographs in the street yet?

JA: No

Seriously, does it seem like Erlang is headed the same way Ruby was 5 years ago?

JA: No idea – but scratch the surface there seems to be lot’s of interesting stuff
going on.

Related to Erlang’s increasing popularity, any thoughts on the proliferation of web platforms for Erlang?

JA: Not really – Erlang wasn’t designed for building web platforms – it was designed for building fault-tolerant systems. Where Erlang seems to excel is in instantant messaging (ejabberd for XMPP and rabbit-MQ for AMQP) and schema-free data-bases (CouchDB, Amazon SimpleDB, Scalaris) etc.

How about Reia – any thoughts about this language? It has mutable variables, among other things…

JA: I haven’t tried it – I don’t like mutable anything

When I try to introduce Erlang to developers, I often get incredulous stares because when they first see the syntax they’re overwhelmed with how unusual it looks. What do you think the major barriers to adoption are with Erlang? Is it syntax or is it unfamiliarity with functional programming? Or something else?

JA: Fear of failure – this is always why people don’t try new stuff. Functional programming takes people out of their comfort zones. Once you get started it’s pretty easy, but the step to getting started is perceived as being large.

If you could change anything about Erlang’s syntax, would you?

JA: The record syntax is a mess, I’d like to introduce hashmaps in some convenient notation …

Do you see language design as a hobby or is this the only language you’ve ever created?

A hobby and obsession. I’ve made several languages – Erlang is the only one that has escaped.

Any thoughts on other functional languages such as Haskell or F#?

JA: I like Haskell very much – not so keen on F# – By tightly integrating with .net you get a lot of benefits but this damages the conceptual integrity of the language.

Any plans for another book?

JA: I’d like to write some more programming books.

misc

R13 推出

March 18th, 2009 :: jackyz

感到动力澎湃了么?啥也不说了,官方消息转载如下:

Erlang/OTP R13A has been released. This is a beta-release in the sense that it is not recommended for use in live products. The R13B release (recommended for use in live products) is planned for April 29. We encourage that you try out this release and give us feedback on errors you may find so we can correct this until the R13B release. Some highlights in this release are:

* Significant improvements of SMP and multi-core performance mainly because of multiple run-queues but also a number of other optimizations.
* Support for Unicode is implemented as described in EEP10.
* New application WxErlang, A binding to the WxWidgets GUI library. In beta status but the intention is that is shall replace GS in a later stage.
* New application RelTools a release management tool with a graphical frontend for interactive customization of a target system. Still a bit limited and should be regarded as experimental in this release.
* Read the complete list of bug fixes and new functions in the readme file.

Download the new release from the download page.

以及:

The next major version of Erlang, R13, has been released in beta form.

The release contains a whole host of bug fixes and goodies. Here are the ones which grabbed my attention:

* OTP-7648: Support for Unicode. This. is. huge. I have been waiting for this since R11.
* OTP-7805: Halle-freakin’-luah! A graphical tool for creating releases. Maybe I won’t have to kill kittens every time I build a release.
* OTP-7500: Replacement of the global run queue with multiple per-scheduler run queues. This should help quite a bit with run queue contention in Erlang systems with boatloads of processes. Ulf Wiger wrote about the problem in some detail but I can’t find a link to it.
* OTP-7777: Scheduler threads can be bound to logical processors. Should give a nice performance boost on supported platforms.
* OTP-7748: Fixed andalso and orelse to be tail recursive-friendly. This has caught me out on at least one occasion.
* OTP-7826: Nodes belonging to different clusters can run on the same host. Should simplify deployments a bit.
* OTP-7864: Opaque datatypes for common structures like arrays, dicts, and sets. No more unhygenic poking about in their guts.

All in all, R13A has some extremely cool stuff in it. I can hardly wait for R13B.

news

Mochiweb的设计分析

March 15th, 2009 :: refactor

Web服务器的基本工作大致分3步: 

  1. 接收HTTP请求; 
  2. 处理HTTP请求,生成响应内容;
  3. 发送响应

一、处理请求和发送响应 

模块mochiweb_request可说是Mochiweb处理HTTP请求的核心部分,它总共负责了第2步和第3步工作。因此参数化模块mochiweb_request的实例不像它的模块名那样单纯:它还负责将请求的响应(通过Socket连接,调用gen_tcp:send)发还(response)给浏览器。这个模块是一个参数化模块。这意味着它具有基于对象(Object-based)的特点,它的每个实例化对象代表一个用户请求,用户可以像OO那样操作HTTP请求:获取请求信息,生成响应内容等。 

对静态内容(如html文件,静态图片)来说,无非是读取文件系统中静态文件的内容作为响应消息体(Body)发送给客户端浏览器。 

对动态内容来说,生成响应内容的业务逻辑是用户通过编程实现的:解析请求(包括URL路径和HTTP请求头),然后生成相应的响应内容。用户编写业务逻辑处理函数,然后将函数插入到mochiweb中。此函数带有一参数,该参数是参数化模块mochiweb_request的实例(假设为Req),在这个定制函数中可以通过这个Req实例对象获取浏览器请求的所有信息(包括URL路径和HTTP请求头),处理后的响应数据也通过Req实例提供的函数发还给浏览器,有好几种方式: 

  1. 通过Req实例对象直接发还给浏览器

      a) 通过这个Req实例对象的response/1函数直接发还给浏览器:
      Req:response({Code, ResponseHeaders, Body})
      这个函数接收一个tuple参数,参数的第一个元素是HTTP状态码(比如200),第二个元素是响应消息头列表(一个二元tuple,key和value都是字符串,如{“Content-Type”, “image/jpeg”}),第三个元素即为HTTP响应内容;如果是动态内容,采用response函数直接发还响应数据还是比较方便的。

      b) 如果是静态文件,通过Req的serve_file(…)函数可以直接将文件发还给浏览器,告诉此函数文件的Web根目录(doc_root)和相对路径就可以将指定的静态文件发给浏览器了。doc_root目录一般在配置文件中设置,这个目录下的所有文件都可以通过HTTP访问。

      c) 一些常见的例行响应:
      不存在的URL请求返回404错误,可以直接调用Req:not_found() 若一切正常,调用Req:ok({…, Body})直接返回状态码为200的HTTP响应
      b,c两种情况的内部其实还是调用的Req:response()

  2. 通过Response模块对象将响应发还给浏览器

      Req:response函数会返回一个mochiweb_response参数化模块实例对象(假设为Response),Response实例对象包含有对应的Req实例对象。通过Response对象可以得到响应的相关信息(如响应状态码,响应消息头,对应的Req对象),它还有一个send函数可以将响应数据发还给浏览器(它的实现其实还是调用Req对象的send函数进行的)。Response之所以还要有send函数是为了发送chunked数据(HTTP 1.1)的方便,在第一次响应完成后,后继的chunk数据就可以通过最初返回的Response对象继续进行发送了,为此Response有个函数write_chunk()专门干这事,write_chunk检查了请求消息头中的HTTP 版本消息后就调用Response:send。

      因此,响应内容最终都是由参数化模块mochiweb_request的response/1函数发送的。而这个response(…)函数内部最后调用了Req:send(Data)函数将响应通过socket连接(调用gen_tcp:send)返还给浏览器,这一发送过程又分成两个阶段:响应消息头(Headers)的发送和消息体(Body)的发送,这两步都是通过Req:send完成的。

小结:对于程序员来说,他编写的服务器端处理HTTP请求的函数必须带有一个参数,这个参数代表了mochiweb_request参数化模块的一个实例对象。通过这个实例程序员可以得到HTTP请求的所有信息(包括路径、请求参数以及HTTP请求消息头),然后生成响应,他还可以通过该mochiweb_request实例对象将响应数据发还给浏览器。 

  • 如果响应的是静态文件,可以通过Request:server_file()函数发送响应;
  • 如果响应是动态生成的内容,通过Request:response()函数发送响应数据(文本数据和二进制数据都可以);
  • 如果是chunk响应,第一次调用Request:response()函数发送数据后,该函数会返回一个Response实例对象(参数化模块mochiweb_response的一个实例),以后的响应数据可以通过这个Response实例对象(调用Response:write_chunk(Data))发送后续的响应数据。

二、Web服务器的业务处理逻辑如何嵌入到Mochiweb 

业务处理逻辑被程序员编写成一个函数(函数式编程中函数也是一种数据,以下称为HttpLoop),处理函数HttpLoop是作为mochiweb启动时的参数之一进入Mochiweb,Mochiweb是这样启动的: 

mochiweb_http:start([{loopHttpLoop},...])  

Request实例对象最终要调用gen_tcp:send(Socket,…)将响应数据由socket连接发给浏览器,因此,Request实例对象应该持有HTTP请求的socket连接。这个目标是这样实现的: 
在启动过程中,mochiweb_http又对用户的HttpLoop函数进行了重新包装 

Loop = fun (Socket) ->
    
mochiweb_http:loop(Socket, HttpLoop)
end,

每个浏览器连接请求对应着一个Socket连接,新的Loop函数以此Socket作为参数,然后通过mochiweb_http:loop函数对Socket连接和用户自定义的HttpLoop函数进行了处理,简单的说,这个函数做了如下工作: 

  1. 将Socket连接设置成能处理HTTP数据包 (inet:setopts(Socket, [{packet, http}]));
  2. 准备接收socket连接传来的数据(gen_tcp:recv(Socket,…));

当收到数据时:

  1. 将HTTP消息头数据提出(成为{HeaderName, HeaderValue}的tuple列表)
  2. 生成一个参数化模块mochiweb_request的实例对象,并将Socket连接、HTTP请求信息(路径、请求方法、HTTP版本)以及请求消息头列表包装到此实例对象中,
  3. 然后调用用户的HttpLoop对请求进行处理和响应(如前所述,处理得到的响应数据也在用户编写的HttpLoop函数中被发送给浏览器)
  4. 最后根据HTTP请求信息决定是简单的关闭Socket连接,还是清理一下Req对象并保持连接(例如对keep-alive,chunk等类型的HTTP请求,以及还没完成数据传送的HTTP请求),以便继续让HttpLoop函数进行处理,

这都是在headers函数中进行的,还是看mochiweb_http模块的代码: 

  1. headers(Socket, Request, Headers, HttpLoop, HeaderCount) -> 
  2.     case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of 
  3.         {ok, http_eoh} -> 
  4.             inet:setopts(Socket, [{packet, raw}])
  5.             % 将Socket连接、HTTP请求信息(路径、请求方法、HTTP版本)以及请求消息头列表打包成参数化模块(mochiweb_request)的实例对象 
  6.             Req = mochiweb:new_request({Socket, Request
  7.                                         lists:reverse(Headers)}),   
  8.             HttpLoop(Req), % 让用户编写的函数处理HTTP请求 
  9.            case Req:should_close() of 
  10.                 true ->  
  11.                     gen_tcp:close(Socket)
  12.                     exit(normal)
  13.                 false ->  
  14.                     Req:cleanup()
  15.                     mochiweb_http:loop(Socket, HttpLoop) 
  16.             end
  17.         {ok, {http_header, _, Name, _, Value}} -> 
  18.             headers(Socket, Request, [{Name, Value} | Headers], HttpLoop
  19.                     1 + HeaderCount)
  20.         _Other -> 
  21.             gen_tcp:close(Socket)
  22.             exit(normal) 
  23.     end.

以上处理逻辑都被重新包装到新函数Loop(又一个函数对象),这一包装过程是在mochiweb_http:parse_options函数中进行的。新的Loop函数对象将作为OTP gen_server模块mochiweb_socket_server的运行状态(State)的一部分(这个OTP server模块被mochiweb_http启动,Loop函数对象作为启动参数之一传给mochiweb_socket_server,进入模块的State) 

三、接受HTTP请求 

作为Mochiweb HTTP Web服务器的核心,mochiweb_socket_server模块负责处理浏览器的HTTP连接(本质上还是socket连接)。 

该OTP模块在初始时(init(…)函数)打开trap_exit使服务器进程成为系统进程,并开始在端口监听网络连接,同时,启动(spawn_link)一个HTTP请求接收进程,这个进程执行的是acceptor_loop(…)函数。acceptor_loop接收一个三元tuple作为参数,tuple参数的第一个元素是mochiweb_socket_server服务器进程Pid,第二个是在网络端口进行监听的监听器,第三个就是前面提到的包装好的Loop函数对象,如下: 

  1. acceptor_loop({Server, Listen, Loop}) -> 
  2.     case catch gen_tcp:accept(Listen) of 
  3.         {ok, Socket} -> 
  4.             gen_server:cast(Server, {accepted, self()})
  5.             call_loop(Loop, Socket)
  6.         {error, closed} -> 
  7.             exit({error, closed})
  8.         Other -> 
  9.             error_logger:error_report( 
  10.               [{application, mochiweb}, 
  11.                "Accept failed error"
  12.                lists:flatten(io_lib:format("~p", [Other]))])
  13.             exit({error, accept_failed}) 
  14.     end.

HTTP请求接收进程收到一个浏览器HTTP连接请求后,先以“发送后不管”的方式(即cast)发accepted消息通知mochiweb_socket_server服务器:有HTTP请求来啦,本进程已接受并负责处理。随后该进程就会调用用户定制的函数对象处理请求。进程是在call_loop函数中执行上述的Loop函数对象 

call_loop(LoopSocket) ->
    
Loop(Socket).  

这个在服务器初始阶段启动的HTTP请求接收进程的Pid被作为mochiweb_socket_server状态的一部分保持在State记录的acceptor字段。mochiweb_socket_server服务器进程的状态还记录了Mochiweb服务器当前能处理的最大连接数(max),每当收到HTTP请求接收进程发过来的一个accepted消息,就减少一个最大连接数,同时,除非当前最大连接数为零,再生成一个新的HTTP请求接收进程,并将mochiweb_socket_server服务器进程的运行状态(State)的acceptor字段替换成这个新的HTTP请求接收进程Pid。这就是Mochiweb接受HTTP请求的策略,有点像自动步枪,尽管枪膛里只有一发子弹,但每射出一发子弹,火药爆炸带来的一部分气体压力自动的将新的子弹压入枪膛,于是就可以连续不断的射击了。 

总结 

服务器端的业务逻辑在一个函数中实现,然后这个函数像数据对象那样嵌入到Mochiweb中,这是典型的函数式程序设计。这个函数通过mochiweb_request实例对象获取请求的相关信息,并将响应通过该对象发送给客户端浏览器。 

每个HTTP请求由一个Erlang进程负责处理,充分发挥了Erlang多线程的优势。一个连接对应一个Erlang进程,比较适合comet这样的应用,并发请求的处理能力与Erlang线程数量正相关。相关测试见A Million-user Comet Application with Mochiweb 。

Mochiweb的实现大量使用了参数化模块的特性,包括核心的代表HTTP请求的mochiweb_request实例对象,某种程度上使Mochiweb有了基于对象(Object-based)的特点。不过参数化模块还是“undocumented and unsupported”,可能在未来版本的Erlang中有一定不兼容风险,但是如果Mochiweb用的人足够多,应用足够广,我想将来的Erlang说不定反受Mochiweb应用的影响,官方确定参数化模块这一特性呢。 

mochiweb这类的轻量级Web服务器小巧灵活,它的Web应用不能像JSP、ASP那样方便的写服务器端代码,也没有很多重型Web服务器常见的功能,但胜在易于理解,又足够快。比较适合纯动态内容生成的Web应用,如comet。可以作为现有Web服务器的补充。Facebook就用它做了Web在线聊天

misc

《Erlang 并发编程》翻译中

March 15th, 2009 :: jackyz

Concurrent Programming in Erlang 《Erlang 并发编程》(Part 1,谁有 Part 2 ?分享一下。) 是长期固定挂在 erlang.org 文档区上的推荐书目。这本出版于 1996 年的书,是 Erlang 史上真正意义的第一本,它见证了第一次互联网泡沫破裂的全过程。是 Erlang 坎坷发展历程的一个注脚。而 2005 年出版的 Joe Armstrong 的《Erlang 程序设计》,在其中文版于 2008 年底推出时,又恰逢史上最严重的经济危机,至今尚未见底。这样的机遇,不知道算不算是一种巧合。

其实,以我个人的经验来说(我有幸经历过上一次的泡沫破裂),这样的行业寒冬,对于技术人员也未必只是一件单纯的坏事。如果不幸去职的话,正好可以趁着成本低廉开始创业,没有创业计划,也可以一边在家 “疗养” 身体,一边 “充电” 头脑。时代的车轮滚滚向前,没有什么危机不会过去的,到时正好可以凭借 “充好电” 头脑再上一个新台阶。连城同学在这个冬天给大家带来了好消息。他接下了艰巨的任务,开始翻译知名的《Erlang 并发编程》Part 1 ,可以在[这里]在线浏览他的成果。严重感谢连城同学对 erlang 中文社区作出的贡献!翻译还在进行中,请有时间有兴趣同学一起来参与连城同学的翻译计划。

misc

Erlide 0.5.1 + Wrangler = refactoring

March 10th, 2009 :: jackyz

如果询问 java 开发人员,在他们的开发经历中有什么是最让人愉快的,那么我相信排行榜中肯定少不了 “使用 eclipse 编写代码” 这个条目。

确实如此, 脱胎于 ibm virtual age for java 的 eclipse 绝对算得上是一个历史悠久的伟大开发工具。它简洁(说的是界面,不是资源占用)而精致,常在细微之处瞥见它设计的精心之处。就好像是一把重量合适的锤子,而且形状趁手,用习惯了会产生 “没有在用锤子” 的错觉,久而久之,无端地就会生出一种 “天下钉子皆可敲” 的豪爽。这就是一个优秀工具应该带给人的快感。据说 netbeans 也差不多,不过,在它大行其道的时候,我已经因为 erlang 的关系转投到 emacs 的怀抱,无缘体验了。

我个人最为欣赏的是代码格式化和重构的功能。赶时间的时候随手写的代码,通常能用,但是气味不好。就好比初中上几何的时候,别人画的图总是那么干净整洁,而我的就总是黑漆吗乌歪歪扭扭。其实,私底下我也是个愿意追求完美的人,也想画得漂亮一点,但如果需要重做的话,就不乐意了。格式化和重构,是懒人梦寐以求的功能,满足美的追求,又不会占用太多的时间,正是恰到好处的平衡。

erlang 程序员们,现在 eclipse 平台的编码工具也要开始有 refactor 功能了,真是个好消息。

This is the first release targeting Eclipse 3.4.

The big news is that we are now including experimental Wrangler refactoring support! György Orosz has put together the required Eclipse scaffolding and we will appreciate all the feedback we can get.

Otherwise, there are some bugs that have been fixed, see the ChangeLog for details.

更多信息,请看:[ChangLog]

btw. 俺的 emacs 配置 wrangler 总是有问题,哪位有成功经验可以分享?

misc