Methods và block trong Ruby
I. Method Trước khi nói về methods tôi sẽ sơ lược một chút về object. object là gì: nó là một instance của class, trong nó có chứa instance variables và instance methods Ví dụ sau bạn sẽ thấy rõ: class Student def info @name = "I'm ...
I. Method
Trước khi nói về methods tôi sẽ sơ lược một chút về object.
-
object là gì: nó là một instance của class, trong nó có chứa instance variables và instance methods
Ví dụ sau bạn sẽ thấy rõ:
class Student def info @name = "I'm Ruby" end end
2.1.5 :010 > st.methods.grep /info/ => [:info] 2.1.5 :008 > st.instance_variables => [:@name]
Theo ví dụ ở trên thì stlà một object của class Student.
1. Instance method
- Là method của instance object, có thể gọi nó thông qua object. Như ví dụ trên ta có thể nói info là một method của st (hay chính xác hơn nó là một instance method của class Student).
2.1.5 :012 >Student.instance_methods.grep /info/ => [:info] 2.1.5 :013 > st.info => "I'm Ruby"
Nhưng không được nói info là một method của Student như vậy sẽ bị nhầm sang class method.
2.1.5 :015 > st.methods == Student.instance_methods => true 2.1.5 :016 > Student.methods.grep /info/ => []
object's method không "sống" trong object mà nó "sống" trong clas của object, chính vì thế mà nó được gọi là instance method của class.
2. Method Lookup
Trước khi đi vào method look ta cần hiểu về hai khái niệm receiver và ancestors chain
- receiver là gì? Đơn giản nó chỉ là một object và gọi được phương thức từ nó. Như trên thì st là một receiver
- ancestors chain: trước khi đi vào định nghĩa tôi có một ví dụ sau:
class Animal def weight puts "10kg" end end class Cat < Animal end
2.1.5 :025 > ca = Cat.new => #<Cat:0x000000022bec18> 2.1.5 :026 > ca.weight 10kg
Khi gọi ca.weight, ruby sẽ tìm trong class ca instance method weight nhưng không thấy, tiếp tục tìm đến superclass tức là class mà nó kế thừa (ở đây là Animal), sau khi tìm thấy nó sẽ dừng lại và thực thi method. Nhưng giả sử nó không tìm thấy ở class nó kế thừa Animal nó sẽ tiếp tục tìm lên superclass của class Animal là Object. Path từ object -> class -> superclass -> superclass... như vậy được gọi là ancestors chain
2.1.5 :033 > ca.class.ancestors => [Cat, Animal, Object, Kernel, BasicObject]
Vậy có thể kết luận như sau: Method lookup là việc tìm kiếm phương thức, Ruby sẽ tìm trong class của receiver và từ đó nó sẽ tìm method theo ancesstors chain cho đến khi nó tìm được phương thức cần gọi.
Ngoài ra ancesstors chain còn bao gồm cả module
module M def name "Reck" end end class Dog include M end
2.1.5 :034 > dg = Dog.new => #<Dog:0x0000000144a1f0> 2.1.5 :035 > dg.name => "Reck" 2.1.5 :036 > dg.class.ancestors => [Dog, M, Object, Kernel, BasicObject]
3. Thực thi Method
Sau khi đã tìm được phương thức theo ancesstor chain Ruby sẽ thực thi phương thức mà nó tìm được.
Khi gọi một method thì receiver sẽ trở thành self (curent object) tất cả các instance variables của receiver sẽ là instance của self, và tất cả các phương thức gọi không tường minh bên trong nó tương đương với việc gọi self.my_instance_method.
class MyClass def testing_self arg @var = 10 # 1 biến instance của self my_instance_method(arg) # giống như gọi self.my_instance_method(arg) end def my_instance_method arg @var = @var + arg end end
4. Dynamic Method
a. Gọi phương thức động
Thông thường khi gọi một phương thức bạn thường sử dụng object + dấu chấm + phương thức , ngoài ra thì có thể gọi phương thức thông qua cách khác đó là sử dụng phương thức send của Object Theo ví dụ MyClass trên ta có thể gọi được instance method của nó thông qua send
2.1.5 :010 > my = MyClass.new => #<MyClass:0x00000001cc2cd8> 2.1.5 :011 > my.testing_self 1 => 11 2.1.5 :012 > my.send(:my_instance_method, 10)
Tham số đầu tiên trong phương thức send là tên phương thức, tham số sau là tham số của phương thức. Phương thức send này rất hữu ích trong việc tránh trùng lặp code, làm cho code của bạn DRY hơn, để thấy được điều đó bạn xem ví dụ tiếp theo:
class Animal def action arg if arg == "run" self.run elsif arg = "eat" self.eat end end def run "Runing..." end def eat "Eating..." end end
Và giờ bạn refactoring nó với phương thức send
class Animal def action arg self.send(arg) end def run "Runing..." end def eat "Eating..." end end
Bạn thấy đó send đã giải quyết vấn đề if else làm cho code của bạn DRY hơn.
Chú ý thêm là send có thể nhận tên phương thức truyền vào theo hai kiểu string và symbol.
b. Định nghĩa một phương thức động
Bạn định nghĩa dynamic method phương thức define_method()
class Cat define_method :fingers do |arg| arg * 4 end end
2.1.5 :018 > ca = Cat.new => #<Cat:0x00000001aa9370> 2.1.5 :019 > ca.fingers 1 => 4
define_mehtod() được thực thi bên trong class Cat và nó được định nghĩa như một instance methods của class Cat, công nghệ định nghĩa phương thức tại thời điểm runtime như vậy được gọi là Dynamic Method.
class Animal def self.define_action name define_method(name) do "#{name.capitalize}ing..." end end define_action :run define_action :eat define_action :drink end
2.1.5 :021 > a = Animal.new => #<Animal:0x00000002caf470> 2.1.5 :022 > a.run => "Runing..." 2.1.5 :023 > a.eat => "Eating..."
define_method() được thực thi bên trong class Animal do đó để định nghĩa dynamic method trong một phức thức khác thì ta phải định nghĩa nó trong một class method. Việc định nghĩa dynamic method một cách hợp lý sẽ giúp code của bạn ngắn gọn hơn rất nhiều.
**5. Method missing **
Khi bạn gọi một phương thức, ruby sẽ tìm phương thức dựa trên ancesstor chain, nếu không tìm thấy phương thức nó sẽ gọi đến phương thức method_missing() và rais ra message NoMethodError,
2.1.5 :025 > a.walk NoMethodError: undefined method `walk' for #< Animal:0x00000002caf470>
Bạn có thể override lại phương thức method_missing để custom tùy theo ý mình
class Animal def method_missing(method, *args) puts "Method #{method} that you've called does not exist" end end
2.1.5 :040 > a = Animal.new => #<Animal:0x00000002b0c438> 2.1.5 :041 > a.lfkajds Method lfkajds that you've called does not exist
Giờ ta sẽ refactoring lại class Animal mà sử dụng define_method phía trên
class Animal def method_missing(method_name, *args) "#{method_name.capitalize}ing..." end end
2.1.5 :047 > a = Animal.new => #<Animal:0x00000002cb2f08> 2.1.5 :048 > a.eat => "Eating..."
Giờ bạn có thể gọi các phương thức drink, eat, run như bạn đã làm đối với define_method. Lúc này drink, eat, run ... được gọi là Ghost Method bởi nó chỉ được gọi ở phía người gọi mà bạn không tìm thấy nó được ở receiver.
Hãy so sánh số dòng code của class Animal ở hai cả 3 cách viết send, define_method và method_missing rõ ràng là method_missing đã ngắn hơn rất nhiều.
II. Block
Block là một phần của "callable object(bao gồm procs, lambda ...)", nó được định nghĩa với ngoặc xoắn hoặc keyword do .... end, bạn chỉ có thể định nghĩa một block khi bạn gọi phương thức và phương thức có thể gọi ngược trở lại block thông qua từ khóa yeild.
class Animal def super_animal arg1, arg2 yield(arg1, arg2) end end
2.1.5 :013 > a.super_animal(2, 2){|x, y| x*x + y*y} => 8
Nếu sử dụng yield trong method mà lúc gọi ta không định nghĩa block cùng method sẽ sinh ra lỗi.
2.1.5 :014 > a.super_animal(2, 2) LocalJumpError: no block given (yield)
def super_animal arg1, arg2 yield(arg1, arg2) if block_given? return "plz define your block" end
2.1.5 :024 > a.super_animal(2,3) => "plz define your block"
Tips:
2.1.5 :045 >arr = [1,2,3,4] => [1, 2, 3, 4] 2.1.5 :046 > arr.map(&:to_s) => ["1", "2", "3", "4"]
Việc gọi map bên trên tương đương với:
2.1.5 :009 > arr.map do |ar| 2.1.5 :010 > ar.to_s 2.1.5 :011?> end => ["1", "2", "3", "4"]
Proc
- Proc là một block, bạn có thể định nghĩa một proc với từ khóa Proc.new và kết hợp với block theo sau nó, nó là một anonymous functions
- Proc là block rồi tại sao lại cần đến nó là vì khi nào bạn muốn tái sử dụng block thì định nghĩa một proc và call nó khi nào cần thiết ngoài ra thì bạn cũng có thể sử dụng proc một cách trực tiếp ngay khi định nghĩa proc
2.1.5 :012 > pr = Proc.new{|name| "Hello #{name}" } => #<Proc:0x00000000f0f0a0@(irb):12> 2.1.5 :013 > pr.call("Ruby") => "Hello Ruby"
Lambda
- Lambda cũng giống như một proc
2.1.5 :014 > la = lambda{|x| "I'm a #{x}"} => #<Proc:0x00000000e93b30@(irb):14 (lambda)> 2.1.5 :015 > la.call("Lambda") => "I'm a Lambda"
Có 2 điểm khác biệt giữa proc và lambda
- Về tham số truyền vào: proc sẽ không check số tham số truyền vào ngược lại lambda thì có check:
2.1.5 :016 > la.call("Lambda", "San") ArgumentError: wrong number of arguments (2 for 1) from (irb):14:in `block in irb_binding' from (irb):16:in `call' from (irb):16 from /home/khanh/.rvm/rubies/ruby-2.1.5/bin/irb:11:in `<main>'
Trong khi đó không có lỗi sinh ra với proc
2.1.5 :017 > pr.call("aa", "bb") => "Hello aa"
Về return và giá trị trả về: Khi return trong proc thì mehod mà gọi proc sẽ dừng và trả về giá trị nhưng đối với lambda thì không khi gặp phải return nó sẽ trả về giá trị và method chứa nó vẫn tiếp tục chạy. Ví dụ dưới đây sẽ cho bạn thấy rõ điều đó
2.1.5 :001 > class Animal 2.1.5 :002?> def pr_ex 2.1.5 :003?> Proc.new{return "Proc val"}.call 2.1.5 :004?> puts "return value pr_ex" 2.1.5 :005?> end 2.1.5 :006?> def ld_ex 2.1.5 :007?> lambda { return "lambda" }.call 2.1.5 :008?> puts "return value ld_ex" 2.1.5 :009?> end 2.1.5 :010?> end => :ld_ex
a = Animal.new 2.1.5 :014 > a.pr_ex => "Proc val" 2.1.5 :015 > a.ld_ex return value ld_ex => nil
Như bạn thấy rõ ràng từ hai method trên thì đối với proc nó sẽ không chạy lệnh puts "return value pr_ex" nhưng lambda thì lại khác nó tiếp tục chạy dòng lệnh puts "return value ld_ex"