Ruby 2.7

นอกจากวันคริสต์มาสจะเป็นวันประสูติของพระเยชูแล้ว เราเหล่า 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
# 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
user = { username: 'bob123', nickname: 'bob', language: 'th' }
case user
in { username: 'bob123', nickname: nickname, language: language }
  puts "bob123 (#{nickname}) uses #{language} language"
end
# 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 นี้จะเพียงแค่แสดงคำเตือนขึ้นมาเมื่อเราไม่ได้มีการระบุรูปแบบที่ถูกต้องเท่านั้น ที่นี้เรามาดูมันแตกต่างจากเดิมอย่างไร

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
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
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
def foo(opt={});  end; foo( key: 42 )   # OK
def foo(**kw); p kw; end; foo("str" => 1) #=> {"str"=>1}
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

[1, 2, 3].intersection([2, 3, 4]) => [2, 3]
[1, 2, 3] & [2, 3, 4] => [2, 3]

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

%w(a a a b b c).tally => { "a"=>3, "b"=>2, "c"=>1 }
[1, 2, 3].filter_map {|x| x.odd? ? x.to_s : nil } #=> ["1", "3"]

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

Enumerator.produce(1, &:next).take(5)

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

References: