8/22/2007

Fedora Core 6 上安裝 beast (3)

覺得 pound + lighttpd 太麻煩嗎?又翻到一個應該不錯的選擇,
是一個俄國人寫的 http server, 也具有 reverse proxy 和
load balancing 的能力。

godfat ~ 3.2$ port info nginx
nginx 0.5.29, www/nginx (Variants: universal, dav, flv, mail, ssl)
http://nginx.net/

Nginx ("engine x") is a high-performance HTTP(S) server and reverse proxy,
as well as an IMAP/POP3 proxy server. Nginx was written by Igor Sysoev for
Rambler.ru, Russia's second-most visited website, where it has been running
in production for over two and a half years. Igor has released the source
code under a BSD-like license. Although still in beta, Nginx is known for
its stability, rich feature set, simple configuration, and low resource
consumption.

這個字實在是很難記…不過討論翻來翻去,他 serve static file 的效能和
lighttpd 在伯仲之間,而 load balancing 是正常可用的。所以與其使用
pound + lighttpd, 單用 nginx 可以簡化一些流程。我想缺點就是設定上
還是比 pound 複雜些,不過我個人是覺得比 lighttpd 簡單了。另外 nginx
好像沒有 windows 版,而 pound 和 lighttpd 都有 windows 版,
我想這對某些情況可能也是個考量吧?

雖然之前因為英文文件太少所以不太流行,但最近似乎還算有蠻多人推薦的,
而且這在俄國好像還蠻廣泛被使用的,試試應該無妨。

sudo yum install nginx

跟 lighttpd 一樣,一起丟到 beast 目錄下比較容易管理。
cp /etc/nginx/nginx.conf \
/home/YOUR_NAME/projects/beast/stable-1.0/config/nginx.conf
cp /etc/nginx/mime.types \
/home/YOUR_NAME/projects/beast/stable-1.0/config/mime.types
前者是 config 檔,後者是 mime-type 設定檔,會被 include 進去。

nano config/nginx.conf
=======================
user YOUR_NAME YOUR_NAME;
worker_processes 1;
pid /home/YOUR_NAME/projects/beast/stable-1.0/tmp/pids/nginx.pid;

error_log /home/YOUR_NAME/projects/beast/stable-1.0/log/nginx_error.log;

events {
  worker_connections 1024;
}

http {
  include /home/YOUR_NAME/projects/beast/stable-1.0/config/mime.types;
  default_type application/octet-stream;

  log_format main '$remote_addr - $remote_user [$time_local] $request '
                  '"$status" $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"';

  access_log /home/YOUR_NAME/projects/beast/stable-1.0/log/nginx_access.log main;

  sendfile on;
  keepalive_timeout 65;

  gzip on;
  gzip_min_length 1100;
  gzip_buffers 4 8k;
  gzip_types text/plain text/html text/xhtml text/css text/javascript;

  upstream mongrel {
    server 127.0.0.1:2000;
    server 127.0.0.1:2001;
    server 127.0.0.1:2002;
  }

  server {
    listen 80;
    server_name localhost;

    location ~ ^/(images|javascripts|stylesheets)/ {
      root /home/YOUR_NAME/projects/beast/stable-1.0/public
      expires 30d;
    }


    location / {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
      proxy_pass http://mongrel;
    }
  }
}
=======================

其實還有一大堆設定我省略掉了,可以自己上官網或 google 找其他的設定。
啟動:
sudo nginx -c config/nginx.conf
關閉:
sudo kill `cat tmp/pids/nginx.pid`

我用這個去跑 YSlow, 應該是設定的關係,分數從 62 => 75...
但當然以我測試機的超低流量來看,就算直接用 mongrel 應該也是瞬間反應…

8/15/2007

Fedora Core 6 上安裝 beast (2)

不喜歡 apache 嗎?apache 沒辦法用 mod_proxy_balancer 嗎?沒關係,我找到另外一個不錯的方式了,是 pound + lighttpd. 如果不想用 apache 的話,上一篇的 httpd 相關的東西都不要灌,改灌:

sudo yum install Pound
sudo yum install lighttpd

我是覺得這邊分大小寫很沒意義,不過 yum 就是這樣﹍。

pound 是什麼?
The Pound program is a reverse proxy, load balancer and HTTPS front-end for
Web server(s). Pound was developed to enable distributing the load among
several Web-servers and to allow for a convenient SSL wrapper for those
Web servers that do not offer it natively.

lighttpd 我想大家都知道,一個非常快速的 http server, 只不過他的 mod_proxy 似乎有很多問題,以致於沒辦法用他。所以這部份用 pound 當前端解決這個問題,流程變成:

http request => pound => lighttpd or mongrel cluster

安裝好後,先來設定 pound, 不過 https 就先暫時略過不管。
sudo nano /etc/pound.cfg
=========================
User "pound"
Group "pound"

ListenHTTP
    Address 0.0.0.0
    Port 80
End

Service
    URL "/(images|stylesheets|javascripts)/"
    BackEnd
        Address 127.0.0.1
        Port    1999
    End
    Session
        Type    BASIC
        TTL     300
    End
End

Service
    BackEnd
        Address 127.0.0.1
        Port    2000
    End
    BackEnd
        Address 127.0.0.1
        Port    2001
    End
    BackEnd
        Address 127.0.0.1
        Port    2002
    End
    Session
        Type    BASIC
        TTL     300
    End
End
=========================
前面的設定是把 host/images, host/stylesheets, host/javascripts 這三個 url 下的 request 都丟給 port 1999 處理,也就是等會 lighttpd 要使用的 port. 這邊使用 regex, 可以自由設定自己要的規則。假使未來 user 要使用 file_column, 可以再加個 user/avatar 之類的。

下面的則是全部丟給 mongrel_cluster 處理。設定上應該比 apache 簡單地多。可以先測試看看,先把 lighttpd 的部份 comment 起來,啟動 pound 試試:
sudo pound

照理說這樣就能使用了。不行的話加個 config 檔路徑給他:
sudo pound -f /etc/pound.cfg

這邊我是在想要不要把 config 也放到 beast 裡面,不過 pound 應該只會有一個,所以我想還是放到 /etc 下好了。停止 pound 的方式:
sudo kill `sudo cat /var/run/pound.pid`

製作 lighttpd 設定檔:
nano config/lighttpd.conf
==========================
server.modules = (
"mod_access",
"mod_accesslog"
)

server.port = 1999
server.bind = "127.0.0.1"
server.document-root = "/home/YOUR_NAME/projects/beast/stable-1.0/public"

server.username = "YOUR_NAME"
server.groupname = "YOUR_NAME"
server.pid-file = "/home/YOUR_NAME/projects/beast/stable-1.0/tmp/pids/lighttpd.pid"
server.errorlog = "/home/YOUR_NAME/projects/beast/stable-1.0/log/lighttpd_error.log"
index-file.names = ( "index.html", "default.html" )
accesslog.filename = "/home/YOUR_NAME/projects/beast/stable-1.0/log/lighttpd_access.log"

# mimetype mapping
mimetype.assign = (
".rpm" => "application/x-rpm",
".pdf" => "application/pdf",
".sig" => "application/pgp-signature",
".spl" => "application/futuresplash",
".class" => "application/octet-stream",
".ps" => "application/postscript",
".torrent" => "application/x-bittorrent",
".dvi" => "application/x-dvi",
".gz" => "application/x-gzip",
".pac" => "application/x-ns-proxy-autoconfig",
".swf" => "application/x-shockwave-flash",
".tar.gz" => "application/x-tgz",
".tgz" => "application/x-tgz",
".tar" => "application/x-tar",
".zip" => "application/zip",
".mp3" => "audio/mpeg",
".m3u" => "audio/x-mpegurl",
".wma" => "audio/x-ms-wma",
".wax" => "audio/x-ms-wax",
".ogg" => "application/ogg",
".wav" => "audio/x-wav",
".gif" => "image/gif",
".jpg" => "image/jpeg",
".jpeg" => "image/jpeg",
".png" => "image/png",
".xbm" => "image/x-xbitmap",
".xpm" => "image/x-xpixmap",
".xwd" => "image/x-xwindowdump",
".css" => "text/css",
".html" => "text/html",
".htm" => "text/html",
".js" => "text/javascript",
".asc" => "text/plain",
".c" => "text/plain",
".cpp" => "text/plain",
".log" => "text/plain",
".conf" => "text/plain",
".text" => "text/plain",
".txt" => "text/plain",
".dtd" => "text/xml",
".xml" => "text/xml",
".mpeg" => "video/mpeg",
".mpg" => "video/mpeg",
".mov" => "video/quicktime",
".qt" => "video/quicktime",
".avi" => "video/x-msvideo",
".asf" => "video/x-ms-asf",
".asx" => "video/x-ms-asf",
".wmv" => "video/x-ms-wmv",
".bz2" => "application/x-bzip",
".tbz" => "application/x-bzip-compressed-tar",
".tar.bz2" => "application/x-bzip-compressed-tar"
)
==========================
我很想省略 mime-type 設定,不過不設的話 firefox 好像會有問題。safari 和 opera 是 ok 的,IE 我沒得測就沒測了。反正要設就是了,有沒有問題不影響。只是希望 lighttpd 能內建這些設定﹍。

啟動 lighttpd
sudo lighttpd -f config/lighttpd.conf

這樣就算大功告成了!剛剛有測試 pound 的話記得改回原本設定並重新啟動。比較詳細的介紹與說明可以參考這篇:
Configuring pound with lighttpd and mongrel.

*

summary:

用到的 server:

(1)
apache2.2 =>
reverse proxy + load balancer + serve static files

(2)
pound => reverse proxy + load balancer
lighttpd => serve static files

(3)
mongrel_cluster => setup mongrel cluster
mongrel => serve beast

結果就是 ((1) or (2)) + (3)

操作 apache:
啟動:sudo apachectl start
停止:sudo apachectl stop
重啟:sudo apachectl restart

操作 pound:
啟動:sudo pound -f /etc/pound.cfg
 或:sudo /etc/init.d/pound start

停止:sudo kill `sudo cat /var/run/pound.pid`
 或:sudo /etc/init.d/pound stop

重啟:sudo /etc/init.d/pound restart

操作 lighttpd:
啟動:sudo lighttpd -f config/lighttpd.conf
 或:sudo /etc/init.d/lighttpd start

停止:sudo kill `cat tmp/pids/lighttpd.pid`
 或:sudo /etc/init.d/lighttpd stop

重啟:sudo /etc/init.d/lighttpd restart

操作 mongrel_cluster:
啟動:sudo mongrel_rails cluster::start
停止:sudo mongrel_rails cluster::stop
重啟:sudo mongrel_rails cluster::restart

操作 postgresql:
啟動:sudo pg_ctl start
停止:sudo pg_ctl stop
重啟:sudo pg_ctl restart

8/14/2007

Fedora Core 6 上安裝 beast

我選擇 apache2.2 + mongrel_cluster + postgresql

安裝 apache 和其他的東西。
sudo yum install httpd
sudo yum install httpd-devel
sudo yum install openssl
sudo yum install readline

安裝 ruby
sudo yum install ruby
sudo yum install ruby-devel
sudo yum install ruby-libs
sudo yum install rdoc
sudo yum install irb

安裝 postgresql
sudo yum install postgresql
sudo yum install postgresql-devel
sudo yum install postgresql-libs
sudo yum install postgresql-server

初始化 postgres
sudo mkdir -p /var/db/postgresql81/defaultdb
sudo chown postgres:postgres /var/db/postgresql81/defaultdb
sudo -u postgres initdb -D /var/db/postgresql81/defaultdb
sudo -u postgres pg_ctl -D /var/db/postgresql81/defaultdb \
-l /var/db/postgresql81/defaultdb/postgres.log start

拿來抓 beast... 捨 svn 取 svk 因為我討厭一堆 .svn
sudo yum install perl-SVK

sudo yum install rubygems

至少要用這些 gem
sudo gem install rails -y
sudo gem install mongrel_cluster -y
sudo gem install redcloth -y
sudo gem install ruby-openid -y
sudo gem install ruby-postgres -y

建立目錄。
mkdir -p ~/projects/beast
cd ~/projects/beast

用 svk 抓不會有一堆 .svn 礙眼。
svk mirror //mirror/beast \
http://svn.techno-weenie.net/projects/beast/branches/stable-1.0/
svk sync -s HEAD //mirror/beast
cd stable-1.0

產生讓 beast 用的資料庫。
sudo -u postgres createdb beast_prod

設定資料庫設定檔。
cp config/database.example.yml config/database.yml

nano config/database.yml
=========================
production:
  database: beast_prod
  adapter: postgresql
  host: localhost
  username: postgres
  password:
=========================

初始化 beast 資料庫。
rake db:schema:load RAILS_ENV=production

非常囉唆的 cluster 設定,user 可以開 mongrel,
不過因為我懶所以用自己的帳號跑。
sudo mongrel_rails cluster::configure -e production \
-p 2000 -N 3 -c /home/YOUR_NAME/projects/beast/stable-1.0 \
-a 127.0.0.1 --user YOUR_NAME --group YOUR_NAME

啟動 mongrel_cluster 吧。
sudo mongrel_rails cluster::start

接著是 apache2.2 的 mod_proxy_balancer 的設定,
如果 mod_proxy_balancer 沒有啟動記得把他打開。

建立 balancer 導向 port 2000~2002
sudo nano /etc/httpd/conf/httpd.conf
=====================================
<Proxy balancer://YOUR_BALANCER>
  BalancerMember http://YOUR_ADDRESS:2000
  BalancerMember http://YOUR_ADDRESS:2001
  BalancerMember http://YOUR_ADDRESS:2002
</Proxy>

Listen 80
<VirtualHost *:80>
  ServerAdmin YOUR_EMAIL
  ServerName YOUR_ADDRESS
  ProxyPass / balancer://YOUR_BALANCER/
  ProxyPassReverse / balancer://YOUR_BALANCER/
  ProxyPreserveHost on
  ErrorLog /home/YOUR_NAME/projects/beast/stable-1.0/log/apache_error.log
  CustomLog /home/YOUR_NAME/projects/beast/stable-1.0/log/apache_access.log combined

  ProxyPass /images !
  ProxyPass /stylesheets !
  ProxyPass /javascripts !
  Alias /images /home/YOUR_NAME/projects/beast/stable-1.0/public/images
  Alias /stylesheets /home/YOUR_NAME/projects/beast/stable-1.0/public/stylesheets
  Alias /javascripts /home/YOUR_NAME/projects/beast/stable-1.0/public/javascripts
</VirtualHost>
=====================================

接著啟動 apache 就 ok 了。
sudo apachectl start

最後來設定 beast 的 smtp, 如果想用 gmail 寄信的話,
我之前試過兩個方式,一個是使用 msmtp,
另一個是有人有寫一小段 ssl smtp 的 patch, 可以參考這裡:
http://www.stephenchu.com/2006/06/how-to-use-gmail-smtp-server-to-send.html

nano config/environment.rb
加入:
===========================
ActionMailer::Base.smtp_settings = {
  :address => "smtp.gmail.com",
  :port => 587,
  :domain => 'YOUR_DOMAIN',
  :user_name => "YOUR_GMAIL_ACCOUNT",
  :password => 'YOUR_GMAIL_PASSWORD',
  :authentication => :plain
}
===========================

