Có gì mới trong Ruby 2.3

Bài viết được dịch từ blog What's new in Ruby 2.3? của tác giả Nithin Bekal.

Có gì mới trong Ruby 2.3

Ruby 2.3 sẽ được phát hành vào Giáng sinh này, và bản trải nghiệm đã có từ vài tuần trước. Tôi đã nghịch thử và xem xét những tính năng mới được giới thiệu

# cài đặt sử dụng RVM
rvm install 2.3.0

# cài đặt sử dụng Rbenv
brew upgrade ruby-build --HEAD
rbenv install 2.3.0

Toán tử safe navigation

Một toán tử mới (&.) được giới thiệu. Toán tử này sẽ rất hữu ích trong trường hợp bạn cần kiểm tra xem một đối tượng có nil hay không trước khi gọi một phương thức trên đối tượng đó. Toán tử sẽ trả về nil nếu đối tượng nil, ngược lại nó sẽ gọi phương thức trên đối tượng đó.

# Ruby <= 2.2.x
if user && user.admin?
  # do something
end

# Ruby 2.3
if user&.admin?
  # do something
end

Có một vài điều đang lưu ý là ở phiên bản code thứ nhất sẽ trả về false nếu user có giá trị là false trong khi toán tử safe navigation sẽ sinh ra ngoại lệ NoMethodError. Điều này tương tự với phương thức #try! của Rails.

Frozen string literals

Đến tận phiên bản Ruby 2.2, mặc định xâu có thể thay đổi được. Chúng ta được phép làm những thứ như str[2] = 'z'. Nếu chúng ta muốn làm cho xâu không thể thay đổi được, chúng ta phải gọi #freeze trên xâu đó (ví dụ str = 'foobar'.freeze).

Sử dụng các xâu không thể thay đổi được cho phép chúng ta nâng cao hiệu năng bởi Ruby sẽ sinh ít đối tượng hơn. Do đó, đã có một kế hoạch khiến xâu mặc định là không thể thay đổi được ở Ruby 3.0.

Để việc chuyển đổi trở nên dễ dàng hơn, Ruby 2.3 cho phép bạn tùy chỉnh việc mặc định cố định tất cả các xâu. Bạn có thể bật tính năng này bằng cách thêm một dòng chú thích frozen_string_literal: true ở đầu tệp. Khi được bật, tất cả các xâu có trong tệp sẽ được cố định mà không cần gọi #freeze trên chúng. Chú ý rằng tính năng này chỉ bật trên các tệp có chứa chú thích trên.

# frozen_string_literal: true

str = 'cat'
str[0] = 'b'

# frozen.rb:5:in `[]=': can't modify frozen String (RuntimeError)
#   from frozen.rb:5:in `<main>'

Mặc dù hiện tại điều này có vẻ như không phải là một thay đổi lớn, nó sẽ mở đường cho việc chuyển đổi sang Ruby 3.0 trong một vài năm tới một cách suôn sẻ.

Array#digHash#dig

Đây là một tính năng hữu ích khác được thêm vào thư viện chuẩn. Giờ chúng ta có thể truy cập vào các phần tử lồng ở trong mảng và hash thông qua một API đơn giản hơn nhiều.

Giờ ta có thể làm như sau với mảng:

list = [
  [2, 3],
  [5, 7, 9],
  [ [11, 13], [17, 19] ]
]

list.dig(1, 2)    #=> 9
list.dig(2, 1, 0) #=> 17
list.dig(0, 3)    #=> nil
list.dig(4, 0)    #=> nil

Với hash:

dict = {
  a: { x: 23, y: 29 },
  b: { x: 31, z: 37 }
}

dict.dig(:a, :x) #=> 23
dict.dig(:b, :z) #=> 37
dict.dig(:b, :y) #=> nil
dict.dig(:c, :x) #=> nil

Tính năng này sẽ rất hữu ích khi làm việc với dữ liệu JSON mà ta phân tích và gán vào hash.

“Did you mean?”

Khi bạn gặp NoMethodError vì gõ nhầm tên phương thức, Ruby sẽ đưa ra gợi ý về các phương thức có tên giống với phương thức đó.

2.3.0-preview1 :001 > "foo bar".uppcase
NoMethodError: undefined method `uppcase' for "foo bar":String
Did you mean?  upcase
               upcase!

Hash “comparison”

Hash sẽ có các phương thức so sánh được định nghĩa bên trong. Nếu bạn dùng a >= b, phương thức sẽ kiểm tra liệu tất các các cặp khóa - giá trị trong b có tồn tại trong a hay không.

{ x: 1, y: 2 } >= { x: 1 } #=> true
{ x: 1, y: 2 } >= { x: 2 } #=> false
{ x: 1 } >= { x: 1, y: 2 } #=> false

Ở ví dụ đầu, cặp khóa - giá trị [:x, 1] trong hash bên phải là tập con của các cặp khóa - giá trị trong hash bên trái - [ [:x, 1], [:y, 2] ], nên phương thức trả về true.

Điều này cũng áp dụng với tất cả các toán tử so sánh khác. Olivier Lacan, người đề xuất tính năng này cho Ruby, đã viết một bài diễn giải tuyệt vời về so sánh hash trong Ruby 2.3.

Hash#to_proc

Hash#to_proc trả về một lambda ánh xạ khóa với giá trị. Khi bạn gọi lambda với khóa, nó sẽ trả về giá trị tương ứng trong hash.

h = { foo: 1, bar: 2, baz: 3}
p = h.to_proc

p.call(:foo)  #=> 1
p.call(:bar)  #=> 2
p.call(:quux) #=> nil

Tính năng này có vẻ không hữu ích cho lắm. Sao không dùng [] để truy cập các phần tử? Nhưng sẽ rất thú vị khi chúng ta sử dụng toán tử & để tạo một proc và truyền nó vào một khối Enumerable.

h = { foo: 1, bar: 2, baz: 3}

# thay vì thế này:
[:foo, :bar].map { |key| h[key] } #=> [1, 2]

# ta có thể sử dụng cú pháp này:
[:foo, :bar].map(&h) #=> [1, 2]

Hash#fetch_values

Phương thức này hoạt động như Hash#values_at - nó lấy các giá trị tương ứng với danh sách các khóa mà ta truyền vào. Điều khác biệt là #values_at trả về nil nếu khóa không tồn tại, trong khi #fetch_values sinh ra ngoại lệ KeyError với các khóa không tồn tại.

h = { foo: 1, bar: 2, baz: 3}
h.fetch_values(:foo, :bar) #=> [1, 2]

h.values_at(:foo, :quux)    #=> [1, nil]
h.fetch_values(:foo, :quux) #=> raise KeyError

Enumerable#grep_v

Phương thức grep_v tương đương với tùy chọn -v của lệnh grep trong command line. Nó trả về danh sách các mục không phù hợp với điều kiện.

list = %w(foo bar baz)

list.grep_v(/ba/)
#=> ['foo']

list.grep(/ba/)
#=> ['bar', 'baz']

Numeric#positive?#negative?

Những hàm này đã có trong phần lõi mở rộng của Rails trong một thời gian và giờ đã được thêmm vào Ruby.

Liên kết liên quan