10/30/2007

Mongrel 1.0.4 出了

    在歷經將近十個月的等待, Mongrel 終於推出了 1.0.3 版,當然依照慣例,很快的馬上就被抓到很多 Bug,隔天就出了 Mongrel 1.0.4 版。安裝方式官方建議是
    gem install mongrel --include-dependencies -P HighSecurity
    不過,最重要的是 release note 最後一句
    Mongrel 1.1 is coming real soon now with JRuby support and a few other things.
    所以,Mongrel 1.1 要出來了,並且還要加上 JRuby Support。

      It's all about JavaScript

      進入公司至今已經一個多禮拜,因為 Project 因素,整天都在碰 Javascript。其實還蠻充實的,而且也學到不少東西,或許我該為這個Blog改個名字叫 LightyJS 之類 :p 很多時候當你真的寫過 Javascript,才發現 RJS 的好處。總之,或許我該多寫點 Javascript 的東西。






      10/17/2007

      新訓結束了,國防役開始

      入伍前,PTT 上面的版眾已經告誡大家某營某連很可怕,但是還是被國防部排進去這個連隊。經過一連串的酸甜苦辣,我從某精實連出來了。因為這個連實在是很精實,所以沒時間看書。我的HTTP:The Definitive Guide也沒翻到百頁。接下來的一周,找房子,搬家,適應新公司等等大概可以讓我多忙個兩週。看來 LightyRoR 要重新出發可能也要等個幾天。對了,我國防役去友邁科技(主力產品 Urmap.com)。

      還有,最近對讀 Rails Source,玩 Xen ,玩 Linux From Scratch,玩仙劍奇俠傳4 很有興趣。真不知道有沒有時間讓我這樣玩。會不會到最後,只有電動玩到了,其他的東西全部都沒時間呢 :p

      總之,我回來了。

      9/08/2007

      AK47 與 M16 補充

      大家好,我又放假了。看到有人問我,上篇文章,意味著誰代表RoR。我只能說,沒有特別的誰代表誰。

      在技術水準差不多的狀態下,所謂的設計好壞其實只是取捨。Ak47 取了簡單,但是失去了準度。M16 強調準度,但是輕巧的設計卻換來了些許的不穩定,以及較高的訓練時間。換來的結果也沒有誰輸誰贏,只是剛好第三世界國家適合 AK47 ,美國適合 M16。

      越戰這個 case ,Ak47 遠勝於 M16,但是這是在叢林戰這個 domain。其實美軍打贏那麼多場戰役,很多其他的戰爭 case,M16 說不定在其他 case 壓的死死的。只是越戰名氣大而已。

      一切都只是需求的不同而已。

      軟體開發也是如此。在工程師水準相近的情況下,你的選擇通常都只是因為這個任務的需求,而非這個設計方式比較好。

      我所陳述的,是一段歷史,是一個工程設計哲學的頂尖對決,是一個值得去瞭解的他山之石。而非誰代表誰。

      9/01/2007

      AK47 與 M16

      大家好,我是 thegiive,我正在服國防役新訓,先跟大家打個招呼。最近一直沒時間碰 Ruby on Rails ,不過軍中倒是花了點時間研究步槍。賢者總是說到「觸類旁通」這件事情,我也剛好從步槍這種殺人兵器中,看到了一些東西,很值得軟體開發借鏡。在此先警告一下,我只是在陳述輕兵器設計哲學之間的比較,並非鼓吹輕兵器,戰爭等東西,如果認為槍枝是邪惡不能被提及之人,請勿進入本文。

      我研究的東西,就是號稱 20世紀最偉大的武器 AK47 ,跟屈居第二的 M16 的之間的瑜亮之爭。

      AK47 是由蘇聯槍械設計師米哈伊爾·季莫費耶維奇·卡拉什尼科夫設計的自動步槍。這款武器號稱槍中之王,很多第三世界國家的戰爭都看到他的身影。可以說是哪裡有戰爭,那裡就有 AK47。有這樣一句話:「美國出口的是可口可樂,日本出口的是Sony電器,而蘇聯出口的是AK47。」

      這不是沒有原因的。 AK47 當初設計的哲學,就是為了「簡單」。他被設計成為任何民眾,在沒有人教導,沒有任何工具,依舊可以在三個小時之內可以徒手拆解結合,並且裝子彈射擊,由此可以知道他的「簡單」,到底是簡單到什麼程度。

      再來因為俄羅斯環境氣候糟糕,他將內部機構組織設計為較粗糙,這代表不容易因為沙土,泥水進去而導致不能擊發,也就是他當初就是為了設計為可以穩定的擊發子彈,就算再惡劣的環境也是如此。後來因為 AK47 大流行,參加了許多場戰爭,擁有相當多的實戰測試,導致他的擊發穩定性更上一層樓。有人戲稱「AK47 掉到泥水中,沒關係甩一甩,繼續打」。

      但是當初社記者因為為了「簡單」,「穩定」,損失了些許射擊準度。導致 300M 外射擊準度很差。

      M16 系列是屬於美國系統,他當初設計的哲學就是為了擁有穩定的設計準度,以及良好的射擊距離。所以他採取相當巧妙的設計(我們輕兵器教官是這樣講的,我沒有研究很深),但是越精巧的系統代表容錯率越低, 第一版 M16 對於環境的容忍程度較差,很容易因為環境因素導致卡彈。美軍在沙漠風暴之中,就常聽說美軍為了避免沙子進入槍口,用保險套套住槍口,形成一種很詭異的情況。

      綜合比較兩者優劣,很明顯的 M16 在射擊準度上面優於 AK47 許多,但是耐用度上面,第一版的 M16 可以說是遠遠輸了 AK47,一直到 M16A2 才比較好一點,不過還是輸給 Ak47 。

      使用率上面,AK47遠遠的贏過 M16,傳說 AK47 有一億隻之多。造成 AK47 大流行的原因很多,
      1. 一來是因為構造簡單,上手簡單,士兵幾乎不用訓練就可以擊發,所以許多第三世界國家喜歡使用 AK47。
      2. 再來就是因為構造簡單,容易仿造,所以很多國家都自己仿冒 AK47,傳說十支 Ak47 只有一支是正版的 AK47。
      3. 再來就是如果你有看軍火之王這部片子,裡面講到蘇聯瓦解,許多將軍失去以前的榮光,為了錢,他們將軍火庫裡面成千上萬的 AK47 盜賣給軍火商,導致黑市 AK47 大流行,既便宜又好用。
      4. 最後雖然 AK47 不準,但是第三國家沒有資源去花時間訓練士兵射擊準度,所以使用 AK47 根本沒差

      這裡必須要提一件題外話,如果你看過血鑽石這部片子,就知道他們是怎麼訓練娃娃兵的。而娃娃兵雖然知識水準不高,但是因為 AK47 實在是使用上太簡單了,所以導致軍閥隨隨便便訓練娃娃兵,他們都可以上手,拿槍來殺人, AK47 的簡單某種程度導致娃娃兵的大流行。這是讓我每次看到都覺得很荒謬的一件事情。

      真正決定 AK47 王者地位的戰役就是越戰。越南一方使用 AK47,美軍使用 M16。越戰是叢林戰為主,叢林戰的攻擊射程可能根本不到 10 m ,在叢林 AK47 跟 M16 射擊射程都是一樣的,也就是 M16 的強項射擊距離跟經準度就跟廢了一樣。再加上叢林環境太過惡劣了,M16的低容錯率造成大量卡彈的發生,很多美軍屍體旁邊都有卡彈的 M16,代表他們是因為卡彈才死去的。到後來,甚至出現許多美軍將自己 M16 放在一旁,反而使用搶來的 Ak47 來打越南軍的怪現象。

      事後想想,AK47 早就已經實戰好幾年了,那時的穩定度以經是 3.0 版。M16 在越戰時穩定度還是 1.0 Pre release 版。再來剛好遇到 M16 強項被極小化,所以才會遇到被 AK47 八的那麼慘的情況。

      雖然剛剛講了那麼多 M16 的壞現象,但是他依舊美軍最佳的利器。因為美軍戰爭時需要「減少傷亡」,減少傷亡最好的方式就是拉遠戰鬥,所以 Ak47 只能較近距離的特性依舊不適合美軍。也就是說,沒有最好的槍枝,只有最適合這次任務的槍枝。

      所謂的簡單就是美,在 AK47 留下的最好的證明。簡單的設計容易修改,複製。產品正確率也較高。並且容易上手就容易造成流行。軟體又何嘗不是如此。

      穩定性的重要,在輕兵器的領域優先權是相當高的,畢竟子彈往身上跑過來的時候,誰都不希望自己手上的槍枝卡彈。越戰時,AK47就在這個領域上面遠遠勝過 M16,也為這次比試留下了勝利。當某個領域的要求穩定性,已經高到一個很重要的程度時,我們應該選擇比較穩定的設計,而非功能比較強大的設計。

      最後,沒有最好的槍枝,只有最合適這次任務的槍枝。同樣的,沒有最好的軟體,只有最符合這次任務需求的軟體。

      我們在這些輕兵器的歷史中,是不是看到許多軟體開發的影子呢。

      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