顯示具有 ruby 標籤的文章。 顯示所有文章
顯示具有 ruby 標籤的文章。 顯示所有文章

12/22/2008

Ruby Memory Leak 巨大問題以及解決方式

Ruby Memory Leak 一向是一個難以解決的議題,不過在今年 11月12日的 Mailing List 裡面有人提出一個重要的問題, Ruby 的 callcc 在使用中產生相當明顯的 Memory Leak。後來有高手 Brent Roman 發表他的發現。他原本再 ARM CPU 上面開發他的 Ruby Robot 程式(Ruby 1.6.8),他的機器人上面只有 32 MB 的 Memory,不過他的 Ruby 程式一天就會吃掉 20MB 的 Memory,經過他的 hack ,現在他的 Ruby 程式已經穩定在 10MB 以下,他發現 Ruby Memory Leak 很大部分在於 pointer ,跟 callcc 的使用上,Javaeye 上面有對於這個問題的詳細中文翻譯


Matz 知道之後,相當鼓勵 Brent 將他的 Patch 放到 Ruby 上面,如今他的 Patch 終於發佈,他是根據 Ruby 1.8.7-p72 做的 Patch ,又由於他在 Monterey Bay Aquarium Research Institute工作,所以叫做 1.8.7-p72 MBARI Patch,目前處於 Alpha 版本。他發現經過他的 hack 之後,對於 Memory Leak 上有相當大的解決。根據他的用 Ruby 預設的 Test Case 的結果,他發現 Memory 消耗從 97MB 降低到 57 MB,JavaEye 也利用他們的程式做測試,Javeeye Fcgi Memory 消耗量從 129MB 降低為 99MB。


相當不錯的結果,期待納入 Ruby 1.8.7 的那一天。


1/07/2008

ludy 0.0.9 released

其實後來的更新幾乎都是亂寫了,這次的更新也差不了太多。大抵上就是增加個 C++ TR1 style 的 bind... 用起來的味道大概是這樣:(如果熟 C++ TR1/boost bind 的話,一眼就知道了)

assert_equal [9,8,7], ([1,2,3].map &(lambda{|lhs, rhs| lhs-rhs}.bind 10, :_1))
assert_equal [3,2,1], (lambda{|a,b,c| [a,b,c]}.bind :_3, :_2, :_1)[1,2,3]
assert_equal [1,9,3], (lambda{|a,b,c| [a,b,c]}.bind :_1, 9, :_3)[1,2,3]
assert_equal [9,2,3], (lambda{|a,b,c| [a,b,c]}.bind 9)[2,3]
assert_equal [9,4,2], (lambda{|a,b,c| [a,b,c]}.bind 9, :_3)[2,3,4]

不過我總覺得 ludy 應該可以繼續走下去才對。所以我想在下次做一次 major update, 版本號大概就是 0.1.0 吧。會做的改變大概如下:

1. 整理 project directory structure, 現在根本就是亂七八糟。我想大概會用 gem bones 去做吧。其實我是比較 prefer hoe, 但是 bones 的 rake task 有 namespace 比較漂亮... 如果 hoe 會更新到使用 namespace 的話,我再 switch 過去。現階段,我想 bones 是個不錯的選擇。

使用這種東西的好處,當然就是不用去設計 directory structure... 而且自己寫 gemspec 老實說也真的是滿麻煩的,bones 和 hoe 都有 rake task 幫我 build gem 檔,甚至還有 publish, 多方便。

2. 思索 puzzle_generator 的未來。其實他跟 ludy 一點關係都沒有,只是我為了使用 svn server 所以才把他加到 ludy 上的。到底 puzzle_generator 應該分出來,還是在 ludy 中找到一個位子?分出來的話,又該分去哪裡?

3. ruby 1.9 compatibility, 我剛剛稍微測試了一下,有幾個比較髒的東西在 ruby 1.9 是跑不起來的。這個應該要想辦法修掉,ruby 1.9 很棒的。除了效能好很多以外,多了很多我想要很久的功能,能早點轉過去就早點。可惜的是 1.9 問題還很多,而且很多 gem 沒辦法在上面跑,所以 1.8 還是必備的。現階段會讓 1.8 和 1.9 都相容,等 1.9 夠穩了,就不會考慮支援 1.8 了。

4. require path 的麻煩問題。去看看 facets, 他們對於 require path 大概也很頭痛吧。從 2.0.0 就整個把 require path 翻修過,結果還有大 bug, 害我完全沒辦法用。果然很快就推出 2.0.1 了...。還是 2.0.1 ~ 2.0.2 的階段,我忘了。反正就是很可笑的狀況,名稱衝突之類的。bug 期間我是暫時 monkey patch.

再看看 rubygems, 一開始有個 require_gem. 而我,卻也弄了個 require_ludy. 沒辦法,因為很難寫一個在任何情況下都有效的 require, 所以才搞出那種東西。但是現在我覺得,還是應該多為 user 想想,畢竟連 rubygems 都捨棄 require_gem, 我也不該繼續使用 require_ludy 才對。

無奈這真的是個很大的問題,可能勢必得做得不要那麼 general 吧?大致問題如下:
a. 使用 gem install 時可以正常 require.
b. 把東西全部 copy 到 project path 時, 讓 -I 可以 work.

其實簡單地說,就是希望不要 gem install 也可以輕鬆放到專案中使用。不過也許得放棄這個要求吧?因為實際在寫時,就會發現很多問題都跟這有關。如果能「假設」gem 一定有安裝起來的話,很多考量就可以不用做了。

事實上,不需要安裝 gem 也能 work, 對於開發也很有用。因為我可以改改程式就測試最新的結果,而不必自己裝一份。所以,還是再看看吧。還是希望能有好方法。

5. unit testing. 既然是 unit testing, 當然就要有能夠分開 test 的動作。我希望可以 ruby test/tc_bind.rb, 也可以 rake test 測試所有的 unit test. 之前因為沒在用 rake, 我自己寫了個 ts_ludy.rb, 大概就是做這件事。所以 ruby test/ts_ludy.rb 就可以跑所有的測試。

而我發現,如果照 unit test 原先的設計,把所有東西都 require 起來再跑,執行效能會變得很爛很爛。原因我不是很清楚... 所以後來我改變作法,變成一個個去跑每個 unit test, 然後把輸出結果蒐集起來,再報告出去。這樣的好處就是跑得速度真的快太多了,好幾倍的差別。缺點當然就是,這樣做挺詭異的...

所以關於 testing 的部份,也還需要再整理一下。大致就是希望能單獨跑也能一起跑。另一方面,我要求 testing 所使用的 lib 應該要是相對路徑下的,而不是 gem 上的。理由同 require path 上面的描述。

其實對於 require path 和 unit testing 的部份,我花了不少功夫,不過成果其實還滿差的。當時看是沒什麼感覺,現在看覺得實在很醜。

6. 去掉 shebang, 那很蠢。(那時候對 shell 太不熟了)

7. ludy_ext.rb
這東西,本來是想放各個沒在 Ludy module 下的東西,不過這樣成長太噁心了。應該學學 facets, 一個 method 一個檔才對。所以這部份也要切開,整理一下。

8. 整合之前為了 shooting-cubes 做的一些 rake task, 還有 erb meta-programming 的一些相關 method. 這個,和 puzzle_generator 一樣好像有點尷尬,不太屬於 ludy 的範疇。
不過在整個 ludy 東西還很少的狀況下,我希望盡量整合一些 lib.

9. 儘快整合 multi, 那個 multi-method 的 lib. 我在想,我可能自己實作一份,就不要直接把他吃進來。multi 是 MIT license, 我想我直接吃進來應該沒問題,只是整個感覺就很怪,而且他的程式有些部份我不太滿意,所以也許我自己做一份還是比較好,這樣也不用想怎麼合併。

如果確實做出來了,那大概就只留一份 NOTICE 或感謝之類的,就不保留原本的 require path 了。不然,我真的覺得那樣很醜,像是分裂的個體似的。

10. 修改一些文字說明,包含 README, CHANGES, description 之類的。現在的格式已經有點接近亂寫了。另一方面,也希望稍微修改一下 rubyforge 上的 release 結構。

11. 撰寫 rdoc, 完全沒 doc 其實也有點奇怪...

*

所有的 source code 都採用 Apache License 2.0, 希望下次的更新可以涵蓋以上幾點。

btw, bind 實作只有 13 行(含註解)

安裝:
gem install ludy

取得所有程式:
svn checkout http://ludy.rubyforge.org/svn/
or
svk mirror http://ludy.rubyforge.org/svn/ //mirror/ludy

12/30/2007

Ruby 1.9 Release

Ruby 1.9 Release了,根據Ruby慣例,版本小數點後面的數字偶數是穩定版本,奇數是測試用的版本。所以 Ruby 1.9 算是 Ruby 2.0 之前的開發版本,現行的穩定版本還是 Ruby 1.8,所以請不要把你公司的 Production 機器都改成 Ruby 1.9。

1.9 除了語法上面的演進以外,最大的不同就是在於內建 YARV 這個 VM。對於 Ruby 整體效能有相當大的增快,如果 Ruby 跟 Python 2 Fibonacci Sequence 的測試,大概比 Python 2 快了三倍。However ,跟幾年前的 Release 比速度,還真的沒啥好誇耀的 :p

Download 點在這
ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.0-0.tar.gz

Change in Ruby 1.9
http://eigenclass.org/hiki.rb?Changes+in+Ruby+1.9

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

7/25/2007

[心得] RubyCocoa

轉自 ptt MacDev 板

作者 godfat (godfat 真常) 看板 MacDev
標題 [心得] RubyCocoa
時間 Tue Jul 24 21:40:20 2007
──────────────────

嫌 objective-c 太難用嗎?還在等 obj-c 2.0 嗎?先試試 RubyCocoa 吧 XD
http://rubycocoa.sourceforge.net/

[quote]
It lets you write full-stack Cocoa applications in Ruby. It is also possible to write a Cocoa application that mixes Ruby and Objective-C code.
[/quote]

how to install?
有灌 macports 的話:(當然,xcode 是一定要灌的)
sudo port install ruby
sudo port install rb-rubygems(非必要,但建議)
sudo port install rb-cocoa

文件放在:
/Developer/Documentation/RubyCocoa

範例放在:(相當豐富)
/Developer/Examples/RubyCocoa

我這裡有一個簡化的 hello world, 可以小瞥一下:

require 'osx/cocoa'
include OSX

app = NSApplication.sharedApplication

hello = NSWindow.alloc.initWithContentRect_styleMask_backing_defer [200.0, 300.0, 100.0, 100.0], 15, 2, 0
hello.setTitle 'Hello'

btn = NSButton.alloc.initWithFrame [10.0, 10.0, 80.0, 80.0]
hello.contentView.addSubview btn
btn.setTitle 'Hello World!'
btn.setAction 'stop:'
btn.setBezelStyle 4

hello.display
hello.orderFrontRegardless

app.run

另外可以順便請教一下,如何讓視窗在按下 X 時關閉而不是隱藏嗎?我沒寫過 obj-c(XD), 昨天文件翻來翻去找不到要怎麼做



另一方面,如果只是要做 GUI 而不需要 call mac 的 api 的話,個人強力推薦 QtRuby

sudo port install qt4-mac(這個會 build 很久)
sudo port install cmake

QtRuby 沒有這麼方便的安裝法,要到
http://rubyforge.org/projects/korundum/
抓 qt4-qtruby, 然後進到目錄下:

cmake .
make
sudo make install

於是就可以正常使用 QtRuby 了,hello world 如下:

require 'qt'
include Qt
app = Application.new ARGV
hello = PushButton.new "Hello World!"
hello.resize 100, 30
hello.show
app.exec



Qt 是什麼?
http://trolltech.com/products/qt
是我用過最棒的 GUI toolkit, 使用 C++ 語言。可用 GPL 授權或用商業授權(很貴),二擇一(這是 Qt 唯一被批評的點)

Ruby 是什麼?
http://ruby-lang.org/
是我用過最方便的 programming language, 使用 Ruby license(很自由)歡迎來 ptt Ruby 板參觀 :p



Mac OS 10.5 將搭載 Ruby 1.8.6 + Rails 1.2.3, 想試試 Rails 寫網站嗎?
sudo gem install rails
不需要等 10.5 就可以輕鬆試驗了 XD
http://rubyonrails.org/

==

發完神經了,有傷眼請見諒 XD

--
#!/usr/bin/ruby          [露比] /Programming (Kn|N)ight/            看板《Ruby》
# if a dog nailed extra legs that http://www.ptt.cc/bbs/Ruby/index.html
# walks like an octopus, and Welcome ~Ruby@ptt~
# talks like an octopus, then ◢█◣ http://www.ruby-lang.org/
# we are happy to treat it as █ http://www.ruby-doc.org/
# if it were an octopus. ◥ ◤ http://www.rubyforge.org/
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 220.135.28.18

7/08/2007

ruby facets

老實講,ruby 有一個地方真討厭,那就是每次都把我想做的東西做完了。有了 ruby, 我不覺得還有需要再自己弄一個程式語言,做得比我想像中的還好。唯一的缺點大概只有 lib 還不夠多,但有了 facets 的話,那也真的是差不多了。
http://facets.rubyforge.org/
該有的幾乎都有了,差的只剩下一些小東西。如果早點發現這東西,大概就不會想弄 ludy 了吧。

不過想用 facets 有點麻煩,例如我想用 Array#shuffle, 就必須
require 'rubygems'
require 'facets'
require 'array/shuffle'
這麻煩到爆炸了吧!!不過有個好東西:

require 'rubygems'
require 'facets/automatic'

接下來,就放心使用 Array#shuffle 吧!因為 automatic 會自動幫你 require... 我沒去看他的實做,但我猜是建 table 吧。有一個小缺點,例如你想這樣做:

[1,2,3].shuffle.map &:to_s

很不幸,這樣會失敗。shuffle 可以正常自動 require, 但是 Symbol#to_proc 不能藉由這個方法自動 require. 如果你寫:

[1,2,3].shuffle.map &:to_s.to_proc

這樣是 ok 的,automatic 可以自動 require 該 require 的東西。問題大概出在,&:to_s 是 implicit call, automatic 偵測不到。所以第一次使用,還是必須明確說明需要用到 Symbol#to_proc, 接下來就可以快樂地使用

[1,2,3].shuffle.map &:to_s

了。或是前面自己手動 require:

require 'rubygems'
require 'facets'
require 'facets/automatic'

第二行的 require 可以讓你使用 Symbol#to_proc, 所以就可以直接寫:

[1,2,3].shuffle.map &:to_s

快去安裝 facets 吧!!
gem install facets
另一個 ruby web framework nitro 0.41.0 也有用到 facets !! 雖然是用 1.4.5 版的,而現在最新的 facets 是 1.8.54(以 2007.07.08 為準的最新版本)有機會也可以試試 nitro
gem install nitro
官方網站是:
http://www.nitroproject.org/
缺點非常明確!幾乎沒有文件 XD 我很少看到一個東西的文件可以少到這樣。

不多說了,我現在頭痛就算了,眼睛也很痛。

5/15/2007

心愛的 Object 變心啦

剛剛發現一個很有趣的事情,寫 code 的時候,有兩個 Model :Person 跟 Setting ,他們呈現 1: 1 關係。結果當我想要創立一個新的 Person 的時候,為了希望能夠簡化設計,我就將其中一個人 a 當作預設值,每個新增 Person 的 Setting 直接 copy 一份過去,所以我就這樣寫




a = Person.find(1)
b = Person.new( :name => 'lala' )
b.setting = a.setting
b.save

我真的沒想太多,但是慘劇就發生了。a.setting 就變心到 b 去了。

>> a = User.find 1
=> #<Person:0x371ee38 @attributes={"updated_at"=>"2007-05-15 22:55:45", "nname"=>"hemidemi lala", "type"=>"GroupAdmin", "id"=>"1", "password"=>"123", "created_at"=>"2007-05-09 00:46:35"}>
>> a.setting
=> nil

也就是他自動的幫你把 a.setting 的 model 裡面的 foreign key 指定到新增的 b ,然後一去不回頭。要預防 object 變心方法也很簡單,就是幫新人找一份完全一模一樣的新伴侶即可。

a = Person.find(1)
b = Person.new( :name => 'lala' )
b.setting = a.setting.clone
b.save

Clone 是 Ruby object 裡面的 method ,作法就是 copy 一份新的 instance。雖然在實際使用上, 其實 clone 並不會直接 new 一份真的 instance,而是 new 一個 object ,然後將裡面 attribute 直接 reference 過去[註1]。但是在 ActiveRecord 裡面使用,因為是直接寫回資料庫,所以就完全沒副作用,所以可以盡量大方的使用。

註1

irb(main):001:0> class Klass
irb(main):002:1> attr_accessor :str
irb(main):003:1> end
irb(main):004:0> s1 = Klass.new => #<Klass:0x89e7c>
irb(main):007:0> s1.str = 'Hello' => "Hello"
irb(main):008:0> s2 = s1.clone => #<Klass:0x79d38 @str="Hello">
irb(main):009:0> s1.object_id => 282430
irb(main):010:0> s2.object_id => 249500
irb(main):011:0> s1.str.object_id => 261360
irb(main):012:0> s2.str.object_id => 261360

我們可以發現到 s1 跟 s2 其實是兩個不同的 object ,但是裡面的 attribute str 卻是同一個 object。




5/05/2007

script/plugin

well, 由於我跟 Rails 不熟,所以很多地方只能憑空臆測,如果有誤也望請指點。很多跟 Rails 有直接關係的細節我也難以深究,所以大概只能從 Ruby 的角度看下去。總而言之呢,Rails 的 plugin 比起 rubygems 還更要簡單得多,根本沒有任何需要設定的部份,只要把目錄開好就可以輕易使用了。目錄結構大約是:

init.rb
install.rb
lib/*.rb
test/*.rb

init.rb 是 Rails 在 load up 時會執行的部份,所以 plugin 要把 init up 的程式碼放到這邊,例如最常見的恐怕是:
ActiveRecord::Base.send(:include, OOO)
這邊使用 send 而不是直接用 . 的緣故是 include 本身是 private method, 用 . 的會有 NoMethodError. 不過我建議可能可以考慮使用 __send__ 而非 send, 因為有些 class 的 send 有被 override, 一不小心可能會有未預期的錯誤。(雖然應該一看就知道會是出什麼問題就是了)

install.rb 則是在安裝時才會執行的程式,例如使用 script/plugin install OOO 就會先執行此 install.rb, 而日後不管何時使用此 plugin 時,install.rb 都不會再被執行。這部份可能會放的程式碼也許是會動到目錄結構的程式,例如在 app/models 下寫入 OOO_plugin.rb, 像這種只需要在安裝時執行一次即可。這樣做有什麼好處呢?我想感覺就有點像 script/generate OOO_plugin 的感覺吧。

至於 lib/ 則會自動加入 load path, 這部份跟 rubygems 裡 gemspec 裡的 s.require_path = 'lib' 是相同的概念。test/ 的部份也和 rubygems 相同。

接下來還有什麼好講的嗎?有的,參考〈acts_as_taggable Plugin 使用方式〉一文,可以發現 acts_as_taggable.rb 寫得相當複雜,這當然是有原因的。我參考了官方網站裡跟 plugin 有關的系列文章,發現 ActsAsOOO 是個相當常見的 plugin 模式,其中似乎有某種 pattern 在其中…。就是 ClassMethods, SingletonMethods, 與 InstanceMethods.

這樣做的原因很簡單,就只是加強模組化與避免名稱污染,所以多繞了幾個圈。在這種模式的做法下,ActiveRecord::Base 所多出來的 method 只有 acts_as_taggable. 而當你在寫 class MyModel < ActiveRecord::Base; acts_as_taggable; end 時,只有 MyModel 多出 class method => find_tagged_with, instance methods => tag_with, tag_list. 而不是整個 ActiveRecord::Base 都多出這些 methods.

其實這做法也有點像 meta-programming, 只不過是用 mixin 的形式。在前一陣子裡,由於我搞不太清楚 include 與 extend 的差異,所以調查了一下,並做了些筆記:(我知道多半連不上,所以 copy 一份到這)

=begin
2007.02.13

include 將 module 內的所有 method 原封不動拿過來,extend 則是在原本的 method 前面多加個 self. 即 class C 內,寫 include M 則追加 instance method. 如果寫 extend M 則追加 class method.

module 內也可以使用 include 與 extend, 結果同 class. include 為 instance level, extend 為 class level. 基本上這兩樣東西是很接近的。至於 module level 的 method, 則不受 include 也不受 extend 影響。其實跟 meta-programming 很像。

module M
def m; end # 只能由 include 或 extend 取用
def self.m; end # 只能用 M.m 來呼叫
end

class C
include C # 獲得 instance method m
extend C # 獲得 class method m
end
=end

除此之外,module 還有個特別的東西叫 module_function, 這東西我還沒仔細研究,但初步認識的結果是,他會造成指定的 method 同時定義 def m; end 與 def self.m; end 詳細的內容可以當成讀者練習,呵。(雖然其實可能就只是這樣而已)

回到 acts_as_taggable, 在 module Taggable 中唯一的 method 是:
def self.included(base)
base.extend(ClassMethods)
end
這個相信大家都很熟了,就是指當此 module 被 include 時會執行的 method, 有點 hook 的概念。這裡的 base 就是 includer, who include this module. 在 Taggable 中,當然就是 ActiveRecord::Base 了。extend 上面看到了,會促使該 class 擁有此 module 中所有的 method 成為 class method, 所以 ActiveRecord::Base 多了個 acts_as_taggable 為 class method. 如此一來,就能寫:
class MyModel < ActiveRecord::Base
acts_as_taggable
end

其實我覺得這樣實作有點過於複雜,我會比較喜歡這種形式:
class ActiveRecord::Base
def self.acts_as_taggable
# ...
end
end
這樣就一目了然了。缺點當然就是會比較難將此 method 丟給其他人用,例如假設哪天有了一個 class 是 PassiveRecord, 也想將 act_as_taggable 丟給他使用,可能就必須 copy & paste, 或是一些非常詭譎的方式,這邊不多提。

而在 acts_as_taggable 的最下方,呼叫了:
include ActiveRecord::Acts::Taggable::InstanceMethods
extend ActiveRecord::Acts::Taggable::SingletonMethods
的意思就非常明顯了,將 module InstanceMethods 的 method 變成 MyModel 的 instance method, 將 module SingletonMethods 的 method 變成 MyModel 的 class method.(不太明白為何要取做 SinglethonMethods, 也許只是因為 ClassMethods 用過了吧?)

原因是 :include 與 :extend 的 message receiver 是 MyModel 而不是 ActiveRecord::Base, 所以被擴充的是 MyModel; ActiveRecord::Base 不受影響。

大抵上就是這樣了。

abstract:
init.rb 在 Rails load up 後會執行
install.rb 在 plugin install 後會執行
lib/ 自動加入 load path

下次再研究 rake 要怎麼用,相信以之拿來做 test 會很方便。

2007.05.05 godfat 真常

延伸閱讀

5/02/2007

require 'rubygems'

寫到一半因為有其他事所以先放著,結果回來就是八小時後了…。
有夠累。

==

大致調查(survey?)了一下要怎麼樣把程式 package 成 rubygem, 主要參考書籍當然是 Programming Ruby 2nd, 這本我看好久還是沒看完的書。(還不都是因為他太厚了…)在 Creating Your Own Gems 這一節中,相當詳細地描述了如何打包(在 MtG 中,不知為何把 entwine 翻譯成打包,瞬間從 spell 的意味變成像外帶一樣)。這邊將簡單示範一次我如何發佈 ludy 的。

首先呢,rubygems 有建議的檔案配置(layout),可以不依照這個規則來做,但一般來說建議使用跟別人相同的方式。所有雜七雜八的東西放在根目錄下,例如 ludy-0.0.1 裡面放的有:ludy.gemspec, LICENSE, NOTICE, README. 其他目錄則是很常見的:bin, doc, lib, test. 我沒有 bin 也沒有 doc, 所以只有放 lib 和 test 兩個目錄。

顧名思義,lib 裡面放的就是 ludy 本身的 source code, 專門拿來給人 require 用。而 test 底下則是所有的 test program, 以 tc_ 開頭的是 TestCase, 可以單獨執行,但主要是由 ts_ 開頭的 TestSuite 去 require 起來,然後 ts_ 會自動執行這些 tc_.

所以我的 ts_ludy.rb 裡面只有兩行:

require(File.join(File.dirname(__FILE__), '..', 'lib', 'ludy'))
require_all_in_dir __FILE__

第一行是把 lib/ludy.rb require 進來,這是使用 ludy 部份元件的必要手續。這樣做的理由是有些 path 問題很煩,所以我把處理 path 的 func 寫在 ludy.rb 中,如此一來需要確保路徑的只有 ludy.rb 這一個檔案。至於為何這裡要使用相對路徑去 require 呢?因為我堅持兩種使用 ludy 的方式,一個是安裝成 gem, 另一個是直接丟到 project 目錄下,使用路徑去 require. 前者不用說,可以有一個很標準的方式使用,但後者就不一定了。

為了處理這個問題,ludy 內部需要某些其他 ludy tool 時,就改用 require_ludy, 集中處理 require 問題,避免重複把相同路徑加入 load path. 使用者當然也可以使用 require_ludy, 或是依然習慣使用 require, 那麼只要確定自己 require 的 path 是正確的就好了,ludy 內部不會有路徑問題。至於 gem version, require 的方式就很單純用 require 'ludy/tool_name' 或是 require_ludy 'tool_name' 就好了。

第二行,則是把所有 tc_ 開頭的 TestCase require 進 TestSuite 裡面,(其實是同個目錄下所有的 .rb 檔,只是現在 test/ 下只有 tc_ 和正在執行的 ts_ 而已)unit test 就會自動執行了。不過這個 require_all_in_dir 的第一個參數卻是檔案名稱,名字好像取得不太好的樣子。

ok, 也就是說我要打包的東西是 ludy.gemspec, LICENSE, NOTICE, README, lib/*.rb, test/*.rb. 其中 lib/ 底下還有其他資料夾,也要一併打包。那麼寫好的 gemspec 就是


require 'rubygems'

spec = Gem::Specification.new{|s|
s.name = 'ludy'
s.version = '0.0.1'
s.author = 'Lin Jen-Shin(a.k.a. godfat)'
s.email = 'strip number: 135godfat7911@246gmail.890com'
s.homepage = 'http://ludy.rubyforge.org/'
s.platform = Gem::Platform::RUBY
s.summary = 'Aims to extend Ruby standard library, providing some useful
tools that\'s not existed in the standard library.'
candidates = Dir.glob '{bin,doc,lib,test}/**/*'
candidates+= Dir.glob '*'
s.files = candidates.delete_if{|item|
item.include?('CVS') || item.include?('rdoc') ||
File.extname(item) == '.gem'
}

s.require_path = 'lib'
s.autorequire = 'ludy'
s.test_file = 'test/ts_ludy.rb'
s.has_rdoc = false
# s.extra_rdoc_files = []
# s.add_dependency 'multi', '>=0.1'
}

if $0 == __FILE__
Gem::manage_gems
Gem::Builder.new(spec).build
end


其中 Dir.glob 的部份我還沒研究,所以寫得很爛,居然用補正的方式。反正就是全部抄自 Programming Ruby 2nd, 再做一點修修改改。其中最下面的 if $0 == __FILE__ 可以判斷這個程式是不是直接被 ruby interpreter 執行?如果是的話,就呼叫建立 rubygem 的 method. 不是的話,就只要定義 spec 就好了。

前者就是:
ruby ludy.gemspec
或是如果有加 #! 的話:
./ludy.gemspec
如果 ludy.gemspec 在現在目錄下的話。

後者就是:
gem build ludy.gemspec

這兩種方式都可以產生 ludy-0.0.1.gem. 接著再下:
gem install ludy
就可以正確安裝 ludy gem 了。至於 remote 的部份呢?只要上傳到 rubyforge 的 file release system 上,rubyforge 每日會有數次掃描裡面的 *.gem, 自動放到 rubygems 的 repository. 這件事我已經做完了,所以現在可以直接:

gem install ludy

就能看到我寫的一點程式了 :)
不過其實我只是在測試功能而已,本來是想標版本號 0.0.0, 可惜這樣用:

gem uninstall ludy

時會出一點問題。所以就還是標成 0.0.1, 但未來的更新暫時都不改版本號。正式釋出時大概會是 0.0.2 吧 :p 還是歡迎大家抓回去試試,使用方法全在 unit test 裡面,文件懶得寫了。

homepage:
http://ludy.rubyforge.org/
有空時會弄個應有的版面出來。

2007.05.02 godfat 真常

4/24/2007

Net::HTTP 使用方式

在 UbiSunrsie 裡面介紹過的小小 Script 裡面有用到,就直接用範例來解釋一下使用方式。




GET
require 'net/http'
Net::HTTP.version_1_2
Net::HTTP.start('www.google.com.tw', 80) {|http|
response = http.get('/index.html')
puts response.body
}
基本上,Start一個 Net::HTTP Connection HOST,然後使用 http.get來抓取你要的頁面,最後用 response.body 印出來即可。

POST
require 'net/http'
Net::HTTP.version_1_2
url ='example.com'
action = '/cgi-bin/query'
Net::HTTP.start( url , 80 ) {|http|
response = http.post( action , 'var1=value1&var2=value2' )
response.body.each do |line|
puts line
end
}
稍微複雜一點的作法,除了 http.get 變成 http.post 之外,後面的 variable = value 的形式是採用 application/x-www-form-urlencoded 的形式傳遞出去的,大家應該都很熟悉。

如果不喜歡這樣,還有其他作法

require 'net/http'
require 'uri'
res = Net::HTTP.post_form(URI.parse('http://example.com'), {'var'=> 'value' , 'var2' => 'value2' })
res.body.each do |line|
puts line
end
end
好處是 value 傳遞是用 hash 方式來傳遞,比較 Ruby 化,壞處是比起 Net::HTTP.post_form ,我比較喜歡 http.post 這樣的形式。

4/16/2007

[Ruby] recursive lambda

[Ruby] recursive lambda

==本文連同引文同步載於 ptt Ruby 板、LightyRoR飽和脂肪星星之一角備份區)==

很抱歉最近狀況真的是相當糟糕,導致很多事情都沒做或是沒做好。雖然以後大概也不會比較好。這樣講講就沒關係嗎?當然不是,只是替自己找一點比較能安心的藉口吧。另外本文有任何錯誤歡迎指出。

==本文開始==

我一直覺得 Ruby 缺少一個類似 self 的東西,用來表達現在這個 function/method. 這個東西有什麼用呢?其實我也不知道有什麼用,就只是單純覺得好像少了這種東西。最直覺的例子,恐怕就是具有遞迴能力的 lambda function. 我曾在 ptt Ruby 板發過一篇文,講 quine(self-reproducing programs),後來我用了 Ruby2Ruby, 寫了像這樣的結果:(飽和脂肪星有該文的備份(通常連不上))

#!/usr/bin/ruby

require 'rubygems'
require 'ruby2ruby'

(a = proc {
puts("#!/usr/bin/ruby")
puts
puts("require 'rubygems'")
puts("require 'ruby2ruby'")
puts
print("(a = ")
print(a.to_ruby)
print(").call")
}).call


最蠢的地方是明明都用 lambda(proc) 了,我卻還得把 lambda 的結果記起來留待以後使用。這樣實在是有點無趣。我希望我可以寫:

lambda{ print(this.to_ruby); print(".call") }.call

這樣不是帥氣多了嗎?於是我開始試著思索實作這東西的可能。接著我忽然想到,所謂 this 不正是指在 call stack 最上端的 function/method 嗎?因為當我們執行到這個 function/method 時,this 一定是指同一 function, 不可能忽然去指涉其他 function, 而另外一個 function 進 call stack 時,不把他解掉也不可能會執行到 this. 於是可以把 this 寫成一個 function, 不吃任何引數,回傳一個 Proc/Method 代表正在 call stack 頂端的那個 function.

而我記得 Ruby 是有方法可以去存取 call stack... 雖然好像是用很蠢的方法,也確實是有點蠢,但總之可以用模擬的。隨意 google 了一下,找到一個很簡單的方式,就是用 set_trace_func, 丟一個 callback 進去,於是 Ruby 在各個 function 間做動作的時候,都會呼叫這個 callback. 感覺就是效率會變狂差,不過呢,至少暫時是可以用的。

