那些 Functional Programming 教我的事 投影片

這是今天在 OSDC.TW 2012 演講的投影片:

這個題目是近年來第一次脫離 Ruby 舒適圈的演講(抖),準備的時候就發現 Functional Programming 不但講不完(30分鐘的演講教不會寫FP啦),有些概念又很難,真要講到 “Pure” 的 Functional Programming 我連 Monad 都還搞不清楚…orz 所以本來準備的內容在前一天又狠狠砍掉 1/3,開頭改成用 Concurrency Program 來引人入勝,希望這樣的目的有達成 :)

HTTP Verbs: 談 POST, PUT 和 PATCH 的應用

關於 HTTP 基本知識,可以參考我的 網路概論3: HTTP

在初學REST的這幾年,我都認為這幾個 HTTP Verbs 就是對應 CRUD:

  • POST = 新增
  • GET = 讀取
  • PUT = 更新
  • DELETE = 刪除

後來在設計 API only 的 Web service 時,常常搞不清楚到底要用 PUT 還是 POST,才發現我被 Rails 的鷹架範例誤導了(被框架框住想法了?),所謂的 PUT 其實也可以用到新增,而且還有一個蠻新的 HTTP Verb 叫做 PATCH,像 Github APIRails 4 都開始採用。

PUT 比較正確的定義是 Replace (Create or Update),例如PUT /items/1的意思是替換/items/1,如果已經存在就替換,沒有就新增。PUT 必須包含items/1的所有屬性資料。

但是這個行為似乎不怎麼好用,如果只是為了更新items/1的其中一個屬性,就需要重傳所有items/1的屬性也太浪費頻寬了,所以後來又有新的 PATCH Method 標準,可以用來做部分更新(Partial Update)。

用幾個 Ruby code 來舉例吧:

POST 新增:

# POST /items
def create
  @item = Item.new
  @item.attributes = { :name => params[:name],
                      :image => params[:image] }
  @item.save
end

PUT 替換(新增或完整更新),此例中如果image參數沒有傳,會被更新成空:

# PUT /items/{id}
def replace
   @item = Item.find_by_id(params[:id])

  unless @item  # if @item.nil?
      @item = Item.new
      @item.id = params[:id]
  end

  @item.attributes = { :name => params[:name],
                       :image => params[:image] }
  @item.save
end

PATCH 部分更新,此例中如果image參數沒有傳,就不會被更新:

# PATCH /items/{id}
def patch
   @item = Item.find(params[:id])
  @item.attributes = params.slice(:name, :image)
  @item.save
end

DELETE 刪除,此例中無論如何 items/1 最後都不存在了

# DELETE /items/{id}
def destroy
   @item = Item.find_by_id(params[:id])
  @item.destroy if @item
end

有時候拘泥於”語意”這件事情不容易想清楚設計 REST API 時要用哪一個 HTTP 方法,因為有時候不一定是CRUD的形式。我認為 9.1 Safe and Idempotent Methods 定義中的 “Idempotent” 特性蠻實用的。idempotent 的意思是如果相同的操作再執行第二遍第三遍,結果還是跟第一遍的結果一樣 (也就是說不管執行幾次,結果都跟只有執行一次一樣)。根據 HTTP 的規格,GET, HEAD, PUT 和 DELETE 是 idempotent,相同的 Request 再執行一次,結果還是一樣。只有 POST 和 PATCH 不是 idempotent,POST 再執行一遍,會再新增一筆資料,PATCH 則是有不能保證 idempotent 的可能性(徵求例子)。POST 和 PATCH 都不是 idempotent 的操作,這也是為什麼 Github API 裡用 POST 當做 PATCH 的相容取代方案。

另一個 HTTP Methods 特性是”Safe”,這比較簡單,只有 GET 和 HEAD 是 Safe 操作。Safe 特性會影響是否可以快取(POST/PUT/PATCH/DELETE 一定都不可以快取)。而 Idempotent 特性則是會影響可否 Retry (重試,反正結果一樣)。

Safe? Idempotent?
GET Y Y
POST N N
PATCH N N
PUT N Y
DELETE N Y

透過 Idempotent 的特性,有時候可以幫助你判斷該用哪一個 HTTP Methods。回到前面講 PUT 好像不太好用,例如以瀏覽器為主的 HTML 應用表單,要麻是 POST 新增資料,要麻就是用 PATCH 部分更新已經存在的資料(你不會希望用 PUT 修改個人資料的時候,每次都要重傳照片吧),因此比較少用到 PUT。這也是為什麼 Rails 4 把表單修改的 PUT 改成 PATCH Method,透過 Rails 鷹架產生出來的 update,其實符合的是 PATCH 行為而不是 PUT。

不過還是有一些我認為蠻適合用PUT的情境,例如訂閱東西該用POST還是PUT呢?

POST /subscriptions
# 還是
PUT /subscriptions/{something_id}

訂閱東西只有兩個狀態,”已訂閱”或”沒有訂閱”,這個訂閱的操作再重送幾次,還是”已訂閱”,所以我認為蠻符合 PUT 的 idempotent 特性。而對應的取消訂閱 API 想當然就是

DELETE /subscriptions/{something_id}

另外一個我覺得有趣又實用的 PUT 例子是,設計 API 给可以離線 offline 使用的行動裝置(例如iPhone)。支援 offline 產生的資料,通常會使用 UUID 來產生 ID,這樣就不需要透過中央伺服器管控 ID,方便裝置之間的同步。這樣的情境下,新增資料的 REST API 其實可以提供兩種:

POST /items # 參數帶 uuid=4937973d-e349-460a-a6ad-38625125b24a。如果不帶uuid則由server來產生uuid
# 和
PUT /items/4937973d-e349-460a-a6ad-38625125b24a

對行動裝置的 client 來說,用POST的問題在於離線環境的不穩定,有可能POST之後沒有收到回傳,因此行動裝置不確定有沒有同步成功,這時候要再重試(retry),但是用 POST 就爆炸了,因為 server 會再新建一筆 uuid 重複的資料。但是用 PUT 就沒有問題了,PUT 是 Idempotent 的操作,可以重送沒有關係 (可以再搭配 Conditional PUT 的機制,用 ETag 和 Last-Modified Headers 確保沒有覆蓋衝突)

如果是沒有 offline 需求的 client,例如第三方應用,那麼就可以用 POST /items 這個 API,交由 server 來產生 uuid。

其他參考資料

JRuby 安裝與 OpenJDK

最近把 JRuby 納入開發的武器之一,幾個你可能會想用 JRuby 的考量:

  • 真 MultiThreading
  • JVM 是一個調教超過十年的 VM
  • 可用 jar 佈署到不同平台(Windows, linux, mac…etc)。Ruby 程式如果要佈署到 Windows,JRuby 我認為是最好的選擇。
  • 可用 war 佈署到 Java Application Server
  • Android
  • GAE
  • 跨平台的 GUI 工具:Swing, SWT
  • 各種精良的 JVM 函式庫: iText, Akka, Apache Batik (SVG), Lucene(search engine) 等等
  • 什麼(奇怪)資料庫都可以連接的 JDBC

安裝

JRuby 的安裝應該是所有 Ruby 實作中最沒有跨平台問題的吧(笑)。只要 JVM 裝好,去 JRuby Download 下載,把 jruby/bin 加到 PATH 就可以用了。如果用 RVM 只要 rvm install jruby 即可。

以 Ubuntu 來說:

sudo apt-get install openjdk-6-jre

wget http://jruby.org.s3.amazonaws.com/downloads/1.6.6/jruby-bin-1.6.6.tar.gz
tar zxvf jruby-bin-1.6.6.tar.gz
sudo mv jruby-1.6.6 /opt/jruby

sudo vi /etc/environment 加上 /opt/jruby/bin

OpenJDK

Mac上其實已經有裝 Apple Inc. 發行的 Java SE 6,不過 Apple 已經宣布不再維護其 Mac 版本了,並把他們的程式貢獻到 Java 的開源版本 OpenJDK 上。也就是說 Mac 上如果想要裝新版的JDK 7 或 8,就是得用 OpenJDK 啦。請參考 OpenJDK 7 and 8 for OS/X Snow and Lion,下載 .dmg 安裝,然後你可以透過設定環境變數 JAVA_HOME 來指定 Mac 使用這個版本,或是透過 Utilities > Java Preferences 做全域的預設設定。

根據 JRuby 官網的這一篇Getting Started with JRuby and Java 7,Java 7 開始支援動態語言的特性,所以跑起 JRuby 效能更好唷。

演講:那些 Functional Programming 教我的事

一年一度的台灣 Open Source 界盛事 OSDC.TW 又到了。在連續講了好幾年 Ruby 和 Rails 之後,今年我的研究主題決定什麼都學學看來點不一樣,其中 Functional Programming 是我很看好的一個趨勢,所以來挑戰看看。

以下是我今年的演講摘要:

Topic: 那些 Functional Programming 教我的事

Abstract:

隨著 CPU 多核和雲端時代的來臨,並行(concurrency)程式和處理巨量資料的需求日益增加,Functional Programming 開始逐漸嶄露頭角。在這場演講之中,我將介紹什麼是 Functional Programming 典範(paradigm)以及為什麼它開始變的重要,以 Scala, Erlang, Haskell, Java, Ruby, JavaScript 等程式語言為例,希望能夠讓您重新思考OO的本質,屆此拋磚引玉。

Speaker:

張文鈿,網路上的代號為ihower,軟體設計師和Ruby on Rails源碼貢獻者,目前是熱情豆行動樂活科技的技術長。曾擔任日本RubyKaigi 2011、中國RubyConf China 2010、台灣RubyConf Taiwan 2010和OSDC.TW歷屆講者。他也是Ruby Taiwan社群創辦人和RubyConf Taiwan大會的主辦人。

RubyConf Taiwan 2011 籌辦心得

活動結束兩個多月了,應該要來記錄一下想法跟心得,有點雜只好條列了:

  • 辦一天不過癮,講者邀一邀一天已經不夠講啦,再加上我比較喜歡單軌議程,單軌議程聽眾也不用煩惱要聽什麼,時間好控制,更不需要跑來跑去找不到位置坐。所以今年挑戰辦兩天!
  • 雖然如此,好像也不能讓講者講超過太久,會擠壓到休息時間,有反應說太累了 XD
  • 這次的餐點是找喜憨兒餐廳,似乎是還不錯,吃的差不多,不會剩太多。根據以往(我的)經驗,乾脆把早上的餐點當早餐,好像不錯(?)
  • 去年的回饋之一就是希望能夠將議程分成入門和進階,所以今年本來是打算第一天是 Tutorial 議程,第二天才是主議程,然後分開售票。不過後來在安排議程上有點難安排,第一天已經不見得完全是 Tutorial 性質了,加上覺得票一起賣也比較簡單,所以索性也就不做分別了。
  • 邀請 Matz 又失敗了。根據去年經驗,週日 Matz 有家庭日不克出席,所以今年安排週五週六,可惜今年這個日期他又剛好有事了。下次先問他哪一天有空好了。
  • 週五週六的安排,似乎讓大家感到驚訝,週五不是要上班嗎? 本來還以為週五報到率會偏低,沒想到竟然還比週六高一些,真是大感意外。而且留著週日放假讓在家睡覺補眠好像挺不錯的。我是覺得如果你工作有用到 Ruby,佔用一個工作天來參加 Ruby 年會一點也不過份,進修也算是工作的一部分。下次辦我還是會辦在週五週六,週日讓腦袋休息,週一再繼續打拼。
  • 這次的 $1500 票價也算是一個門檻,考量到去年的贊助情況,所以把票價定比較高。根據今年好一點的贊助情況,下次應該可以略微調降。要跟 COSCUP 或 OSDC.TW 相比,這種比較專門的研討會拉贊助比較困難些,因此只能夠從門票上彌補。話說回來,$1500 說貴不貴,恐怕也是因人而異。看個某駭客研討會的門票價格之後,覺得沒在怕定太貴的啦(?)
  • 高一點的票價也有一點以價制量的意味,如果辦免錢,想必可以吸引到最多的聽眾,但是也造成了報到率低愛來不來的情況。有人說參加 conference 的目的之一就是可以來交朋友,那麼身為主辦單位,我會希望來參與的人是真正對 Ruby 興趣的人,而不是”來看看”的人。想來看看 Ruby on Rails 能做什麼的人太多了,但這不是這場年會的目的。
  • 關於議程,如果好整以暇等著 CFP 結束來挑選,那一定是不夠。身為主辦人一定要主動出擊,我想大概很多社群朋友都被我問過要不要來投(笑)。在議程方向上,我有幾個方向:
    1. Ruby != Rails,對很多人來說以為 Rails 就是 Ruby 的全部,但是 Ruby 並不只是 Rails 而已,各位 Ruby 人千萬不要侷限自己。因為最多人用 Rails 開發,所以無可避免的 Rails 主題最多,但是我還是特別希望有更多非 Rails 的主題,至少第二天議程上幾乎都不是 Rails (得到 godfat 大大的讚賞)。這裡是 Ruby conference,而不是 Rails conference。
    2. 國外講者之必要,今年有來自美國、日本、香港、德國、法國的講者。還記得我第一次參加 OSDC.TW 的衝擊,絕對是大大提昇你的視野和熱情,增長國際見識。另外,還會激勵你練好英文,哈。另外,COSCUP 總召 pingoo 大大也有提到第一手講者的想法。如果沒有第一手講者,那就好像辦讀書會格局而不是 “Conference” 了。
    3. 國內的 Ruby 社群相對年輕,要找到足以擔當 Keynote 的大師有困難,因此就想到來邀請到國內資深的軟體開發前輩(即使不用Ruby也沒關係,軟體開發的道理都通的),第一個想到的人選就是 Qing 大大,非常感謝他的支持,他的演講超棒的,非常感動。
  • 關於贊助,感謝銳綸、和多、Optimis 的支持(好像都是我待過的公司XD),最後一週還有非常婚禮主動來函贊助,真是太佛心了。這次也首度開放個人贊助 (從RubyKaigi那得到的點子),也是一個開源的辦法。
  • 每次辦都在想好累喔,下次不想辦了,可是活動一結束就在思考下次怎樣可以更好,還可以搞什麼。像是下次想來印衣服、想來辦 Official Party。這次獲得了一些廠商的長期支持承諾,至少以後不用擔心贊助掛零。如果您很喜歡這次的 RubyConf,貴公司願意支持 Ruby Taiwan 社群,建立開發者與廠商的正向循環,歡迎與我聯絡。我相信一場令人感動的 Conference 無形中會影響 Programmer 決定使用 Ruby 當做他第一個(或下一個)專業程式語言,這些新生軍力量將會在未來回饋給廠商。
  • 我非常注重 conference 資料紀錄,特別是 1. conference 網頁 2. 講者 slides 3. 講者錄影,我自己也常常演講,我認為這些東西對於講者來說,是很重要的 credit 紀錄。網頁的部分我選擇了靜態網頁放在子目錄下,靜態網頁 HTML 純文字最大的好處,就是十年之後還是可以看,不用擔心平台維護,十年之後 CMS 早不能跑了。錄影的話,想不到場地附的錄影品質意外的好,贊。我本來就有預期講英文的 Ruby 1.9 release manager @yugui 演講會被國外 Ruby 社群引用,果然 Ruby InsideInfoQ 就報導了,這就是邀請第一手講者的好處,不用自己宣傳就登上主要媒體 XD
  • 最後還是要感謝 OSSF 幫忙場地和支援報到、慕凡幫忙處理餐點、所有講者、贊助商、來幫忙攝影的 miau715、鴨七幫忙將去年 2010 網頁改成 2011。也許明年得考慮增加專職場務了。

