Block trong Ruby
Trong bài viết lần này, mình sẽ chia sẻ vơi các bạn về một kiến thức sẽ rất hay được sử dụng trong ngôn ngữ lập trình Ruby, đó chính là block. Block chính là một trong những tính năng mạnh mẽ nhất của Ruby.
1. Block là gì ?
Block là một khối lệnh được đặt trong do
...end
, chúng ta có 2 cách viết đối với một block.
Với những đoạn code nhiều dòng sẽ được đặt trong do...end
[1, 2, 3].each do |n| puts "Số #{n}" end
Đối với những ngắn, có thể viết trên một dòng thì chúng ta đặt trong 2 dấu ngoặc nhọn {}
Bài viết này được đăng tại [free tuts .net]
[1, 2, 3].each {|n| puts "Số #{n}"}
Kết quả
Số 1 Số 2 Số 3
Cả hai cách viết trên đều cho chúng ta kết quả giống nhau. Có một điều đặc biệt đó chính là |n|, đây là tham số được truyền vào block với giá trị lần lượt tương ứng với các giá trị trong mảng là 1, 2 và 3.
Đối với những đoạn code dài dòng từ 2 trở nên, các bạn nên sử dụng cách viết đặt trong do..end để code trong clear và dễ đọc hơn.
Tham số trong block được đặt trong dấu | thay vì dấu ngoặc đơn như cách chúng ta truyền tham số ở method
2. Sử dụng yield trong Block
Từ khóa yield có thể coi là một trong những "sức mạnh" của block. Nó giúp chúng ta thực hiện một khối lệnh trong block mà không ảnh hưởng gì tới các đoạn code xung quanh.
Khi làm việc với method chúng ta có thể truyền trực tiếp block vào method dưới dạng như là một tham số.
Ví dụ mình có một hàm viết như thế này
def hello puts "Hello" end hello
Kết quả
Hello
Giờ chúng ta sẽ thử truyền một block vào method
def hello puts "Hello" end hello { puts "Code truyền vào method" }
Kết quả vẫn sẽ là Hello.
Để chúng ta có thể thực thi được đoạn code bên trong block mà chúng ta truyền vào method hello thì sẽ sử dụng đến yield. Để hiểu dễ hơn chúng ta sẽ xem qua ví dụ luôn
def hello puts "Hello" yield puts "Goodbye" end hello { puts "Code truyền vào method" }
Kết quả
Hello Code truyền vào method Goodbye
Khi chúng ta sử dụng method hello nó sẽ thực thi lần lượt các đoạn code được viết trong hàm và khi đến dòng yield, nó sẽ gọi đến block mà chúng ta đã truyền vào. Sau khi đoạn code bên trong block thực thi xong, các đoạn code tiếp bên trong method sẽ được thực thi tiếp.
Nếu chúng ta khia báo yield mà không truyền block vào method thì sẽ gây ra lỗi và đoạn code bên dưới yield sẽ không được thực thi
Ví dụ
def hello puts "Hello" yield puts "Goodbye" end hello
Kết quả
Hello Traceback (most recent call last): 1: from loop.rb:7:in `<main>' loop.rb:3:in `hello': no block given (yield) (LocalJumpError)
Đoạn code bên trên yield vẫn sẽ được thực thi cho đến khi yield được gọi tới.
3. Kiểm tra sự tồn tại của block
Như mình đã nói ở phần trên, khi chúng ta sử dụng yield mà lại quên mất việc truyền block vào method thì sẽ gây ra lỗi chương trình.
Để kiểm soát được việc này theo mình nên cẩn thận kiểm tra xem có tồn tại block hay không, nếu không thì bỏ qua để tránh gây ra lỗi không mong muốn.
Muốn kiểm tra được sự tồn tại của block chúng ta sẽ sử dụng phương thức block_given?
def hello puts "Hello" return yield if block_given? puts "Goodbye" end hello
Kết quả
Hello Goodbye
4. Truyền tham số vào yield
Ở phần trên chúng ta đã biết yield nhận vào một block và thực thi code trong block đó, ngoài ra chúng ta cũng có thể truyền thêm tham số vào trong yield. Bất kì tham số nào được truyền vào trong yield sẽ đóng vai trò là tham số của block.
Ví dụ
def hello puts "Hello" return yield('Phú', 22) if block_given? puts "Goodbye" end hello do |name, age| puts "Xin chào tôi là #{name}, năm nay tôi #{age} tuổi" end
Kết quả
Hello Xin chào tôi là Phú, năm nay tôi 22 tuổi
Các tham số được truyền vào yield sẽ chỉ có phạm vi cục bộ bên trong block mà bạn truyền vào, tức là nó chỉ có giá trị bên trong block đó mà thôi.
Thứ tự tham số bạn truyền vào trong yield sẽ tương ứng với các tham số mà chúng ta sẽ gọi ở bên trong block.
Có thể thấy, các tham số bên trong |...| sẽ chứa các tham số của block với giá trị tương ứng được truyền từ yield vào.
5. Giá trị trả về
yield sẽ trả về giá trị của biểu thức cuối cùng trong block, tức là kết quả trả về của block sẽ tương ứng với kết quả của yield.
def hello value = yield puts "Kết quả của yield là #{value}" end hello do 1 + 1 end
Kết quả
2
6. &block là gì ?
&block là một đối với những newbie mới học Ruby thì chắc sẽ khá lạ lẫm. Khi tiếp xúc và làm việc với Ruby thì có thể bạn sẽ rất hay gặp &block xuất hiện trong code của bạn.
Ở phần trên mình đã giới thiệu với các bạn về việc sử dụng yield để gọi tới block. Còn một cách nữa giúp chúng ta tham chiếu tới block đó chính là sử dụng &block.
Lưu ý là chúng ta cần phải đặt tiền tố là & để tham chiếu tới block.
Ruby cho phép bạn truyền bất cứ đối tượng nào thành phương thức như thể nó là một block. Ruby sẽ xử lý đối tượng này để chuyển chúng về dạng block
Ví dụ
def hello &block puts block block.call end hello do puts "#{1+1}" end
Đọc doạn code trên chúng ta sẽ thấy biến block trong method hello sẽ tham chiếu đến block. Lúc này, biến block sẽ là một Proc. Cụ thể hơn về Proc các bạn hãy xem ở bài tiếp theo, mình sẽ giải thích kỹ hơn.
Để có thể thực thi được các đoạn mã bên trong block chúng ta cần phải gọi đến một phương thức là call. Đây cũng giống như cái cách mà chúng ta làm việc với yield, chỉ khác là thay vì sử dụng yield chúng ta sẽ sử dụng một đối tượng Proc và gọi đến phương thức call để thực thi block.
Kết quả
#<Proc:0x00005563e3e56db0@loop.rb:6> 2
Tùy vào sở thích mỗi người mà sẽ lựa chọn sử dụng call hoặc yield. Tuy nhiên về phần hiệu năng thì yield sẽ mang lại một tốc độ tốt hơn so với cách chúng ta sử dụng call.
7. Kết luận
Ở bài này mình đã giới thiệu với các bạn về block trong Ruby, đây là một kiến thức quan trọng mà chúng ta cần phải hiểu và nắm rõ thì mới có thể làm việc với chúng dễ dàng và hiệu quả hơn. Thế nên, các bạn hãy học phần block này cẩn thận nhé.