nano app/models/user_mailer.rb
===============================
@from = 'YOUR_GMAIL_ACCOUNT@gmail.com'
===============================
我不是很確定這一步要不要做,我猜不做會被 gamil 擋掉。

接下來把那段程式碼 copy 到可以被 require 的地方,我是放在 lib 下:
nano lib/smtp_tls.rb
接著把他 require 進去:
nano config/environment.rb
===========================
require 'smtp_tls'
===========================

sudo mongrel_rails cluster::restart
註冊一下試試寄信吧!如果失敗的話,那篇連結裡有個 workaround:

nano lib/smtp_tls.rb
=====================
rescue EOFError
  ;
rescue OpenSSL::SSL::SSLError => e
  puts('Unexpected Exception: ' + e.message + ' : ' +
    e.backtrace.join("\n") )
end
=====================

我之前用是沒有這個問題,但在這台 fedora 上卻有這個問題,
也許是要更新 openssl 的版本?不管如何,加入上面那段暫時可用。

結論:
還是 macports 好用,速度快,更新快,只是 build 要很久就是了。

8/13/2007

ludy 0.0.4 released

花了一點時間再測試了一下 curry 的實做,i am glad to announce that ludy 0.0.4 is released. 原本的 Proc#curry 被改名為 Proc#__curry__, 我很想把他
deprecate 掉,但是後來的 Kernel#curry 也有運用到該實做,是有在考慮把他從 public 改到 private, 但考慮到也許還有用處,還是暫時放在 public 下,以後如果發現真的沒用了,會改到 private 下。

現在的用法是:

multiply = lambda{|l,r| l*r}

double = multiply.curry[2]
assert_equal 8, double[4]
assert_equal 6, double[3]

xd = multiply['XD', 5]
assert_equal 'XDXDXDXDXD', xd

assert_equal 29, :+.to_proc.curry[18][11]
assert_equal (0..4).to_a, lambda{|a,b,c,d,e|[a,b,c,d,e]}.curry[0][1][2][3][4]

只要呼叫到了 Kernel#curry, 且 caller 本身回應(respond_to?):call 和 :[], 則回傳一個 curry function, 這樣就可以有更強的 consistency, 不需要注意什麼時候使用 () 而什麼時候使用 [], 請一律使用 function call/[], 不用擔心參數是否足夠,足夠時就會回傳真正的答案,否則再度傳回 curry function.

所以其實我是在想,以下兩者是否相同?
class Array; include Curry; end
func1 = [].cfoldr
func2 = [].method(:foldr).curry
func1 == func2 # => true? or false?

我的希望是相同,當然。只是我直接換上這樣的實做似乎有點問題,這個狀況可能在 0.0.5 中解決,使 curry module 和 kernel#curry 也能夠擁有該有的一致性。

另一個棘手問題是 ruby 的 block, 乍看很好用,實際上也是,但是卻造成了很大的不一致。這一點也真的是很難搞定,之前的 this 就有碰上這樣的大問題,使用 yield 似乎無法產生正確的 call stack.

*

最後則是 0.0.4 上的實做問題,由於 :*.to_proc 的這個 proc 無法預測其正確的 arity, 就像 :message_that_you_never_know.to_proc 也不可能能知道他的 arity 是多少一樣,這造成了難以判斷何時該回傳正確的值而非另一個 curry function. 我是想要從 Symbol#to_proc 去竄改,不過這會碰上另外兩個問題:

1. 會跟其他人的實做衝突
2. caller 和 arity 是合併在一起的,意味還沒 call 之前都不會知道 arity, 這樣我就沒辦法強迫 Symbol#to_proc 能產生正確的 arity.

所以我只好用另外一個很愚蠢的方式:trial & error.
begin # let's try if arguments are ready
  self.__send__ :orig_call, *args, &block