接著可以利用 Thread.current[:symbol] 來儲存 current thread call stack info, 任何 function call 時,push 資料進這個假的 call stack, function return 時,pop 資料出來。這樣就有一個很簡單的 call stack info 可以用了。

以下程式歡迎任意使用,licensed under Apache License 2.0, 複製到檔案用的話希望可以把以下這段 copy 到檔案最前面… XD

#    Copyright (c) 2007, Lin Jen-Shin(a.k.a. godfat 真常)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

module Kernel
# 由於 google 到的參考程式把抓 call stack 叫 invoker,
# 所以這裡沿用他的名字。有個常數比較容易看懂程式在做什麼。
INVOKER_EVENT = 0
INVOKER_FILE = 1
INVOKER_LINE = 2
INVOKER_MSG = 3
INVOKER_BINDING = 4
INVOKER_CLASS = 5

# -1 就是 top 的意思囉
def invoker levels = -1
st = Thread.current[:callstack]
# st 有可能是 nil, 如果 invoker 先被 call 到的話。
# 雖然我不知道什麼時候會發生這種事…。levels - 2 的原因是:
# 0(stack bottom) => function that you called(this is what we want)
# 1 => Kernel#invoker
# 2 => Array #[]
# 所以去掉額外不要的額外兩個資訊。
st && st[levels - 2]
end

def this
# 因為多 call 了 this, 所以要再多去掉一個額外資訊。
info = invoker(-2)
# 這邊我本來寫成 lambda 的形式,可以正確執行,但有一個狀況
# 卻是失敗的。就是 lambda{yield}.call{} 這樣是會 error 的 :(
# 試了多次還是找不到 Proc.call 吃 block({}) 的方法,只好改寫
# 成用 Method 的形式。不知為何,Method 就可以正確使用 block...
eval("self", info[INVOKER_BINDING]).method(info[INVOKER_MSG])
end
end

set_trace_func lambda{ |*args|
case args[INVOKER_EVENT]
# 可能的有 call 和 c-call, 都是 function
when /call$/
# 這邊我搞不清楚到底是誰會先被 call, 是 call 還是 return?
# google 來的是把初始化寫在 call 裡,可是我測試都顯示是在
# return 上,所以反而是 return 的地方需要初始化。或是乾脆
# 全部拉出來在最上面初始化也可以 :)
(Thread.current[:callstack] ||= []).push args
# 同上可能有 return 和 c-return
when /return$/
(Thread.current[:callstack] ||= []).pop
end
}


以上不管有沒有問題,都可以來看一下我額外寫的幾個 unit test, 參考一下幾個我目前想到的用法。

require 'test/unit'

class TestThis < Test::Unit::TestCase
def test_fact
assert_equal(120, fact(5))
assert_equal(3628800, fact(10))
# 試用 recursive lambda
assert_equal(5040, lambda{|n| return n*this[n-1] if n>0; 1}[7])
end
def fact n
# 恐怕是最常見的用法
return n*this[n-1] if n > 0
1
end
##
def test_pass_around
# 這邊流程可能很怪,因為只是我隨便寫的,單純測試正確性罷了。
assert_equal(method(:pass_around_forward), pass_around.call(lambda{|v| v}))
end
def pass_around mode = 'pass'
case mode
when 'pass'
pass_around_forward this
else
'value'
end
end
def pass_around_forward func
assert_equal('value', func['value'])
this
end
##
def test_with_block
# 同上,流程亂寫的,單純測試正確性。
with_block{|b| assert_equal('value', b['value'])}
end
def with_block mode = 'pass', &block
case mode
when 'pass'
block[this]
else
'value'
end
end
##
def test_more_args
# Proc 就是死在這個測試,block 展開怎麼做都失敗 :(
# 改成 Method 後這邊就可以通過測試了。
more_args('get_this'){}.call('call', 1, 2, 3, 4, 5, &lambda{6})
more_args('get_this'){}.call('call', 1, 2, 3, 4, 5){6}
end
def more_args mode, a1=nil, a2=nil, a3=nil, *as, &block
case mode
when 'get_this'
this
else
assert_equal(1, a1)
assert_equal(2, a2)
assert_equal(3, a3)
assert_equal(4, as[0])
assert_equal(5, as[1])
assert_equal(nil, as[2])
assert_equal(6, yield)
assert_equal(6, block.call)
end
end
end

最後,我可以把 quine 改寫成:

#!/usr/bin/ruby

require 'rubygems'
require 'ruby2ruby'
require 'invoker'

def f
puts("#!/usr/bin/ruby")
puts
puts("require 'rubygems'")
puts("require 'ruby2ruby'")
puts("require 'invoker'")
puts
print(this.to_ruby)
print("\nf")
end
f


為什麼要用 def f; end 呢??這樣不就失去 this 的意義了??因為不曉得為什麼,Ruby2Ruby 跑 lambda + this 都會有奇怪的 runtime error, 而這個錯誤是來自 class RubyToRuby 的 rewrite_defn(exp), 最後的:「raise "Unknown :defn format: #{name.inspect} #{args.inspect} #{body.inspect}"」這我一時不知道要怎麼解決,不知道會是誰的錯,所以只好暫時用 def f; end 這種蠢方式了。

另外,還有一些無聊的花枝可以玩:

def just_print_it n
print n
this
end

然後就可以:

just_print_it(1)[2][3][4][5]

輸出:12345

def add_attr attr
($attr ||= []).push attr
this
end

add_attr('hello')['world']['and then?']['good-bye']

或是很簡單的流程控制:

def fight step = :ready
case step
when :ready
ready this
when :go
go this
when :finish
finish this
end
end

def ready callback
play_ready_animate
if blah
callback[:go]
else
callback[:finish]
end
cleanup_ready
end

def go callback
gogogo
if blah
callback[:finish]
else
callback[:ready]
end
cleanup_go
end


類推,我也不知道有什麼用,搞不好有什麼 cleanup 是要等所有的事都做完才能做?總而言之,就是這樣啦,沒什麼營養,但我卻覺得很重要的功能。在很多時候可以少打很多字。搞不好哪一天也會想到什麼真的很有價值的應用也說不定。總覺得真的有太多事是無心插柳柳橙汁。雖然大部份都來不及喝就乾掉了。

2007.04.16 godfat 真常

4/04/2007

Struct:簡單的宣告物件方式

在 PHP 裡面,很多時候我們會用到 Hash 來傳遞變數,像是 $something['abc']['def'] 之類的方式,或許這是一個在效率上面很好的實做方式,但是總是...怪怪的,不是那麼的優雅。在 Rails 裡面,難免會有 controller 傳遞變數到 view 裡面的時候,這個時候通常是用 flash 這個內定的 Hash 來傳遞,但是也會遇到相同的問題,就是 Hash 實在是不夠物件導向。

flash[:somthing][:abc][:def]

這樣的寫法到底是好還是不好呢?很多時候我很想說服自己這樣就好了,code work 即可,但是心中把這種事情物件導向化的想法一直沒變。所以我就開始 survey ,該怎麼作會比較 OO 呢?

我將訊息傳遞的 Object 宣告成一個 Model 上面好不好?code 很好看,可是心理總覺得小題大作了點,明明一個 Hash 就可以解決的事情,需要特別使用一個 Model 物件來寫嗎?有沒有辦法可以超快速的把訊息傳遞包成一個簡單的物件,而且裡面不需要 method ,只需要幾個 message 的 attribute 即可?

我看到了 Struct ,他的作法很簡單

宣告一個 class definition

class_def = Struct.new( :name, :address, :zip )


就是宣告一個 class 的定義叫做 class_def,裡面一共有 name ,address,zip 幾個變數。

New Object 

當我們需要 new 一個 object ,我們把他叫做 obj

obj = class_def.new

如此即可。以後就可以這樣使用

obj.name
obj.address
obj.zip

Constructor

一開始,所有的變數都是 nil,這是因為沒有 constructor去宣告初始值。在 Struct 裡面有辦法達成嗎?當然有,在 new 這個 object 時這樣作

obj = class_def.new( 'a' , 1 , '123')

即可。

我的使用方式

我常常這樣使用Struct 來做 message passing 的 object,一行即可做到 new 一個我自己定義的 message object

msg = Struct.new( :error , :alert , :warning).new

如此的寫法對我來說,比 flash[:error] 來的更為優雅。

