以 Thinker 舉的例子為例, 「每行有兩個數字, 將它們相乘後, 加總各行的結果」, 試寫 Ruby 的程式如下:
>> $data = ["2 3", "4 5"] => ["2 3", "4 5"] >> $data.map{ |line| line.split } => [["2", "3"], ["4", "5"]] >> $data.map{ |line| line.split }.map{ |xs| xs.map{ |x| x.to_i } } => [[2, 3], [4, 5]] >> $data.map{ |line| line.split }.map{ |xs| xs.map{ |x| x.to_i } }.map{ |x,y| x*y } => [6, 20] >> $data.map{ |line| line.split }.map{ |xs| xs.map{ |x| x.to_i } }.map{ |x,y| x*y }.reduce { |sum,n| sum + n } => 26
懶得裝 Ruby/irb 的人可以用 tryruby.org 試試, 上面的程式碼是我在上面寫好後, 排版後貼過來的。習慣這種資料轉換的思維後 (functional programming?), 配合 Ruby 的 code block, 可以一直線地寫到底, 相當痛快。不過是否好讀, 和習慣和個人喜好有觀, 不在此篇的討論範圍。我自己是覺得寫起來很爽, 但不好讀, 少了用變數抽象化前面操作的結果, 得讀完全部細節才知道在做什麼。
2011-04-15 更新
避免沒寫過 Ruby 的人誤會 code block 像是塞成一行程式碼寫爽的功能, 將最後的結果排版了一下:
>> $data = ["2 3", "4 5"] >> $data.map { |line| .. line.split .. }.map { |xs| .. xs.map { |x| .. x.to_i .. } .. }.map { |x, y| .. x * y .. }.reduce { |sum, n| .. sum + n .. } => 26
實際寫的時候, 常會要在一個 code block 寫個兩三行, 這時就排版拆行寫了。
作者已經移除這則留言。
回覆刪除其實Python 也可以寫成一行啦
回覆刪除thinker_func=lambda data: sum([ int(i[0])*int(i[1]) for i in [ line.split() for line in data.strip().split('\n')]])
當我們用 for-loop 寫程式時, 基本上我們要考慮到 pre-condition, loop-content, post-condition 的正確性與一致性. 但是因為 list (list comprehension) 的強大, 所以在設計時可以很容易的 "遷就" 上一個流程方塊裡的內容, 所以很適合拿來做方塊之間傳遞資料的容器. 或者說是一種 decoupling, 讓每一行基本上是獨立的 (I/O 都是 list). 我是覺得, 這才是用"回推法"寫程式的有效益的主因. 但是如果是這樣由上往下寫應該也是差不多的.
這裡想強調的不是「可以寫成一行」, 而是思考順序和 coding 順序一致。我自己用 Python 寫的時候, 若要由前往後寫, 變成先寫最裡面的程式 (data.strip().split('\n')) 再回頭寫上一層函式呼叫。
回覆刪除於是游標得來來回回, 而不是一邊想, 一邊順著思緒往下寫, 不用移動游標。
code block 的好處是, 它可以放 statement, 介於 imperative 的寫法和 FP 的形式之間, 外層 (code block) 是借用 FP 的概念, 看起來像一連串資料型別轉換; 內層卻可以有像 imperative 一樣有 state, 符合未受嚴謹 FP 訓練的程式設計師的習慣。
我知道你不是想要強調「可以寫成一行」.
回覆刪除我想說的是, 就像是 thinker 的 code 一樣, 順著寫一樣可以不用回頭去修改. 只要在寫的時候,盡量把東西放到 list 裡去就好. 我從上往下寫一樣會寫出 thinker 那樣的 code. 而且也不需要回頭去改.
bag=[ line for line in data.strip().split('\n')]
bag=[ line.split() for line in bag]
sum([ int(i[0])*int(i[1]) for i in bag])
所以我不覺得是因為回推法的幫助, 而是因為 list comprehension 這個工具(syntax sugar?), 讓程式更好寫了. 實際上, 這個工具只是讓一個迴圈可以做完的東西, 變成三個迴圈才做完. 但是好處是 input 和 output 是一致的資料結構 (list) 所以在銜接上更簡潔. 這點或許 Ruby 的 code block 做得更好, 這我就不清楚了. 但我猜測往下寫的快感上, 差距可能不是太大.
我比較贊同你把 thinker 的這個做法和 FP 類比. 我也基於同樣的原因而不認為 thinker 的做法特別. Ruby 和 Python 都大量跟 FP 學習. Python 也有 map/reduce/filter 等基礎函式. 我覺得這種類似的做法, 比較接近 pythonic, 是 Python 語言發展作者群所希望引導學習使用 python 語言的人的寫作方式.
我以前一直覺得這種 line interpreter 超難用, 很難往回改! 但是當慢慢學習用這種方式寫作時, 才覺得比較適應, 或許這也是她們當初設計時想要克服的困難之一?
1.
回覆刪除"line interpreter" 是指 interactive interpreter? 像是 irb、ipython 這類工具?
在 ipython 裡可以打 edit 進入自己慣用的 editor, 寫個 code 再存檔離開, 可降低你說的困擾。通常我得寫較多行時, 會進 editor 寫個 function 再出來。
2.
看到你的描述, 讓我又想得更清楚一些了。
以 FP 的思維來說, 關鍵在於 data type 的轉換。所以, 更精確的說, 差異在 [str] -> [(str, str)] -> [(int, int)] -> [int] -> int, 思考時有個明確的 data type 轉換的流程。如同你說的, list comprehension/code block 有助於用這種思維寫程式。
所以在和 Thinker 討論後, 我才在這裡想通關鍵在於 reduction (data type 轉換) 的思考方式
我覺得大家依自己的經驗各自有些想法, 互相討論後似乎愈聊愈清楚了。之後再重制其他人嘗試的經驗, 應該會有更完整的想法。
3.
這是比較細的事, code block 的好處是, 可以一路寫到底不用動游標。至少我自己在用 Python 的 list comprehension 時, 有時得先寫 for 後面的東西, 再移回來寫 for 之前的東西, 而覺得有一點卡
避免落入 Ruby vs. Python 的討論, 先聲明我個人是偏好用 Python 開發程式 :D