rescue ArgumentError # oops, let's curry it
  method(:call).to_proc.__send__ :__curry__, *args
end

效率問題就別提了,我真的覺得這樣很蠢,可是好像也想不到更好的方式。

所以 0.0.4 就只暫時強化了 curry 的實做,離完善還有很大的一段距離。雖然心血來潮度極高,但好像還算有進展?

gem install ludy
to see detail.

8/07/2007

ludy 0.0.3 released

睽違已久,忽然心血來潮多加了幾個東西。
in CHANGES:
==============================
ludy 0.0.3, 2007.08.07

1. ludy_ext:
added:
1. Proc#curry
2. Proc#compose
3. Proc#chain
4. Symbol#to_proc
5. Array#foldl
6. Array#foldr
7. Array#filter

removed:
1. Fixnum#collect # see tc_ludy_ext.rb#test_fixnum_collect for reason

info:
1. ruby2ruby has NilClass#method_missing return nil,
so i can't just make it return blackhole

2. module Curry:
see test/tc_curry.rb for usage

see unit test for usage
==============================
雖然說請看 unit test 來揣摩用法,不過這樣真的有點無趣,所以還是來稍微介紹一下。這次之所以忽然心血來潮想做,是因為看到James Edward Gray II 的 higher-order ruby 專欄:
http://blog.grayproductions.net/articles/category/higher-order-ruby
第六篇的:Currying, 他的 curry 實做:
class Proc
def curry(&args_munger)
lambda { |*args| call(*args_munger[args]) }
end
end

老實講,不是說看不懂,可是我不明白為什麼要寫得那麼複雜,乍看之下實在看不太出來。丟掉他的實做,我試著做了一個:
class Proc
def curry *pre
lambda{ |*post| self[*(pre + post)] }
end
end

就我自己測試起來,效果是一樣的,我認為應該簡潔易懂多了。可是這根本不太像 curried function 吧?內心吶喊著。不過在看我後來寫的 curry module 之前,先來簡單介紹一下 currying.

在 lambda caculus 中,每個 function 都只能有一個 argument, 這是因為 lambda caculus 是一種極簡的語言,用來研究某些模型的語言。但是如果 function 只能吃一個 argument, 有很多事是會做不到的。於是我們可以靠著 tuple 把許多的 argument 包成一個 argument, 例如在 Haskell 中,tuple 就是 (1,2,3), 用括號括起來的就是 tuple. 所以上面的 (1,2,3) 是一個有三個值的 tuple, 可以把他視為一個值。

比方說有個 function 叫 power, 像是:power 2, 10 會回傳 1024. uncurried function 就會是 power (2, 10), 他吃一個有兩個元素的 tuple, 吐出一個 1024 的值。可是如果是這樣使用的話,其實是很不方便的。有一個方法可以讓 function 依然只吃一個 argument, 但是又不需要使用 tuple, 可以一個值一個值傳入,那就是 curried function.

在 functional programming 中,function 的地位極高,不管在做什麼事,幾乎都是在操作 function. 這也就是所謂的 higher-order function, 操作 function 的 function, 或是產生 function 的 function 諸如此類。curried function 的效果就是當所吃入的 argument 不足時,他會再吐出另一個 function 去吃其他 argument,直到 argument 足夠時才會吐出結果。

power 2 的回傳會是一個 function, 他記住了 2, 當他再吃一個 argument 後,則會再把 2 拿出來跟 argument 做運算。所以 power 的 type 會是:
power :: Int -> Int -> Int
結合順序是從右邊開始,所以是吃一個 Int, 吐出 (Int -> Int), 也就是吃一個 Int 吐出一個 Int 的 function. 可以想成這樣:

power2 = power 2
result = power2 10
result # => 1024

也就是說,可以把他看成是一個會不斷記憶 argument 的 function. 看看 James Edward Gray II 的範例:
multiply = lambda { |l, r| l * r }
double = multiply.curry { |args| args + [2] }
triple = multiply.curry { |args| args << 3 }