這個東西與其說是技巧,不如說是一個物件導向的偏執分子的固執行為,沒什麼了不起。你依舊可以用 Hash 達成每一件 Struct 做的到的事情。

3/27/2007

class 與 instance

不知道為何,最近不管是私事或公事似乎都很多。造成的結果就是很多事不知道該從何處著手比較好。也許短時間內沒辦法寫什麼新東西也說不定,所以我又拿舊文章出來改了。這次講的是 Ruby 的物件系統。不過呢,由於這篇原本是回答別人的問題,因此有些地方講得不是很齊全,有些也講得比較偏。找到時間後,會再重新整理一次,現在就先這樣吧

編輯筆記:後面一段無關的刪去了,並補充了一小段。

==

在看 Ruby 的 class 與 instance 之前,先來看所謂 prototype-based language 是怎麼樣的東西,當然是舉大家最耳熟能詳的例子,ECMAScript(即 Javascript)

所謂 prototype-based 的意思是,沒有 class 的概念,所有的一切都是 instance, 產生東西一律使用 clone 的手法,從 prototype clone 出來。

在 ECMAScript 裡,要這樣操作 prototype:

=begin 例子

// 產生一個 function object, 會輸出「我是 ooo」
function say(){ print('I am ' + this) }

// 產生一個 function object, 拿這當 Duck 的 prototype
function Duck(){}

// 讓 Duck prototype 產生一個成員,也就是讓 say 變成他的 method
Duck.prototype.say = say;

// 定義 toString 讓 say 使用
Duck.prototype.toString = function(){ return 'Duck' }

// 假設現在有一個讓某東西說話的 function
function say_hello(who){ who.say() }

// 於是我們可以這樣呼叫 say_hello
say_hello(new Duck)

=end 例子

new Duck 會去尋找 Duck 的 prototype, 然後 clone 一份該 prototype 後傳回。所以 say_hello 的 who 會是一份 Duck 的複製,執行 say 則會輸出:

I am Duck



ok, 回到 Ruby. 雖然說 Ruby 被分類成 class-based, 但事實上,everything(ok, almost) is an object in Ruby, 就算是 class, 他其實本質上也是某個 instance, 是 Class 的 instance.

class A; end
a = A.new

a 是 A 的 instance, 所以 a 的 class 是 A.

a.class # A

A 是 Class 的 instance, 所以 A 的 class 是 Class

A.class # Class

其實,我覺得可以把這個 Class 視為某種 meta-class, 即 class 的 class, 如果我們要把 A 當嚴格 class 的話。但如果我們依然把 class 當 instance 看的話,當然,Class 本身其實也是一個 instance, 他是他自己的 instance.

Class.class # Class

有趣的是,這樣寫的話:

Class.object_id == Class.class.object_id

答案是:true.
Class.class 傳回來的,其實就是 Class, 也就是,他是他自己的 instance. 換句話說,其實 Class 是所有的 class 的 class.

A.kind_of? Class # true
Class.kind_of? Class # true
A.class # Class
Class.class # Class
A.class.object_id == Class.object_id # true



(btw, 其實更妙的是:

Module.kind_of? Class # true # Module 是 Class 的 instance
Class.kind_of? Module # true # Module 是 Class 的 superclass
Module.new.kind_of? Class # false # Module 的 instance 不是 Class



再加上 Object 會更複雜,可以試著畫畫看物件結構)

但是回想一下,一般我們是怎麼定義 class 的?

class A; end

其實,我個人會說這是一種 syntax sugar, 因為更合於 Ruby object system 的定義方式,應該是這樣:

A = Class.new
A.send(:define_method,
:say_hello,
lambda{ puts "Hello from A's instance." })

a = A.new
a.say_hello # Hello from A's instance.



由於 define_method 是 private 的,所以要用 send 去呼叫。其第一參數是你所要回應的 message symbol, 第二參數是 Proc/Method/Block 都可,將會成為該 method 的 body.

也就是說,其實你寫

class A
def say_hello
puts "Hello from A's instance."
end
end



對於 A 來說,他是先從 Class 產生一個實體(instance),然後將 A 這個「常數」指向那個實體,再對 A 呼叫 define_method, 把 say_hello 變成 symbol, 將 Block 變成該 method 的 body.

哪一個比較容易寫?當然是後者,畢竟那是大家都很習慣的模式,簡潔易懂。所以我會說那種寫法其實在某種程度上來說,是 syntax sugar... 而 Ruby 其實也是用 prototype 建出其 class 體系,這樣應該算是 prototype-based 還是 class-based, 看倌認為哩?

2007.02.08

補充:

有在寫 Ruby 的人,應該都知道 class 的名稱一定要大寫開頭。事實上,這只是一種延伸規則,並不是基本的規則。依照:

a = Class.new

仍然是可以得到一個不是大寫開頭的 class. 大寫開頭的 identifier 在 Ruby 是一個更簡單的概念:「常數」(constant)。也就是說,其實每一個 class name 都是一個常數,一個 constant variable refer to a Class's instance, 一個指向 Class 實體的常數,就跟一般的變數沒兩樣。所以我們可以寫:

def make klass
klass.new
end

這個 functiona 接受一個參數,這個參數的 type 的必要條件是實作 new method, 而任何一個 Class instance 都符合這個條件。

make(Array).push(123)

把 Array 這個 instance 丟給 make 這個 function, 回傳就是一個 Array instance.(global function 也值得探討,不過還是留待下次吧 :p)

那麼為什麼要讓 Class instance 成為一個常數?其實這是很顯而易見的,如果你把原本定義好的 class 整個變成其他人了,例如:

Array = String

這樣意義在哪裡?以後寫 Array.new 產生的就不是原本的 array, 而是 string 了。所以讓 class 成為一個 constant, 並要求其開頭字母是大寫,並非單純只是為了命名習慣的問題,背後是還有牽扯到其他因素的。

我覺得 Ruby 有趣且厲害並迷人的地方就在於,他不單單只是有強大的威力在內,還額外提供了許許多多方便使用的 syntax sugar, 使任何人都能快速上手,也能快速做到任何他想做到的事情。而當你需要更強大的表達能力時,也能立刻拋棄 syntax sugar, 從最原始的 Ruby 道理開始寫起程式。也就是說,Ruby 提供了各種不同的寫程式角度,大家都可以去找他喜愛的角度去使用。個人認為,Lisp 會不及 Ruby 方便,最主要的差別就在這裡了。(當然,我並不熟 Lisp, 所以這句話恐怕是大有問題在…。)

2007.03.27 godfat 真常

延伸閱讀

3/21/2007

Gentoo 上面跑 DRB 可能出現的問題

有時候在 Gentoo 上面使用一些有關 DRB 的東西,像是 Breakpoint Server ,或是 gem_server。通常會遇到兩種很奇怪的 Error


  1. Exception `SocketError’ at /usr/lib/ruby/1.8/drb/drb.rb:840 - getnameinfo: ai_family not supported
  2. /usr/lib/ruby/1.8/drb/drb.rb:840:in `getaddrinfo’: getnameinfo: ai_family not supported (SocketError)
這問題的解法我一直到看到這個網頁才瞭解,是因為在 Gentoo 上面編 Ruby 時需要啟動 ipv6。不要問我為啥,事情就是這樣 。So, 解決方式很簡單,/etc/portage/package.use 裡面加一行 dev-lang/ruby ipv6。然後重新安裝 Ruby 即可。

3/18/2007

SWIG/Ruby

以下文章最早發表於 2006.08.06, ptt Ruby 板。由於這裡的版面跟 BBS 有極大的差異,所以稍微調整了一下排版。不過程式碼的部份可能很難調整到方便閱讀,這點也就請見諒了。

http://www.swig.org/

SWIG is an interface compiler that connects programs written in C and C++ with scripting languages such as Perl, Python, Ruby, and Tcl.

也就是,讓 C/C++ 與 scripting language 溝通的介面產生器。目前支援 13 種程式語言(含非 scripting language):

* Allegro Common Lisp
* C#
* Chicken
* Guile
* Java
* Modula-3
* Mzscheme
* OCAML
* Perl
* PHP
* Python
* Ruby
* Tcl

之前我測了一些 Ruby 與 C++ 的溝通方式,以下將簡單介紹一下測試的方法。首先先看到使用方式,我想這個應該是最重要的部份,畢竟如果使用不便,那其他的也不用再多說什麼了…。




以下這個程式是一個簡單的 type wrapper,(傳值版)由 template parameter 指定型別,data() 當 getter, data(type) 當 setter. 另外在 c'tor 和 d'tor 中插入 IO 來追蹤物件的生成與摧毀:
// in Wrapper.h

#include <iostream>

template <class Data>
class Wrapper{
public:
Wrapper(Data new_data): data_(new_data){
std::cout << "C++: Wrapper is created, which is '"
<< data_ << "' .\n";
}
~Wrapper(){
std::cout << "C++: Wrapper is decayed, which is '"
<< data_ << "' .\n";
}
Data data() const{ return data_; }
void data(Data new_data){ data_ = new_data; }
private:
Data data_;
};

有了這個 .h 檔的介面後(這裡同時也包含實作),接下來我們需要的是一個由 swig 產生的膠水,將 C++ 介面與 Ruby 介面混合,暫時命名為:Wrapper.i, 詳細做法等一下我們再來看。總之最終我們將產生一個動態連結檔,也就是一個 Ruby 的 module, 叫 Wrapper. 不過需要一提的是,由於 Ruby 本身不支援 template, 所以我們在 Wrapper.i 中必須明確指出我們需要什麼型別,否則 Ruby 會無法使用。(更明確來講,純 template 根本無法 compile)

所以在 Wrapper.i 中,裡面有一行是這個:

%template(Integer) Wrapper<int>;

這句話的意思是,將 Wrapper<int> 這個型別取作 Integer. 於是 Ruby module 做好後,可以使用 Integer 這個型別。




雖然我中間還有很多測試,不過直接看目前的最後結果吧。寫一個 Ruby 程式,內容如下:
#!/usr/bin/ruby
# in test.rb

require 'Wrapper' # 讀入剛剛做好的 Wrapper module

class Test < Wrapper::Integer # 繼承 C++ 寫好的 Wrapper<int>

def initialize(new_data) # c'tor 其實可以省略,因為可以使用
super(new_data) # C++ 中寫好的 Wrapper
end # 這裡只是展示 super 是可以用的

def plus(num)
data(data + num)
data
end

end

# 正式測試開始

fat = Test.new(123) # 建構出 Test 物件,傳入 123 當引數
puts fat.plus(7) # output: 130

fat.data(456) # 設值為 456
puts fat.plus(4) # output: 460

ok, 可是原本插入 C++ 的 c'tor 中的 IO 呢?整個程式的輸出結果是這樣:

C++: Wrapper is created, which is '123' .
130
460
C++: Wrapper is decayed, which is '460' .

也就是說,C++ 的 c'tor 與 d'tor 有被確實喚起。這樣說不定可以替 Ruby 實作出 d'tor...不過這是題外話。

另外,剛剛程式中的 fat.data 與 fat.data(456) 確實是喚起正確的 getter 與 setter, 可是 Ruby 沒有 overload? 我沒有實際去看,但我猜他內部大概是這樣實作的:
def data(*args)
case args.size
when 0 then return self.data_a
when 1 then return self.data_b(args[0])
end
end

所以如果你想要重新定義其中一個 overloaded method, 做不到…可能必須找到他實際的名字才有辦法。




那麼我們再來看到由 C++ 呼喚 Ruby... 否則不就太寂寞了嗎?基本上 Ruby 本身就提供了良好與 C 溝通的機制,照用不就好了?是這樣說沒錯,可是說真的我很討厭 C 的介面…囧rz
常常會要你記下一堆有的沒的,感覺很討厭。所以我覺得需要一個 C++ 的 wrapper, 一個可以輕鬆呼叫 Ruby 的方式。後來我找到這個:

http://www.sourcepole.com/sources/software/c++ruby/

不過這個東西實在是寫得不好,沒有處理 finalize 的部份。不知道還有沒有其他類似的 wrapper 可以用,所以我就暫時用這個來改。改寫他 singleton 的實作,還有記得在 d'tor 中呼叫 ruby_finalize();

最後的結果是,我可以這樣寫:
#include "rubyeval.h" // 就是上面抓來的那個
#include <ruby.h> // embedded ruby 必要的東西,去 ruby-lang 就可以抓

int main(){

RubyEval& ruby = RubyEval::instance();
ruby.eval("require 'Wrapper'"); // 直接用字串執行 Ruby
// 這邊是含入剛剛做好的 C++ => Ruby mod
// 當然這東西要先 compile 好

ruby.eval("fat = Wrapper::Integer.new(123)"); // 直接建立物件
std::cout << NUM2INT(ruby.eval("fat.data")) << std::endl; // 輸出 123

ruby.eval("fat.data(456);"); // 設值 456
std::cout << NUM2INT(ruby.eval("fat.data")) << std::endl; // 輸出 456

ruby.run_file("test.rb"); // 直接執行 ruby 程式
}

那個 test.rb, 就是上面寫好拿來測試 Ruby 呼叫 C++ 的程式。於是整個輸出結果就是:

C++: Wrapper is created, which is '123' .
123
456
C++: Wrapper is created, which is '123' .
130
460
C++: Wrapper is decayed, which is '460' .
C++: Wrapper is decayed, which is '456' .

第一行是從 C++ 建構的 Integer; 123, 456 則是 cout 輸出的。第四行的 123 則是 test.rb 產生 class Test < Wrapper::Integer 那個。130, 460 則是 Ruby 的 puts 產生出來的。

最後兩個 decayed 則是 Ruby 的 gc 正確在程式結束時摧毀物件輸出的順序剛好跟 c'tor 反過來,一切正常。




最後就來看怎麼實現這個的。Wrapper.i 是這樣寫的:

%module Wrapper

%{
#include "Wrapper.h"
%}

%include "Wrapper.h"
%template(Integer) Wrapper<int>;

這些語法請參考 SWIG 的網站,那邊都有詳細說明。甚至是 C++ 的多重繼承,在 Ruby 中也能使用。當然多少可能會有點限制,但似乎可以實現一定的功能。

下指令:
swig -c++ -ruby Wrapper.i
這樣就可以以 Wrapper.i 這個介面檔實作出由 Ruby 溝通 C++ 的程式。那個程式會叫做 Wrapper_wrap.cxx, 也就是 YOUR_NAME_wrap.cxx

再來就是將所有的程式打包成一個動態連結檔了,所有的檔案是:Wrapper.h (包含實作), Wrapper_wrap.cxx (SWIG 的膠水)。方便的做法是使用 Ruby 的 lib, 叫 mkmf
寫一個 Ruby 程式叫 mkmf.rb, 內容是:

require 'mkmf'
$libs = append_library($libs, "stdc++")
create_makefile(ARGV[0])

這邊是由於我個人方便,所以這樣寫的。第一行含入 mkmf, 第二行是因為我用到 C++ 標準函數庫(cout),所以必須連結 stdc++ 才行(我的系統是 GCC, VC++ 的話我不清楚)接著由 create_makefile 產生出我要的 makefile, 名稱由 cmd line 輸入。這邊我是輸入 Wrapper.(btw, 有人知道 stdc++ 可否動態連結嗎?)

接著就可以由 make 將 Wrapper.h 和 Wrapper_wrap.cxx 合併做出 Wrapper.so(我想 VC++ 系統應該會做出 Wrapper.dll 之類的)




最後要提的是,整個程式的執行環境。由於我是在 cygwin 下作業的,所以獨立執行這些程式需要的是:

cygwin1.dll 1.78 MB
cygruby18.dll 703 KB
cygcrypt-0.dll 6.5 KB

我想如果在 windows 下由 VC++ compile 的話,應該就只會需要 Ruby interpreter 的 dll 檔(這裡是 cygruby18.dll)。不過我就沒有做這一步的測試了,留給讀者當作練習吧 XD

(忽然心血來潮所以整理了這些東西)

2006.08.06 godfat 真常

延伸閱讀

3/16/2007

why scripting?

why scripting?

簡單一句話就是:「為了增進生產力」。不過這樣回答的話,就會衍生出另外一個問題:
「為什麼要增進生產力?」當然不是這個問題,這種問題根本沒有回答的必要。
這個問題是:「如何增進生產力?」

