Created
September 23, 2018 17:52
-
-
Save oNguyenNgocTrung/ab526c3152dd79d7405c29873e9a9acb to your computer and use it in GitHub Desktop.
Revisions
-
Nguyen Ngoc Trung created this gist
Sep 23, 2018 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,266 @@ --- layout: post title: Block trong Ruby tags: [ruby, block] --- ## Block là gì? Về cơ bản thì block là một hay nhiều dòng code được bao bởi `{}` hay `do` và `end` Về chức năng thì chúng như nhau, nhưng thường sẽ sử dụng `{}` cho block nào có ít code, thường là một dòng và ngược lại, nếu có nhiều code, thường hai dòng trở lên thì dùng `do ... end` mục đích chỉ để dễ đọc hơn thôi. Ví dụ khi viết code nhiều dòng ```ruby [1, 2, 3].each do |n| # Prints out a number puts "#{n}" end ``` Chúng ta có thể viết thành 1 dòng như thế này ```ruby [1, 2, 3].each {|n| puts "#{n}"} ``` `|n|` được gọi là `tham số block (block parameter)`, giá trị của nó trong trường hợp này được gọi lần lượt theo giá trị của mảng đã cho, đầu tiên nhận giá trị là 1, lần thứ 2 là 2, cuối cùng là 3 Kết quả ```ruby 1 2 3 ``` ## `yield` và cách sử dụng `yield` là một phần sức mạnh của `ruby block`, nó chịu trách nhiệm về mọi sự ảo diệu cũng như nhầm lẫn trong `block`. Phần lớn sự nhầm lẫn đều đến từ cách gọi `block` và việc làm thế nào để truyền tham số vào trong nó ```ruby def my_method puts "reached the top" yield puts "reached the bottom" end my_method do puts "reached yield" end ``` Kết quả ``` reached the top reached yield reached the bottom ``` Về cơ bản, khi bạn thực thi `my_method` và đến dòng gọi đến `yield`, nó sẽ chuyển đến gọi block truyền tham số. Sau đoạn code bên trong block chạy xong, nó sẽ lại tiếp tục chạy code trong hàm `my_method` tiếp. ## `yield` có tác dụng thực thi code trong block, và bản thân nó sẽ nhận giá trị return từ block đó. ```ruby def test_yield puts "Start of method" puts yield puts "End of method" end test_yield {1 + 1} ``` Kết quả ``` Start of method 2 End of method => nil ``` Ví dụ trên block `{1 + 1}` sẽ return về 2, `yield` thực thi code trong block nên sẽ nhận được return từ block là 2. `yield` cũng có thể truyền tham số vào. ```ruby def test_yield puts "Hello" yield "Foo", "Bar" end test_yield {|str1, str2, str3| puts str1 + str2} test_yield do |str1, str2| puts str1 + str2 end ``` ``` Hello FooBar => nil ``` Dễ dàng nhận ra, `| ... |` sẽ chứa danh sách tham số được sử dụng cho block, được truyền từ `yield` vào. Danh sách tham số trong `| ... |` sẽ map chính xác với danh sách tham số truyền vào khi gọi `yield`, gọi thừa hay thiếu cũng không sao, chỉ là không có dữ liệu cho tham số thôi. ```ruby def test_yield yield "a" end test_yield do |str1, str2| puts str1 puts str2.nil? end ``` kêt quả ``` a true ``` ```ruby def test_yield yield "a", "b" end test_yield do |str1| puts str1 end ``` Kết quả ``` a => nil ``` ## Sử dụng biến cục bộ trong block. Để khai báo biến cục bộ dùng trong block, trong danh sách tham số khai báo bởi `| ... |`, ta sẽ dùng `;` để chia danh sách tham số thành 2 phần, phần đầu là những tham số được truyền vào qua `yield`, phần còn lại phía sau là danh sách biến dùng cục bộ trong block. ```ruby x = 10 y = 10 1.times do |n; y| x = n y = n puts "x inside the block : #{x}" puts "y inside the block : #{y}" end puts "x outside the block : #{x}" puts "y outside the block : #{y}" ``` Kết quả ``` x inside the block : 0 y inside the block : 0 x outside the block : 0 y outside the block : 10 ``` Ta dễ dàng thấy, biến `x` không được khai báo như biến local của block, nên giá trị của nó ở bên ngoài block đã bị thay đổi khi ta gán `x = n`, ngược lại giá trị của `y` vẫn không bị thay đổi bên ngoài block. ### Sử dụng `block_given? ` để kiểm tra có block nào được truyền vào method hay không. Một điều mà không nhắc cũng hiểu, đó là nếu block được truyền vào nhưng ta không gọi tới `yield` thì block đó cũng không có tác dụng gì và không hề được thực thi. Còn ngược lại, ta gọi `yield` nhưng không truyền block vào thì sao ? ```ruby def test_no_block yield end test_no_block ``` ``` LocalJumpError: no block given (yield) ``` Sẽ có lỗi ngay lập tức. Để tránh lỗi xảy ra, ta có thể sử dụng method `block_given?`, method này sẽ kiểm tra xem có block nào được truyền vào method mà ta đang gọi `yield` hay không. ```ruby def test_block if block_given? yield else puts "No block given" end end test_block test_block {puts "I saw a block"} ``` ``` No block given I saw a block ``` # Sử dụng `&block` Sử dụng cú pháp này để truyền reference của block như là một tham số vào trong method. ```ruby def test_method &block puts block block.call end test_method {puts "Hello Proc"} ``` ``` #<Proc:0x007f99e9896950@(pry):193> Hello Proc => nil ``` Ta có thể thấy block ta truyền vào là một instance của class `Proc`. # Sử dụng `&:something` ```ruby ["a", "b"].map &:upcase ``` ``` => ["A", "B"] ``` Như vừa nhắc tới cách sử dụng `&block`, sẽ không có chuyện gì nếu tham số truyền vào method là reference của một block, nhưng nếu nó không reference tới block thì method sẽ gọi tới `to_proc` để chuyển nó thành block cho việc sử dụng như bình thường. Như ví dụ trên, đầu tiên ta sẽ có `:upcase.to_proc` để tạo ra một instance của `Proc`, sau đó truyền reference của block vừa tạo ra vào trong method `.map` để sử dụng. # Ứng dụng Ứng dụng của block thì khỏi cần kể, dùng nó ở mọi lúc mọi nơi, nó cũng là một phần tạo ra sự đặc biệt cho ngôn ngữ Ruby. Tiện vừa nhắc tới việc nếu tham số truyền vào không phải reference tới một block, nó sẽ được `to_proc` để cố gắng chuyển thành một block. Ta có thể áp dụng nó cho bài toán cơ bản sau : > Từ một mảng các số nguyên, lọc ra những số chia hết cho 3. ```ruby class Fixnum def to_proc Proc.new do |obj| obj % self == 0 end end end numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].select &3 puts numbers ``` ``` 3 6 9 ``` 3 là một `Fixnum`, nên ta định nghĩa thêm method `to_proc` nhằm mục đích đưa logic để xác định nó có chia hết cho 3 hay không. # Kết luận `Block` là một trong những tính năng mạnh mẽ của ruby nhưng thường xuyên bị bỏ quên. Bạn có thể thấy `block` không chỉ đơn giản là một đoạn code giữa `do` và `end`, mà trong bài này ta còn được biết đến `yield`, nó cho phép bạn chèn các đoạn code vào bất cứ đâu trong phương thức. Điều đó có nghĩa là bạn có một phương thức mà có nhiều cách hoạt động khác nhau giống như có nhiều phương thức. ## Tài liệu tham khảo [Mastering ruby blocks in less than 5 minutes](https://mixandgo.com/blog/mastering-ruby-blocks-in-less-than-5-minutes)