最後,在這場 Conference 中,我有兩個非常感動的時刻,一個是聽 Qing 大大演講時,另一個是 Lightning Talks (沒有料到可以有這麼多精彩的LT啊)。前輩大師的經驗分享,後輩們快樂地 Hacking,那一瞬間我與現場一百多人的感動和歡樂,我永遠不會忘記。

RubyKoans: 透過單元測試來學習 Ruby 語法

rubykoans.com/EdgeCase 所推出的 Ruby 學習工具。它的特別之處在於它是用單元測試填空題的方式來認識Ruby語法。要執行很簡單,下載之後,輸入 rake 就會開始跑測試了(如何安裝Ruby請參考這裡),例如以下的測試:


  def test_arrays_and_ranges
    assert_equal __, (1..5).class
    assert_not_equal Array, (1..5)
    assert_equal __, (1..5).to_a
    assert_equal __, (1...5).to_a
  end

其中 __ 的地方就是你要解答的部份,因為是空的,所以顯然測試不會通過(Red),它會提示你那一行出錯,然後實際的值是什麼:


~/koans] (master) .rvm-$ rake
/Users/ihower/.rvm/rubies/ruby-1.9.2-p290/bin/ruby path_to_enlightenment.rb
AboutArrays#test_arrays_and_ranges has damaged your karma.

The Master says:
  You have not yet reached enlightenment.
  You are progressing. Excellent. 20 completed.

The answers you seek...
  <"FILL ME IN"> expected but was  .

Please meditate on the following code:
  /Users/ihower/koans/about_arrays.rb:48:in `test_arrays_and_ranges'

mountains are merely mountains
your path thus far [...X______________________________________________] 20/274

當你填入答案之後,就會通過進到下一個測試。你可以看到總共有274題,要寫完也是要花點時間。這模式有趣吧,練完之後應該會對Ruby語法有扎實的認識。