Một số thay đổi ở Ruby 2.3.0 qua các ví dụ

by Giang, last updated 19 Jan 2018

Phiên bản thử nghiệm Ruby 2.3.0 preview 1 vừa được phát hành giới thiệu một số cú pháp và functions mới cho các core classes. Bài viết này sẽ điểm qua một số thay đổi đáng chú ý kèm theo các ví dụ minh họa.

frozen-string-literal pragma

Đóng băng (freeze) string được giới thiệu trong Ruby 2.1 với mục đích tối ưu bộ nhớ cho việc sử dụng string. Và việc mặc định đóng băng các string đang được thảo luận cho Ruby 3.0. Trong hoàn cảnh đó Ruby 2.3.0 giới thiệu một magic comment cho phép mặc định đóng băng các string trong một Ruby file.

Giả sử chúng ta có 1 file frozen.rb như sau:

# frozen_string_literal: true
puts "abc".reverse!

Thực thi frozen.rb trên terminal:

ruby frozen.rb
#=> frozen.rb:2:in `reverse!': can't modify frozen String (RuntimeError)

Chúng ta có thể bỏ dòng comment # frozen_string_literal: true hoặc thay bằng # frozen_string_literal: false thì sẽ không bị lỗi khi thay đổi string nữa:

# frozen_string_literal: false
puts "abc".reverse!
ruby frozen.rb
#=> cba

Cũng có thể thử nghiệm frozen_string_literal comment trong irb:

# frozen_string_literal: true
"abc".reverse!
#=> RuntimeError: can't modify frozen String
# frozen_string_literal: false
"abc".reverse!
#=> "cba"

Ngoài ra, chúng ta có thể sử dụng command line option --enable=frozen-string-literal / --disable=frozen-string-literal

safe navigation operator

Xét một câu điều kiện quen thuộc như sau

current_user && current_user.is_admin?

Ruby 2.3.0 cho phép rút gọn cú pháp như trên thành như sau:

current_user&.is_admin?

Tính năng này không những giảm bớt số kí tự cần viết, đồng thời cũng bảo đảm trả về giá trị nil một cách an toàn thay vì raise NoMethodError cho nil object, một lỗi thường gặp do chúng ta không kiểm tra đầy đủ các trường hợp (VD: gọi current_user.is_admin? mà chưa kiểm tra current_user có tồn tại hay không).

Hash

Hàm Hash#dig cho phép một hash truy xuất dữ liệu một cách an toàn (trả về nil trong trường hợp không truy xuất được thay vì NoMethodError exception)

hash = {a: {b: {c: 1}}}
hash[:a][:b][:c]     # => 1
hash.dig :a, :b, :c  # => 1
hash[:a][:d][:c]     # NoMethodError: undefined method `[]' for nil:NilClass
hash.dig :a, :d, :c  # => nil

Các hàm Hash#<=, Hash#<, Hash#>=, Hash#> dành cho việc so sánh các hash:

{ a: 1, b: 2 } > { a: 1 }             # => true
{ a: 1 } > { a: 1 }                   # => false
{ b: 1 } > { a: 1 }                   # => false
{ a: 1, b: 2 } < { a: 1, b: 2, c: 3 } # => true

Hàm Hash#fetch_values cho phép raise exception khi key không tồn tại:

values = {
  foo: 1,
  bar: 2,
  baz: 3,
  qux: 4
}

values.values_at(:foo, :bar)    # => [1, 2]
values.fetch_values(:foo, :bar) # => [1, 2]

values.values_at(:foo, :bar, :invalid)    # => [1, 2, nil]
values.fetch_values(:foo, :bar, :invalid) # => KeyError: key not found: :invalid

Hàm Hash#to_proc biến một hash thành proc object

hash = { a: 1, b: 2, c: 3 }
keys = %i[a c d]

keys.map(&hash) # => [1, 3, nil]

Array

Giống như Hash#dig, hàm Array#dig cho phép một array truy xuất dữ liệu một cách an toàn (trả về nil trong trường hợp không truy xuất được thay vì NoMethodError exception)

arr = [[1, 2, 3], [4, 5, 6]]
arr[1][2]        # => 6
arr.dig(1, 2)    # => 6
arr[2][2]        # NoMethodError: undefined method `[]' for nil:NilClass
arr.dig(2, 2)    # => nil

Hàm Array#bsearch_index trả về vị trí thay vì giá trị của kết quả

[1, 2, 5, 3, 4].bsearch { |x| x > 2 }
# => 5
[1, 2, 5, 3, 4].bsearch_index { |x| x > 2 }
# => 2
[1, 2, 5, 3, 4].bsearch_index { |x| x > 5 }
# => nil

Enumerable

Hàm Enumerable#grep_v cho phép chọn lọc các phần tử KHÔNG phù hợp với điều kiện đặt ra (ngược với hàm Enumerable#grep)

[1, "abc", nil, :abc, 5.0].grep(Numeric)    # => [1, 5]
[1, "abc", nil, :abc, 5.0].grep_v(Numeric)  # => ["abc", nil, :abc]

Numeric

Thêm hai hàm Numeric#positive?Numeric#negative? để kiểm tra giá trị của một số là âm hay dương.

3.positive?                           # => true
3.negative?                           # => false
-7.33.negative?                       # => true
[1, -3, 5, -7, 9].select(&:negative?) # => [-3, -7]

cài đặt sẵn gem did_you_mean

Gem did_you_mean đưa ra gợi ý khi gặp phải các exceptions NoMethodError hay NameError. Gem này được cài đặt sẵn trong Ruby 2.3.0

3.positive
# NoMethodError: undefined method `positive' for 3:Fixnum
# Did you mean?  positive?

class User; end

Usser
# NameError: uninitialized constant Usser
# Did you mean?  User

Tham khảo