4-2 Lệnh kèm block

4-2 Lệnh kèm block

Block của Ruby có thể đưa ra lệnh rồi nhận argument từ lệnh và xử lý. Chương này sẽ giới thiệu cách xử dụng khối kết hợp lệnh và block

Block

Tại loop xử lý theo lệnh [times], sau [times] có đoạn từ [do~end] đây chính là block.

3.times do
  puts "Hello"
end

Ngoài [do~end], cách biểu thị của block còn 1 cách nữa. Đó chính là cách đó chính là xử dụng dấu {}.

3.times {
  puts "Hello"
}

Như vậy chúng ta có thể dùng {~} để tạo ra một block. Cách viết xử dụng dấu {} này rất hay xử dụng cho những block viết trong một dòng.

3.times {puts "Hello"}

Ngoài ra chúng ta cũng có thể viết [do~end] trong 1 dòng.

3.times do puts "Hello" end

Trong quyển sách này, những chương trình ví dụ, khi viết những block trong một dòng thì sẽ xử dụng {~}, còn nếu viết block phức tạp cần nhiều dòng thì sẽ dùng [do~end] Những lệnh có block thì người ta cũng có thể gọi là Iterator.

Lệnh [each] sử dụng trong dãy được giới thiệu sau đây cũng là một lệnh kèm block. [main_loop] của [MyGame] cũng nhận xử lý loop bằng block nên cũng là lệnh kèm block.

require 'mygame/boot'
main_loop do
	# thông tin xử lý
end

Argument của block

Trong block được đưa cho lệnh kèm block. chúng phải nhận giá trị đầu vào gọi là Argument của blcok. Ví dụ trong lệnh [times] thì chúng ta cũng có thể nhận được Argment của block.

3.times do |i|
  puts i
end

[Kết quả hiển thị]

0
1
2

Phần |i| chính là argument của block. i là biến số. Tại đây chúng ta có thể tự do viết tên biến số nào mình thích. Như vậy argument của blcok được viết bằng biến số bao bởi dấu || đừng ở đầu block. Biến số i đại diện cho các giá trị [0,1,2]

Về thứ tự thực hiện xử lý trả lại trong 3 lần lặp lại và những giá trị được i đại diện sẽ chạy theo dòng dưới đây.

3.times do |i|	# Tại loop đầu tiên, giá trị vào i là 0
  puts i
end
3.times do |i|	# Tại loop tiếp theo, giá trị vào i là 1
  puts i
end
3.times do |i|	# Tại loop thứ 3, giá trị vào i là 2
  puts i
end

Biến số đưa cho đầu vào của block chúng ta có thể đặt tên tùy theo sở thích.

3.times do |number|
  puts number
end

Những giá trị được thay thế bởi argument của block bắt đầu từ [0] đến giá trị mà lệnh times gọi ra. Tại ví dụ trên, số times gọi ra là 3 lần nên từ đó các số nguyên lần lượt được hiện ra.

1.times {|i| puts i}	  # => 0
3.times {|i| puts i}	  # => 0, 1, 2
100.times {|i| puts i}	# => 0~99

Hiển thị lần lượt những thành tố trong dãy

Trong Ruby, để thực hiện những xử lý lặp lại đối với những thành tố trong dãy, chúng ta được chuẩn bị một lệnh vô cùng tiện lợi đó chính là [each] Tuy nhiên, chúng ta cũng có thể không sử dụng lệnh [each] mà vẫn có thể lặp đi lặp lại dãy. Trước khi giới thiệu lệnh [each], chúng ta hãy thử nghĩ lại những cách không sử dụng lệnh [each] mà tiến hành xử lý lặp đi lặp lại.

Giả sử chúng ta muốn puts tất cả những gì có trong dãy thì chúng ta phải làm thế nào? Ví dụ, như dưới đây chúng ta muốn truy cập vào từng thành tố một, chúng ta cũng có thể dùng [puts]

a = ["Hello", "Hi", "Bye"]
puts a[0]
puts a[1]
puts a[2]

Như vậy chúng ta có thể biểu thị những thành phần có trong dãy. Tuy nhiên, khi số thành tố trong dãy tăng lên, nếu chúng ta vẫn sử dụng lệnh puts thì rất vất vả. Trong trường hợp đó chúng ta có một đối sách tiện lợi để sử dụng, đó chính là lệnh [times].