multiply[5, 2] # => 10
double[5] # => 10
triple[5] # => 15
triple["Howdy "] # => "Howdy Howdy Howdy "

所以他的實做其實很簡單,就是用一個 array 記憶 arguments,
最後再 prepend 到最後的 arguments 裡。
class Proc
def curry(&args_munger)
lambda { |*args| call(*args_munger[args]) }
end
end

不過我覺得不用寫得那麼複雜,所以改寫為:
class Proc
def curry *pre
lambda{ |*post| self[*(pre + post)] }
end
end

這裡利用了 ruby 的 lambda 有 closure 的特性,把 *pre 紀錄下來,再把他 prepend 到 post 上,最後再呼叫原本的自己(self)。

可是這樣不完整,因為你必須明確表達你需要做 curry, 而 Haskell 的 curried function 是可以讓你忽略這件事的。

lambda{|a,b,c,d,e|}.curry(1).curry(2).curry(3).curry(4).carry(5)

這樣不煩死才怪。我希望能用:

lambda{|a,b,c,d,e|}[1][2][3][4][5]
也能使用:
lambda{|a,b,c,d,e|}[1,2][3][4,5]

可惜我暫時還沒找到好做法 XD 目前暫時僅提供可以 mixin 的 module, 大概是這樣用:

class Array; include Curry; end

接著 array 所有以字母開頭的 method 會多個 curried 版,prefix c. i.e., map => cmap; foldr => cfoldr

func1 = [1,2,3].cfoldr[:-.to_proc]
assert_equal 2, func1[0]

做法其實很簡單,就只是檢查參數夠了沒,不夠就重新 curry 一份,夠了就呼叫原始 method. 我原本一直不希望前綴 c, 而以相同名字命名之,然後原本的名字改為:orig_method. 可惜不管怎麼試都失敗,原因不是很清楚,但這種 side-effect 超大的動作,會失敗其實也不怎麼奇怪吧,我想。雖然我總覺得以正常呼叫法而言,應該是沒什麼差才對,也許我有哪裡寫錯了,只是還沒發現而已。

*

至於其他新增的東西,這裡也稍微介紹一下。首先 Array#filter 只是 select 的 alias, Array#foldl 也只是 inject 的 wrapper. Array#foldr 稍微複雜些,不過概念上只是類似反過來的 inject 而已。Symbol#to_proc 大家應該都很熟,連 active_support 裡面也有。只是單純的 message/method 轉換而已。

比較需要提的應該是 Proc#compose 和 Proc#chain. 前者就是數學上的 compose, facets 裡其實也有,不過手癢還是自己做了一份。他有點類似反向的 inject, 很容易做出來。至於 chain, 這是模仿 C++ 的 loki 中的 functor 中的 chain. 效果很單純,就是把 function 串起來而已。這拿來做 callback 應該還算方便,例如:

button.on_click = menu.method(:popup).chain button.method(:hide)
接著當按鈕被按下去後,選單就會彈出,且按鈕自動隱藏。
至於 arguments 和 returns 呢?arguments 會統一給所有人。
f1.chain(f2)['XD']
這樣 f1 和 f2 都會接到 'XD' 這個 argument. return 則會蒐集成一個 array 並 flatten 回去。
[f1 的結果, f2 的結果, f3 的結果,...]
如果 f3 的結果是 array, 則會依序儲存:
[..., f3 的結果1, f3 的結果2, f4 的結果, ...]

這樣做的原因是要讓 chain 還能繼續 chain 而不會出現非常恐怖的 nested array.
f1.chain(f2).chain(f3).chain(f4)
但是其實可以這樣 chain:
f1.chain(f2, f3, f4)
結果和上面的會是相同的。

在 chain 之間的 travel 還沒做,下次有機會時會做。

gem install ludy # to see detail

ruby 寫起來真的很簡潔,很多功能 10 行內都能解決。

2007.08.07