วันก่อนอ่านไปเห็น tweet ของพี่ @wiennat พูดถึง syntax block ของ Ruby ตามนี้
จะส่ง function ไปเป็น parameter ของ Ruby นี่ทำลำบากแหะ ต้องสร้างเป็น Proc แทนเอาเหรอเนี่ย https://twitter.com/wiennat/status/324438254210396160
แบบนั้นก็ได้แหลครับ แต่มันไม่สวยแบบ javascript หรือ python ที่ส่งไปเป็น callable เลย (จริงๆ คือขี้เกียจ) https://twitter.com/wiennat/status/324439144463990786
ทำให้นึกถึงตอนเริ่มเขียน Ruby ใหม่ๆ ที่เคยสงสัยว่าทำไมมันต้องแยก method ออกจาก function (Proc, lambda) ด้วย และมีความรู้สึกว่ามันสับสน และไม่เท่เลย ตอนนั้นผมคิดว่ามันต้องมีแค่อย่างเดียวอย่างที่ Javascript ทำสิถึงจะเท่ ความคิดนี้เป็นความคิดที่่ติดมาจากที่เคยศึกษา Haskell กับ Scala มาใหม่ๆ ณ ช่วงนั้น แต่ตอนนี้ผมเห็นประโยขน์ของการแยกกันนี้แล้ว เลยเอามาเล่าสู่กันฟัง
Ruby ออกแบบมาให้ Block ดูกลมกลืนไปกับ code ส่วนอื่น ซึ่งต่างจากการประกาศ function ตามภาษา Object-oriented ทั่วๆไป ที่ต้องมี syntax ที่เห็นได้ชัดเจนว่านี่คือการประกาศ function/method นะ วันนี้ผมได้ทำ refactoring code ท่านึง ซึ่งผมค่อนข้างพอใจกับผลลัพธ์ และคิดว่าไม่น่าจะเหมาะ ถ้าใช้ syntax function ของภาษาอื่น ลองมาดูกันครับ
ผมมี code แบบนี้
def method1 value = call_api return if value.nil? # use value end def method2 value = call_api return if value.nil? # use value end def method3 value = call_api return if value.nil? # use value end def call_api end
ถ้าคุณเขียนของนี้ด้วยภาษาที่คุณถนัด คุณจะจัดการกับ “return if value.nil?” ที่ซ้ำๆ กันยังไงครับ
ผม refactor code ออกมาเป็นแบบนี้
def method1 try_call_api do |value| # use value end end def method2 try_call_api do |value| # use value end end def method3 try_call_api do |value| # use value end end def try_call_api result = call_api yield result unless result.nil? end def call_api end
ดูสะอาดขึ้นเยอะเลยใช่มั้ยครับ มาลองดูกันว่าถ้าเป็น Javascript method1 จะหน้าตาเป็นยังไง
function method1() { tryCallApi(function(value) { // use value }); } function tryCallApi(callback) { var result = callApi(); if (result === null) { return callback(result); } return null; }
จุดที่น่าสนใจคือบรรทัดระหว่าง
try_call_api do |value| กับ
tryCallApi(function(value) {
จะเห็นได้ว่า do |value| มันดูดีในแง่ความเป็นภาษาคนกว่า function(value) { มากๆ
จากความสั้นของ do ตรงนี้(บวกกับความสามารถด้าน meta programming) ทำให้ Ruby กลายเป็นภาษาที่นำไปเขียน DSL ได้ง่ายและสนุก อย่างที่เราได้เห็นใน Chef หรือ tool ของ Ruby เอง อย่างเช่น RSpec, Rails routing
ใน code Ruby ทั่วไป เราจะไม่ค่อยเห็นคนสร้าง Proc หรือ lambda object ขึ้นมาแล้วโยนไปเป็น method parameter เท่าไหร่ถึงแม้ว่าจะสามารถทำได้ ส่วนมากจะเห็นคนใช้เป็น block ไปเลย ซึ่งเกิดจากความง่ายของการใช้ block ที่มาจาก design decision ของภาษาที่ผู้สร้างเลือกที่จะยืมเอา functional syntax มาใช้อย่างพอดีและพอเพียงบนภาษา Object-oriented
ผมว่า JS มันก็สวยออก https://gist.github.com/hybridknight/5629115
ถ้าตัดเรื่อง function( ออกไป 5555+
มันก็ดีกว่าหลายๆ ภาษาที่ส่ง anonymous function ไม่ได้อะ แต่สมมติเอาไปทำ DSL มันจะอ่านได้ไม่เนียนเท่าอะ เพราะมี functionๆ อยู่ตลอดเวลา
อย่างเช่นที่ chef ใช้แบบนี้
directory “/tmp/folder” do
owner “root”
group “root”
mode 0755
action :create
end