我們知道有一些狀況下,必須不斷修改程式,不管是因為現階段不知道最好的解決方式
是什麼,或是現況真的是會不斷改變。總而言之,我們必須「嘗試」而後「修改」。
這種時候,我們可以用需要 compile 而後 link 的程式語言(或是工作環境)嗎?

這篇影片道出了一些 "why": Better Web App Development
可以不用看沒關係,這邊會稍微提一下大概內容。簡單地說,GUI 的部份是最明顯的。
因為我們不知道我們的 user 會比較想要哪一種操作模式,甚至,連他們自己也不清楚。
這種事說來很不可思議,但實際上就是如此。很多事情是很難預測的。或是該說,
要正確地預測事情的成本,遠遠高於直接試試看,再從中獲取經驗,進而修改以符合
我們的希望。所以我們需要的是「能夠快速修改、變化」的程式語言(或是工作環境)。

scripting 就是為此而誕生的。他們的立意和 system programming 有著根本上的不同,
所以無須拿此二者來比較。這一篇並不是想探討此二者間的異同,所以不會再對此著墨。
這裡直接假設兩者同等重要,以此討論兩者之間該如何取得平衡。

*

事實上,越是前端的程式,越是需要此種「變化」的特性。因為 system programming
是面對機器,而機器可不會跟你鬧脾氣。(如果不明原因的當機不算的話…)
但是 scripting 通常是面對人,而人可是可以陰晴不定,今天好好的明天就跟你翻臉,
或是嘴巴說不要,身體倒是挺誠實的,諸如此類的問題可多著。
為了「適應」人的這種「特性」,我們也需要一個擅長「變化」的東西,
以此銜接 system, application, 與人類。

除此之外,大家也都知道 scripting 的執行效率一定遠遠不如 binary program,
在大部份的情況下,這不是個問題。因為現在的電腦已經快到一種很恐怖的境界了,
真的非常需要執行效能的狀況,其實很少。更何況大家都知道 80-20(1) 法則,效能的
重要性只會隨著時間越來越減少,但永遠不會消失,因為人的慾望是無窮的…。

*

舉遊戲程式為例。一般比較大型的遊戲程式,大抵上都會切成三個部份。第一個是
廣為人知的 engine, 這邊的執行效能是非常 critical 的。第二部份是遊戲主程式,
這部份一般而言,效能也是非常重要的,所以還是會以 C++ 來完成。但是有些東西,
卻不得不引入 scripting, 儘管執行效能可能會是個問題,但還是不得不引入。
這就是第三個部份,掌控一些最細微的遊戲設定,也是跟「人」最有關係的部份。

為何這部份需要引入 scripting? 最簡單的例子就是平衡度設定。Dragon 是否太強?
Drawf 是否太弱?Golem 需不需要再多加個 Trample 的能力?Ranger 的 favored enemy
是否該換成 Goblin? 諸如此類,不是一開始就能確定,你一定得「玩一玩」才有可能
知道的麻煩事。甚至,必須讓成千成萬個玩家去玩過,才會知道的,很隱晦的事。
在複雜系統之下,這種碰過才知道的事,是非常多的。但是我們可不能稍微修改一下,
重新 compile, link, 泡杯咖啡。接著測試兩分鐘,啊,不行,這個數字應該再減少一個
單位。重複上面發生的事。這樣別說效率很差了,耐心都被磨光了。有些時候,士氣其實
是遠遠重要於產出的程式,像這種極度消磨耐心的事,再怎麼樣都是不能出現的。

引入 scripting 的好處就在這裡,我們可以輕易地修改程式,接著直接重新啟動遊戲
即可。更有甚者,直接叫你的遊戲重讀 script 檔即可,連重新啟動遊戲都不必。
不過就像我上面說的,基本上遊戲是個幾乎每一處都很 critical 的程式,所以
scripting 也要越快越好。最常被選用的就是 Lua, 因為他真的很快。不過啊,個人是
不太喜歡使用 Lua, 因為他和 C++ 間的連接不是那麼地良好,怎麼說,有點詭異…。

第二個最常被使用的,可能是 Python. 這是個相當不錯的程式語言,boost 也有一整份
Python 的 binding library. 不過我個人還是比較偏好 Ruby 一點,Ruby 是目前為止
我看過我覺得最好的 scripting language, 原因很多,但主要可能是因為他對物件導向
的支援非常完善,而物件導向在遊戲裡,是個幾乎可以說不能沒有的東西。光 GUI 就
非常仰賴物件導向了,更何況 GUI 只是遊戲的一部分。這部份可以很複雜,也可以很
單純。但不管怎麼樣,物件導向都太重要了。

*

所以事實上,我是很希望遊戲程式可以引入 Ruby, 雖然 Ruby 的執行效能比較差,
但應該好用的東西就應該好用到底,應該跑得快的東西就應該快到底,我是這樣認為的。
Python 被用很多,但我相信 Ruby 也會越來越重要。最近也確實如雨後春筍一般,
四處都出現其應用,不是嗎? :p

boost 沒有提供 Ruby binding 是有點可惜,我想這是因為 Ruby 的熱門是最近幾年的
事。不過呢,SWIG 還是有提供 Ruby 的 binding! 而且也是相當完整的支援。
何不就來試試 C++ 透過 SWIG 與 Ruby 溝通的方式呢? :p
欲知詳情,請待下回分曉。

2007.03.16 godfat 真常

p.s. 為何遊戲一定要用 C++? 這…不在本篇討論範圍裡 :o

(1) 一個程式只有 20% 的部份會佔去 80% 的執行效率。或是各種類似說法。

為啥我得升級 Ruby 1.8.6

為什麼我得升級 Ruby 1.8.6?會跟 Rails 衝突,還得一定要升到 Rails 1.2.3 才能夠跑 Rails。那麼麻煩的 Ruby 版本,為啥我一定要升級?我給你一個原因
Ruby 1.8.6 已經將 FastThread包進去了
也就是說灌 Mongrel 應該不用加裝 FastThread 了。我們可以合理推測 Ruby Native 支援總比 Gem 支援來的好不是嗎 :p

還有一點,隨著 Ruby 1.8.6 的推出,看來下一版的 Mongrel 除了 fastThread,也不用裝 cgi_multipart_eof_fix 了。 Ruby 的整合度越高,狗皮膏藥越來越少總是一件好事。

3/13/2007

Ruby 1.8.6 Release:要升級請三思

Ruby 1.8.6 Release了,想看詳細情形,或是下載 Source 請看這裡。根據 fixneo 先生的提醒,我花了時間去找了一下 Bug Report ,發現到這篇文章。裡面寫到 Time.to_date 跟 MD5.new 會出現問題。不過這些 Bug 會在 Rails 1.2.3 做修正,也就是應該是 Rails 的問題?

隔天早上,Rails Team 也立刻發表了 Rails 1.2.3,所以要升級 Ruby 1.8.6也請順便升級 Rails 1.2.3。

2/25/2007

M$ 也加入 Ruby 這個大戰場了嗎?

InfoWorld 上面有消息指出,M$ 對 Ruby 這個程式語言非常有興趣,並且對於支援 Ruby 有很多想法。Microsoft is "very interested" in the Ruby programming language

"We are very interested in Ruby and have lots of thinking going on," but nothing to announce at this time, Key said. He advised chatters to "stay tuned."
不過在這個階段是屬於一些想法的階段,其他的還無可奉告。放話歸放話, M$ 到底要以什麼樣的形式加入 Ruby on Rails 戰場呢?我的看法是
  1. 強力主導開發 RubyCLR
  2. 官方的 Visual Stdio .NET Ruby支援
  3. 幫忙加強Ruby on Rails 的 SQL Server 支援
如此 Ruby 在 Windows 上面的情況就不是那麼肉腳了。

以下是本報導跟 Ruby 有關的原文

During an Internet chat with InfoWorld, Forest Key, Microsoft director of Web and client user experience marketing for the company's developer division, acknowledged Ruby is on the company's radar screen. Asked if the company would accommodate the Ruby on Rails Web framework, which is based on Ruby, in Expression, Key said, "Ruby is currently more of a 'developer' concept for us."

"We are very interested in Ruby and have lots of thinking going on," but nothing to announce at this time, Key said. He advised chatters to "stay tuned."

Key added he was not the Microsoft person to comment in detail on this subject because he did not know the company’s plans. One chatter expressed wishes for an IDE for Ruby on Rails from Microsoft.