พูดถึง Ruby Block

วันก่อนอ่านไปเห็น 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

2 thoughts on “พูดถึง Ruby Block

    1. Tap Post author

      มันก็ดีกว่าหลายๆ ภาษาที่ส่ง anonymous function ไม่ได้อะ แต่สมมติเอาไปทำ DSL มันจะอ่านได้ไม่เนียนเท่าอะ เพราะมี functionๆ อยู่ตลอดเวลา

      อย่างเช่นที่ chef ใช้แบบนี้
      directory “/tmp/folder” do
      owner “root”
      group “root”
      mode 0755
      action :create
      end

      Reply

Leave a comment