นอกจากวันคริสต์มาสจะเป็นวันประสูติของพระเยชูแล้ว เราเหล่า 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 ด้วยดังแสดงในรูปแบบด้านล่าง
1
2
3
4
5
6
7
8
| case [variable or expression]
in [pattern] [if|unless condition]
...
in [pattern] [if|unless condition]
...
else
...
end
|
- ตัวอย่างการใช้งานกับชุดข้อมูล Array
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 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
1
2
3
4
5
6
| # 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 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| ได้
1
2
3
4
5
| # 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 ให้อยู่ในรูปคีย์เวิร์ดให้อัตโนมัติ
1
2
3
4
| 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 จะแสดงคำเตือนขึ้นมา ดังนั้นถ้าจะการเรียกใช้งานทำได้ถูกต้องจะต้องใส่ { } คลอบไว้ด้วย
1
2
3
4
| 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 หรือคีย์เวิร์ด
1
2
3
| 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 ยังคงทำงานได้ถูกต้องไม่แสดงคำเตือนขึ้นมา
1
| def foo(opt={}); end; foo( key: 42 ) # OK
|
- ไม่อนุญาตให้ส่งพารามิเตอร์แบบคีย์เวิร์ดที่ไม่เป็น Symbol ในกรณีที่ฟังก์ชันนิยามพารามิเตอร์แบบคีย์เวิร์ดไม่จำกัดจำนวน
1
| def foo(**kw); p kw; end; foo("str" => 1) #=> {"str"=>1}
|
- อนุญาตให้ประกาศ
**nil ในฟังก์ชัน เพื่อระบุว่าฟังก์ชันไม่รับพารามิเตอร์แบบคีย์เวิร์ด ถ้าหากมีการเรียกฟังก์ชันโดยการส่งพารามิเตอร์เป็นแบบคีย์เวิร์ดเข้าไปจะแแสดง ArgumentError ออกมา
1
2
3
4
5
| 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
เราสามารถส่งต่อพารามิเตอร์จากฟังก์ชันที่ถูกเรียกไปยังฟังก์ชันอื่นได้ด้วย ...
1
2
3
| def foo(...)
bar(...)
end
|
ฟังก์ชันใหม่ใน Array
#intersection เป็นฟังก์ชันที่หาข้อมูลที่เหมือนกันระหว่าง Array 2 ตัว หรือจะใช้ & แทนได้เหมือนกัน
1
2
| [1, 2, 3].intersection([2, 3, 4]) => [2, 3]
[1, 2, 3] & [2, 3, 4] => [2, 3]
|
ฟังก์ชันใหม่ใน Enumerable
#tally ส่งคืนผลลัพท์เป็น Hash ของข้อมูลพร้อมจำนวน
1
| %w(a a a b b c).tally => { "a"=>3, "b"=>2, "c"=>1 }
|
#filter_map จากการที่ #select และ #map ถูกใช้งานบ่อยในการแปลงข้อมูลเป็น Array ขณะที่มีการกรองข้อมูลบางอย่างด้วย ดังนั้นจึงได้มีการเพิ่มฟังก์ชัน filter_map ขึ้นมาเพื่อทำให้ใช้งานได้ง่ายขึ้น
1
| [1, 2, 3].filter_map {|x| x.odd? ? x.to_s : nil } #=> ["1", "3"]
|
ฟังก์ชันใหม่ใน Enumerator
- #produce
เป็นฟังก์ชันสำหรับการสร้างลำดับที่ไม่สิ้นสุด โดยการส่งค่าล่าสุดที่ได้ไปในบล๊อคถัดไป
1
| Enumerator.produce(1, &:next).take(5)
|
สำหรับฟีเจอร์ในเวอร์ชัน 2.7 นั้นยังมีอีก แต่ในบทความนี้จะขอสรุปไว้เพียงเท่านี้ หากต้องการศึกษาเพิ่มเติมสามารถค้นหาได้จากลิงค์ด้านล่าง
References: