น่าจะเคยเห็น pattern นี้ method มี options hash เป็น argument สุดท้าย เก็บ extra parameters:
def hello_message(name_parts = {}) first_name = name_parts.fetch(:first_name) last_name = name_parts.fetch(:last_name) "Hello, #{first_name} #{last_name}" end
เสียดายต้อง extract parameters จาก hash เยอะ setup ก่อนถึงส่วนสำคัญ
แตถ้าเปลี่ยน method ให้用 keyword arguments ใน Ruby 2.0+ ไม่ต้อง pull :first_name และ :last_name จาก hash Ruby ทำให้เอง:
def hello_message(first_name:, last_name:) "Hello, #{first_name} #{last_name}" end
ดีกว่า ถ้า app 用 Ruby 1.9+ hash syntax methods ใช้ keyword arguments ได้โดยไม่เปลี่ยน callers:
def hello_message_with_an_options_hash(name_parts = {}) first_name = name_parts.fetch(:first_name) last_name = name_parts.fetch(:last_name) "Hello, #{first_name} #{last_name}" end def hello_message_with_keyword_arguments(first_name:, last_name:) "Hello, #{first_name} #{last_name}" end hello_message_with_an_options_hash(first_name: "Justin", last_name: "Weiss") hello_message_with_keyword_arguments(first_name: "Justin", last_name: "Weiss")
เห็นไหม arguments เหมือนกัน!
ดัน Keyword Argument Syntax เกิน
ถ้ายัง ไม่ได้ สลับไป new Hash syntax? แปลงโค้ดทั้งหมดได้ แตใน Ruby 2.2.1 เก่า hash syntax ใช้กับ keyword arguments ได้ดี:
irb(main):007:0> hello_message_with_keyword_arguments(:first_name => "Justin", :last_name => "Weiss") => "Hello, Justin Weiss"
ดี! ส่ง hash object แทน arguments?
irb(main):008:0> options = {:first_name => "Justin", :last_name => "Weiss"} irb(main):009:0> hello_message_with_keyword_arguments(options) => "Hello, Justin Weiss"
Whoa ผสม hash และ keyword arguments?
irb(main):010:0> options = {last_name: "Weiss"} irb(main):011:0> hello_message_with_keyword_arguments(first_name: "Justin", options) SyntaxError: (irb):11: syntax error, unexpected ')', expecting => from /usr/local/bin/irb:11:in `<main>'
โอเค เกินไป แก้ได้โดย Hash#merge สร้าง hash ส่งเข้าไป แตมีวิธีดีกว่า
ถ้า用 regular arguments splat arguments จาก Array ด้วย *:
def generate_thumbnail(name, width, height) # ... end dimensions = [240, 320] generate_thumbnail("headshot.jpg", *dimensions)
แต่มีวิธี splat keyword arguments เข้า argument list ไหม?
มี: ** แก้ตัวอย่างที่พัง:
irb(main):010:0> options = {last_name: "Weiss"} irb(main):011:0> hello_message_with_keyword_arguments(first_name: "Justin", **options) => "Hello, Justin Weiss"
ถ้า 真的 คุณผสม regular args, keyword args, และ splats:
def hello_message(greeting, time_of_day, first_name:, last_name:) "#{greeting} #{time_of_day}, #{first_name} #{last_name}!" end args = ["Morning"] keyword_args = {last_name: "Weiss"} hello_message("Good", *args, first_name: "Justin", **keyword_args) # => "Good Morning, Justin Weiss!"
แตถ้าต้องทำแบบนี้ อาจผิดที่ไหนไปก่อนมาก!
Capture Keyword Arguments ง่าย
รู้ไหมว่าส่ง method arguments ทั้งหมดเป็น array ด้วย *?
def argument_capturing_method(*args) args end argument_capturing_method(1, 2, 3) # => [1, 2, 3]
Keyword arguments ด้วย แปลงเป็น hash โผล่เป็น last argument ของ args array:
argument_capturing_method(1, 2, 3, key: "value") # => [1, 2, 3, {:key=>"value"}]
แต args.last[:key] ไม่ใช่วิธีดีอ่าน keyword arguments ที่จับมา 用 ** syntax ใหม่ได้ keyword arguments แยก:
def dual_argument_capturing_method(*args, **keyword_args) {args: args, keyword_args: keyword_args} end dual_argument_capturing_method(1, 2, 3, key: "value") # => {:args=>[1, 2, 3], :keyword_args=>{:key=>"value"}}
ด้วย syntax นี้ เข้าถึง regular argument แรกด้วย args[0] และ keyword argument :key ด้วย keyword_args[:key]
… กลับมาที่ options hashes อีกแล้ว
Keyword arguments ดี ลด parameter extraction boilerplate มาก และอาจไม่ต้องเปลี่ยนโค้ดเลย
แตตอนเขียน generic methods มี techniques ใหม่ต้องเรียนจัดการ keyword arguments ให้ดี อาจไม่ต้อง用บ่อย แตตอนต้องการ power และ flexibility นี้จะรออยู่