a = ["Hello", "Hi", "Bye"]
a.size.times do |i|
  puts a[i]
end

[a.size] đó chính là kích cỡ của dãy, có nghĩa kết quả trả lại là [3]. Lệnh [times] sẽ được thực hiện với số [3], có nghĩa là xử lý sẽ được lặp lại 3 lần.

Tuy nhiên chúng ta cũng có một cách sử dụng tiện lợi hơn dùng lệnh [times] đó chính là dùng lệnh [each].

Sử dụng each để lặp lại

Lệnh [each] là lệnh sử dụng để tiến hành lặp lại những xử lý đối với các thành tố trong dãy.

a = ["Hello", "Hi", "Bye"]
a.each do |e|
  puts e
end

[Kết quả hiển thị]

Hello
Hi
Bye

Trong chương trình này, giống như chương trình dùng [times] thì những thành tố trong dãy được hiển thị lần lượt. Block [do~end] được viết sau [each] sẽ được thực hiện lặp đi lặp lại. Lúc đó, argument của block |e| sẽ thay thế lần lượt những thành tố trong dãy và hiển thị. Bộ phận này được thực hiện lặp lại trong 3 lần.

puts e

Lúc này, tại e sẽ lần lượt được thay vào bằng những thành tố có trong dãy nên tất cả những thành tố trong dãy sẽ được hiển thị một cách lần lượt. Tuy nhiên. Trong ví dụ trên, chúng ta có thể giản lược biến số a.

["Hello", "Hi", "Bye"].each do |e|
  puts e
end

Trong khi làm block mà chúng ta sử dụng {~} thì câu lệnh của chúng ta còn trở nên gọn nhẹ hơn nữa.

["Hello", "Hi", "Bye"].each{|e| puts e}

Ý nghĩa của [i] và [e]?

Trong những chương trình ví dụ trên, tên biến số có trong block chúng ta đã dùng những chữ cái Alphabet [i] hay [e] để hiển thị. Ý nghĩa của những chữ cái này sẽ được giải thích như dưới đây.


i .......... là chữ cái đầu tiên của [index] - chỉ số e ......... là chữ cái đầu tiên của [element] - thành tố


[i] và [e] là những biến số biểu thị cho chỉ số và thành tố nên rất hay được sử dụng. Tất nhiên chúng ta cũng có thể sử dụng tên những gì mình thích nên kể cả khi sử dụng những tên ngoài [i] hay [e] thì lệnh cũng không bị lỗi.

Lặp lại bằng each_with_index

Chúng ta có thể dùng lệnh [each_with_index] để lặp lại lệnh đối với 2 block.

a = ["Hello", "Hi", "Bye"]
a.each_with_index {|e.i| puts "#{i}: #{e}"}

Lệnh [each_with_index] cũng gần giống như lệnh [each]. Điểm khác đối với lệnh [each] đó chính là điểm nó có thể nhận cả chỉ số vào trong block.

Block và [Array.new]

Khi chúng ta viết [Array.new] thì chúng ta có thể tạo dãy mới.

a = Array.new	# => giống như a = []
p a	# => []

[Array] chính là tên của class dãy. [new] là lệnh để gọi ra một Object mới từ class. Về chi tiết sẽ giải thích ở p.178 nên tại đây chúng ta chỉ cần hiểu đơn giản khi dùng [Array.new] thì chúng ta có thể tạo một dãy mới. Trong trường hợp mới tạo dãy mới bằng [Array.new], sau [new] chúng ta có thể viết số để chỉ kích thước của dãy.

p Array.new(5)	# => [nil, nil, nil, nil, nil]

Mặt khác, chúng ta cũng có thể thêm thành tố vào dãy luôn nhờ block.

p Array.new(5){0}	# => [0,0,0,0,0]

Hơn nữa, chúng ta cũng có thể viết như sau.

p Array.new(5){|i|i}	# => [0,1,2,3,4]

Nếu sử dụng chỉ số như thế này thì chúng ta cũng có thể viết ra những con Slime có kèm số đằng sau.

Slimes = Array.new(10) {|i|"Slime#{i}"}
Slime.each {|e| puts "#{e}đã hiện ra"}