Sunday, May 29, 2005

เรียนรู้ yield ใน ruby

วันนี้ได้ลองเรียนรู้ ruby ในส่วนของ yield
ซึ่งเป็นส่วนที่สงสัยมานานแล้ว (ไม่รู้ว่ามันคืออะไรแน่)

ลองดูที่ code นี้
def three_times
yield
yield
yield
end

three_times {puts "hello"}

ผลลัพท์ทีได้
Hello
Hello
Hello


ส่วน {puts "hello"} เราเรียกว่า block
การที่เราเรียกใช้ method three_times
จะเกิดการส่ง block ไปด้วย แต่ยังไม่มีการ execute
จนกว่าจะมีการสั่ง yield จึงจะทำงาน

ที่นี้ลองดูโจทย์ประเภทหาค่า Fibonacci กันบ้าง
def fib_up_to(max)
i1, i2 = 1, 1
while i1 <= max
yield i1
i1, i2 = i2, i1 + i2
end
end
fib_up_to(1000) {|f| print f, " "}

ได้ผลลัพท์

1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

จะเห็นว่า block สามารถ declare parameter ได้
เพื่อให้ yield สามารถส่งค่า parameter ไปให้

scope ของ block ก็น่าสนใจทีเดียว
ลองดู testcase นี้
def test_yield1
i = 0
def x
yield
yield
yield
end

x {i = i + 1}
assert_equal(3, i)
end

จะเห็นว่าภายใน block สามารถอ้างถึง variable
ที่อยู่ภายนอกได้ รวมทั้งสามารถ modify ค่าได้ด้วย

ใน ruby เราจะเห็นว่ามีการใช้ block และ yield เต็มไปหมด
เช่นในเรื่องของ Array เราสามารถใช้ Enumeration#find
ในการค้นหาค่าที่ต้องการได้
b = [1, 3, 5, 7, 9].find {|v| v*v > 30}
assert_equal(7, b)


ซึ่งในการ implement feature find ของ ruby ทำได้ดังนี้
class Array 
def find
for i in 0...size
value = self[i]
return value if yield(value)
end
return nil
end
end


ลองดูตัวอย่าง method ของ array ที่ชื่อ inject บ้าง
b = [1, 3, 5, 7].inject {|sum, element| sum+element}
assert_equal(16, b)

inject จะ pass parameter ให้ block 2 ตัว
โดยเอาค่ามาจาก array ที่ index = 0 และ 1
จากนั้นผลลัพท์ที่ได้จาก yield ก็จะนำมาเป็น
parameter ตัวที่ 1 และนำค่าจาก array ตัวถัดไป
มาเป็น parameter ตัวที่ 2 ทำซ้ำไปเรื่อยๆจนหมด array
ผลลัพท์ที่ได้ในตัวอย่างก็คือ ค่า sum ของทุกค่าสมาชิกใน array นั่นเอง

ตัว block สามารถเก็บเป็นตัวแปรไว้ได้เช่นกัน
โดยใช้ method ที่ชื่อ lambda
จากนั้นก็เรียกใช้โดยผ่าน method call
def test_lambda 
def n_times(init)
return lambda {|n| init * n}
end

p1 = n_times(2)
assert_equal(6, p1.call(3))
assert_equal(8, p1.call(4))

end

Related link from Roti

2 comments:

bact' said...

ืทำแบบนี้ก็คือ yield นี่เหมือนกับ .. ส่งฟังก์ชั่นเป็นพารามิเตอร์ ?

polawat phetra said...

น่าจะเป็น spacial parameter
โดยมีชื่อเรียกว่า block
นิยามของ block ก็คือ
อยู่ท้าย message (อยู่หลัง param block)
แล้วก็เริ่มต้นด้วย { จบ ด้วย }