Ruby 2.7

Published on
Tags
ruby

นอกจากวันคริสต์มาสจะเป็นวันประสูติของพระเยชูแล้ว เราเหล่า Rubyist ก็ให้ความสำคัญกับวันคริสต์มาสเช่นเดียวกัน เนื่องจากเป็นวันที่มีการปล่อย Ruby เวอร์ชันใหม่ออกมานั้นเอง สำหรับวันคริสต์มาสที่ผ่านมา (25/12/2562) ก็ได้มีการประกาศปล่อย Ruby เวอร์ชัน 2.7 ออกมาอย่างเป็นทางการ ซึ่งน่าเป็นเวอร์ชัน 2 ตัวสุดท้ายก่อนที่จะกลายเป็นเวอร์ชัน 3 ที่ทางทีมพัฒนาได้วางแผนที่จะปล่อยออกมาในวันคริสต์มาสในปีนี้ (แต่ก็แอบเห็นมีเวอร์ชัน 2.8-DEV ในรายการติดตั้งของ rbenv) เกริ่นกันมาพอสมควรแหละ มาดูกันว่าเวอร์ชัน 2.7 นี้มีฟีเจอร์อะไรใหม่ ปรับปรุงแก้ไขอะไรบ้าง

ฟีเจอร์ใหม่ใน Ruby 2.7

Pattern Matching (Experimental)

Pattern Matching เป็นฟีเจอร์ที่ใช้ตรวจสอบจับคู่เพื่อเปรียบเทียบข้อมูล ในเวอร์ชันนี้เราสามารถนำมาใช้งานในชุดคำสั่งแบบ  case ซึ่งโดยปกติเราจะใช้ชุดคำสั่ง case/when แต่ในกรณีที่จะใช้งานร่วมกับ Pattern Matching เราจะใช้ชุดคำสั่ง case/in แทน นอกจากนี้ยังรับการใช้งานร่วมกับ if หรือ unless ด้วยดังแสดงในรูปแบบด้านล่าง

case [variable or expression]
in [pattern] [if|unless condition]
  ...
in [pattern] [if|unless condition]
  ...
else
  ...
end
  • ตัวอย่างการใช้งานกับชุดข้อมูล Array
# Simple array
data = ['John', 'Doe', 27]
case data
in [name, lastname, age]
  puts "#{name} #{lastname} #{age}"
end

# Complex array
language = ['python', [3, 0, 1]]
case language
in ['ruby', [2, 7, *]]
  puts "Get ruby 2.7.x"
in ['python', [3, 0, *]]
  puts "Get python 3.0.x"
else
  puts "Unknown language"
end
  • ตัวอย่างการใช้งานกับชุดข้อมูล Hash
# Hash
user = { username: 'bob123', nickname: 'bob', language: 'th' }
case user
in { username: 'bob123', nickname: nickname, language: language }
  puts "bob123 (#{nickname}) uses #{language} language"
end
  • ตัวอย่างการใช้งานกับชุดข้อมูล JSON
# JSON
person = {
  username: 'karn18',
  profile: {
    firstName: 'Karn',
    lastName: 'Tirasoontorn'
  },
  age: 35
}

require 'json'
case JSON.parse(person.to_json, symbolize_names: true)
in { username: "karn18", profile: { firstName: first_name } }
  puts "Username: karn18, First Name: #{first_name}"
end

สำหรับการใช้งาน Pattern Matching นั้นมีความหลากหลาย ดังนั้นสามารถศึกษาเพิ่มเติมได้ที่ https://www.toptal.com/ruby/ruby-pattern-matching-tutorial

อย่างไรก็ตาม Matz ได้บอกว่าการใช้งาน Pattern Matching ใน Ruby นั้นค่อนข้างช้า ดังนั้นควรใช้อย่างระมัดระวังเพื่อประสิทธิภาพความรวดเร็วของโปรแกรม

Numbered Parameters (Experimental)

พารามิเตอร์ตัวเลขถูกนำมาใช้เป็นค่ามาตรฐานสำหรับพารามิเตอร์ในบล็อก ทำให้เราสามารถละการใช้งาน |value| ได้

# Array
[1, 2, 10].map { puts _1 } => 1, 2, 10

# Hash
{ id: 1, name: 'ruby 101', price: 30 }.map { puts "#{_1} - #{_2}" } => id - 1, name - ruby 101, price - 30

Separation of positional and keyword arguments

ปกติการส่งพารามิเตอร์ที่มีการจัดลำดับตำแหน่ง และแบบคีย์เวิร์ดร่วมกันนั้น Ruby จะทำการตรวจสอบและจัดการให้อัตโนมัติ แต่สำหรับในเวอร์ชัน 3 การส่งพารามิเตอร์เข้าไปในฟังก์ชันจะต้องระบุให้ชัดเจนว่าเป็นการส่งพารามิเตอร์แบบใดเข้าไป ซึ่งในเวอร์ชัน 2.7 นี้จะเพียงแค่แสดงคำเตือนขึ้นมาเมื่อเราไม่ได้มีการระบุรูปแบบที่ถูกต้องเท่านั้น ที่นี้เรามาดูมันแตกต่างจากเดิมอย่างไร

  • เมื่อฟังก์ชันนิยามให้รับคีย์เวิร์ดเป็นพารามิเตอร์ตัวสุดท้าย แต่ฟังก์ชันถูกเรียกใช้งานโดยการส่ง Hash เข้าไปจะต้องทำการใส่ ** นำหน้า { } ไว้ด้วย ทั้งนี้โดยเวอร์ชันก่อนหน้า Ruby จะทำการแปลงข้อมูลที่อยู่ใน Hash ให้อยู่ในรูปคีย์เวิร์ดให้อัตโนมัติ
def foo(key: 42); end; foo({key: 42})   # warned
def foo(**kw);    end; foo({key: 42})   # warned
def foo(key: 42); end; foo(**{key: 42}) # OK
def foo(**kw);    end; foo(**{key: 42}) # OK
  • เมื่อฟังก์ชันนิยามให้รับพารามิเตอร์แบบลำดับ และคีย์เวิร์ดแบบไม่จำกัดจำนวน เมื่อมีการเรียกใช้ฟังก์ชันโดยไม่ได้ระบุพารามิเตอร์แบบลำดับ แต่ระบุพารามิเตอร์แบบคีย์เวิร์ดเท่านั้น ซึ่งปกติพารามิเตอร์แบบคียเวิร์ดจะถูกว่าเป็นพารามิเตอร์ตัวสุดท้าย เมื่อรันโปรแกรม Ruby จะแสดงคำเตือนขึ้นมา ดังนั้นถ้าจะการเรียกใช้งานทำได้ถูกต้องจะต้องใส่ { } คลอบไว้ด้วย
def foo(h, **kw); end; foo(key: 42)      # warned
def foo(h, key: 42); end; foo(key: 42)   # warned
def foo(h, **kw); end; foo({key: 42})    # OK
def foo(h, key: 42); end; foo({key: 42}) # OK
  • เมื่อฟังก์ชันนิยามให้รับพารามิเตอร์ที่เป็น Hash และคีย์เวิร์ดร่วมกัน และมีการส่งพารามิเตอร์ที่เป็น Hash มีคีย์ที่เป็นทั้ง Symbol และไม่เป็น Symbol ถ้าไม่ได้ระบุประเภทที่ชัดเจน Ruby จะแสดงคำเตือนขึ้นมา ดังนั้นเวลาใช้งานควรระบุประเภทให้ชัดเจนว่าเป็น Hash หรือคีย์เวิร์ด
def foo(h={}, key: 42); end; foo("key" => 43, key: 42)   # warned
def foo(h={}, key: 42); end; foo({"key" => 43, key: 42}) # warned
def foo(h={}, key: 42); end; foo({"key" => 43}, key: 42) # OK
  • เมื่อฟังก์ชันนิยามให้รับพารามิเตอร์ที่เป็น Hash เมื่อมีการเรียกใช้งานฟังก์ชันโดยส่งพารามิเตอร์แบบคีย์เวิร์ด Ruby ยังคงทำงานได้ถูกต้องไม่แสดงคำเตือนขึ้นมา
def foo(opt={});  end; foo( key: 42 )   # OK
  • ไม่อนุญาตให้ส่งพารามิเตอร์แบบคีย์เวิร์ดที่ไม่เป็น Symbol ในกรณีที่ฟังก์ชันนิยามพารามิเตอร์แบบคีย์เวิร์ดไม่จำกัดจำนวน
def foo(**kw); p kw; end; foo("str" => 1) #=> {"str"=>1}
  • อนุญาตให้ประกาศ **nil ในฟังก์ชัน เพื่อระบุว่าฟังก์ชันไม่รับพารามิเตอร์แบบคีย์เวิร์ด ถ้าหากมีการเรียกฟังก์ชันโดยการส่งพารามิเตอร์เป็นแบบคีย์เวิร์ดเข้าไปจะแแสดง ArgumentError ออกมา
def foo(h, **nil); end; foo(key: 1)       # ArgumentError
def foo(h, **nil); end; foo(**{key: 1})   # ArgumentError
def foo(h, **nil); end; foo("str" => 1)   # ArgumentError
def foo(h, **nil); end; foo({key: 1})     # OK
def foo(h, **nil); end; foo({"str" => 1}) # OK

Argument Forwarding

เราสามารถส่งต่อพารามิเตอร์จากฟังก์ชันที่ถูกเรียกไปยังฟังก์ชันอื่นได้ด้วย  ...

def foo(...)
  bar(...)
end

ฟังก์ชันใหม่ใน Array

  • #intersection เป็นฟังก์ชันที่หาข้อมูลที่เหมือนกันระหว่าง Array 2 ตัว หรือจะใช้ & แทนได้เหมือนกัน
[1, 2, 3].intersection([2, 3, 4]) => [2, 3]
[1, 2, 3] & [2, 3, 4] => [2, 3]

ฟังก์ชันใหม่ใน Enumerable

  • #tally ส่งคืนผลลัพท์เป็น Hash ของข้อมูลพร้อมจำนวน
%w(a a a b b c).tally => { "a"=>3, "b"=>2, "c"=>1 }
  • #filter_map จากการที่ #select และ #map ถูกใช้งานบ่อยในการแปลงข้อมูลเป็น Array ขณะที่มีการกรองข้อมูลบางอย่างด้วย ดังนั้นจึงได้มีการเพิ่มฟังก์ชัน filter_map ขึ้นมาเพื่อทำให้ใช้งานได้ง่ายขึ้น
[1, 2, 3].filter_map {|x| x.odd? ? x.to_s : nil } #=> ["1", "3"]

ฟังก์ชันใหม่ใน Enumerator

  • #produce เป็นฟังก์ชันสำหรับการสร้างลำดับที่ไม่สิ้นสุด โดยการส่งค่าล่าสุดที่ได้ไปในบล็อกถัดไป
Enumerator.produce(1, &:next).take(5)

สำหรับฟีเจอร์ในเวอร์ชัน 2.7 นั้นยังมีอีก แต่ในบทความนี้จะขอสรุปไว้เพียงเท่านี้ หากต้องการศึกษาเพิ่มเติมสามารถค้นหาได้จากลิงค์ด้านล่าง

References: