Tìm hiểu về Metaprogramming trong Ruby
Người viết: Hiếu Hoàng Trọng Metaprogramming – là một khái niệm trừu tượng. Ta tạm hiểu nó là kỹ thuật viết “những đoạn code mà sinh ra những đoạn code khác” trong thời gian chạy của chương trình. Kỹ thuật này được sử dụng trong nhiều ngôn ngữ lập trình phổ biến thông ...
Người viết: Hiếu Hoàng Trọng
Metaprogramming – là một khái niệm trừu tượng. Ta tạm hiểu nó là kỹ thuật viết “những đoạn code mà sinh ra những đoạn code khác” trong thời gian chạy của chương trình. Kỹ thuật này được sử dụng trong nhiều ngôn ngữ lập trình phổ biến thông qua các khái niệm: Reflection trong C và Java, Homoiconicity trong Lisp , …
Nó là một khái niệm rất khó hiểu, nhưng mình sẽ cố gắng trả lời 2 câu hỏi:
Với ngôn ngữ Ruby, metaproramming hoạt động như thế nào? Lợi ích của nó là gì?
Trong bài viết lần này, mình sẽ nói về 3 hành động liên quan đến meta programming trong Ruby:
- Định nghĩa class và method để tạo ra các class và method khác.
- Mở và sửa lại các class đã định nghĩa.
- Sử dụng method #send và #define_method để tránh lặp lại code trong 1 số bài toán.
Định nghĩa 1 class có đơn giản chỉ tạo ra 1 class?
Singleton method và singleton class trong ruby.
Trong ruby, mọi thứ đều là đối tượng. Một đối tượng có thể gọi ra các instance_method được định nghĩa trong class của nó, ví dụ:
1 2 3 4 5 6 7 8 9 10 |
class Person def hello p "Xin chao moi nguoi" end end hieu = Person.new.hello #=> "Xin chao moi nguoi" |
Ngoài ra, chúng ta có thể định nghĩa các method mà chỉ một đối tượng duy nhất có thể sử dụng, ví dụ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Person def hello p "Xin chao moi nguoi" end end hieu = Person.new.hello #=> "Xin chao moi nguoi" def hieu.xinchao p "Xin chao bang singleton_method xinchao" end hieu.xinchao #=> "Xin chao bang singleton_method xinchao" |
Method xinchao mà mình định nghĩa ở trên được gọi là singleton_method . Các singleton_method được lưu bởi singleton_class và hãy nhớ rằng:
Trong Ruby, một đối tượng được tạo ra cùng với singleton_class duy nhất của nó.
Singleton class này được dùng để lưu các singleton_method.
Singleton class không thể khởi tạo instance của nó. (nó không được định nghĩa method new )
Ta có thể chứng minh sự tồn tại của singleton_class với đối tượng hieu như sau:
1 2 3 4 5 6 7 8 9 10 |
class Person end hieu = Person.new => #<Person:0x000055e8ec07eb28> hieu.class => Person hieu.singleton_class => #<Class:#<Person:0x000055e8ec07eb28>> |
Và nói singleton_method được gói trong singleton_class cũng cần có bằng chứng:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Person def hello p "Xin chao moi nguoi" end end hieu = Person.new.hello #=> "Xin chao moi nguoi" def hieu.xinchao p "Xin chao bang singleton_method xinchao" end hieu.class.instance_methods false #=> [:hello] hieu.singleton_class.instance_methods false #=> [:xinchao] |
Bạn thấy đấy, đoạn code trên nói rằng method :xinchao là instance_method của hieu.singleton_class.
Từ đầu ví dụ, mình luôn sử dụng method new để khởi tạo instance của 1 class:
1 2 3 4 5 6 |
Class Person end hieu = Person.new => #<Person:0x000055e8ec07eb28> |
Chúng ta đều biết, method new được gọi là class method. Nó là một method được tạo ngay khi chúng ta khởi tạo 1 class và được gọi trực tiếp từ class . Vậy câu hỏi là:
Các class method giống như Person#new được lưu ở đâu?
Với việc hiểu về 2 khái niệm singleton_method và singleton_class , mình sẽ giải thích về bản chất thật sự của khái niệm class_method.
Class method chính là singleton method
Mình nhắc lại : “Mọi thứ trong Ruby đều là đối tượng”. Các class cũng vậy. Ví dụ:
1 2 3 4 5 6 7 |
Class Person end Person.class #=> Class |
class Person ở đây cũng chỉ là một đối tượng – một thể hiển của lớp Class.Và như đã nói ở phần 1.1 :
Trong Ruby, một đối tượng được tạo ra cùng với singleton_class duy nhất của nó.
Vậy Person cũng có singleton_class của nó:
1 2 3 4 5 6 7 |
Class Person end Person.singleton_class # => #<Class:Person> |
Vậy chúng ta cũng có thể định nghĩa singleton_method của Person.
1 2 3 4 5 6 7 8 9 10 11 |
class Person end def Person.dance p "Let your body move!" end Person.dance # => "Let your body move!" |
Và những method nhưmethod Person.dance được gọi là class method . Như vậy, sau khi hiểu về khái niệm singleton_method, chúng ta có thể hiểu về class methodnhư sau:
Class method chính là singleton_method của đối tượng class
Tạo một class chính là meta programming
Cái gọi là “code sinh ra code” luôn được thực hiện vô hình khi chúng ta sử dụng ngôn ngữ Ruby. Với phần giải thích 1.1 và 1.2 chúng ta đều thấy rằng đoạn code sau đây không đơn giản chỉ là khởi tạo class Person.
Nó còn tạo ra singleton_class và 1 loạt các class method của Person:
1 2 3 4 5 6 7 8 |
class Person end Person.singleton_class # => #<Class:Person> Person.singleton_class.instance_methods # => [:allocate, :superclass, :new, :<=>, :<=, :>=, :==, :===, :included_modules, :include?, :name, :ancestors, :attr, :attr_reader, :attr_writer, :attr_accessor, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?, :class_variables, :remove_class_variable, :class_variable_get, :class_variable_set, :class_variable_defined?, :freeze, :inspect, :private_constant, :public_constant, :const_missing, :deprecate_constant, :include, :singleton_class?, :prepend, :module_exec, :module_eval, :class_eval, :remove_method, :<, :>, :undef_method, :class_exec, :method_defined?, :alias_method, :to_s, :private_class_method, :public_method_defined?, :private_method_defined?, :protected_method_defined?, :public_class_method, :instance_method, :public_instance_method, :define_method, :autoload, :autoload?, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_set, :protected_methods, :instance_variables, :instance_variable_get, :private_methods, :public_methods, :public_send, :method, :public_method, :singleton_method, :define_singleton_method, :extend, :to_enum, :enum_for, :=~, :!~, :eql?, :respond_to?, :object_id, :send, :display, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :yield_self, :then, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :equal?, :!, :instance_exec, :!=, :instance_eval, :__id__, :__send__] |
Theo nghĩa đen, đây rõ ràng là code sinh ra rất nhiều code trong thời gian chạy của trình thông dịch.
Đến đây, các bạn có lẽ đã hiểu hơn một chút về cái gọi là metaprogramming. Ở phần tiếp theo, mình sẽ giải thích khái niệm thực sự có ích khi nào?.
Khởi tạo, sửa lại các class và method ở bất cứ đâu
Một trong những lợi ích của metaprogramming là bạn có thể khởi tạo và sửa lại các method bất kỳ lúc nào. Ví dụ, nếu mình muốn định nghĩa lại class_method #new của class Person chỉ cần làm đơn giản như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Person end Person.new # => #<Person:0x00005629d932c658> def Person.new p "Tao 1 person mới nè!" end Person.new # => "Tao 1 person mới nè!" |
Với cách làm này, chúng ta có thể sửa các class_method ở bất kỳ đâu, thậm chí là ở ngoài phần code định nghĩa class. Với instance_method, ta cũng có thể làm điều tương tự với method class_eval.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Person def hello p "Xin chao" end end Person.new.xinchao # => "Xin chao" Person.class_eval do def xinchao p "Van la xin chao nhung duoc dinh nghia lai!" end end Person.new.xinchao # => "Van la xin chao nhung duoc dinh nghia lai!" |
Bình thường, với các class được tích hợp sẵn trong ruby như Array, String,…. chúng ta thường không biết nó được lưu ở đâu trong sourecode. Tuy vậy, với ứng dụng của meta programming, chúng ta có thể sửa lại các class này ở bất kỳ file nào trong sourecod.
Ví dụ:
1 2 3 4 5 6 7 8 9 10 |
Array.class_eval do def vi_length "Mảng này gồm: " + length.to_s + " phần tử " end end [1,1,1].vi_length => "Mảng này gồm: 3 phần tử " |
Viết code không bị lặp (DRY)
Định nghĩa các method lặp với #define_method
Với trường hợp chúng ta cần định nghĩa những instance_method như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Person def hello_dad p "Hi Dad!" end def hello_mom p "Hi Mom!" end def hello_sister p "Hi Sister!" end end |
Chúng ta thấy rằng phần code định nghĩa 3 method Person#hello_dad, Person#hello_mom, Person#hello_sister có nhiều điểm tương đồng.
Chúng ta có thể viết ngắn lại như sau:
1 2 3 4 5 6 7 8 9 |
class Person ["mom", "dad", "sister"].each do |member| define_method "hello_#{member}" do p "Hi #{member.capitalize}!" end end end |
Với method #define_method của class Module, chúng ta có thể định nghĩa các methodlặp một cách ngắn gọn.
Ma thuật của method #send
Method #send của class Object được dùng như sau:
1 2 3 4 5 6 |
Array.new 3, 3 # => [3, 3, 3] Array.send "new", 3, 3 # => [3, 3, 3] |
Nó đơn giản là một trick để bạn biến phần tên của method khi gọi thành parameters dạng String hoặc Symbol . Có nghĩa là với hàm #send, phần tên gọi của method cũng có thể truyền vào được. Điều này có ích trong 1 số bài toán. Ví dụ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class Person def initialize name @name = name end def hello if @name == "dad" hello_dad elsif @name == "mom" hello_mom elsif @name == "sister" hello_sister else p "Hello" end end def hello_dad p "Hi Dad!" end def hello_mom p "Hi Mom!" end def hello_sister p "Hi Sister!" end end Person.new("dad").hello # => "Hi Dad!" |
Với method #send, mình sẽ viết method Person#hello gọn lại như thế này:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class Person def initialize name @name = name end def hello if !@name puts "Hello" else send "hello_#{@name}" end end def hello_dad p "Hi Dad!" end def hello_mom p "Hi Mom!" end def hello_sister p "Hi Sister!" end end Person.new("dad").hello # => "Hi Dad!" |
Gọi và định nghĩa đồng thời một method chưa tồn tại
Trong ruby, Kernel#method_missing là method sẽ được gọi khi ta chạy một method chưa được định nghĩa:
1 2 3 4 |
"Hieu".method_nay_chua_dinh_nghia # => NoMethodError (undefined method `method_nay_chua_dinh_nghia' for "
Có thể bạn quan tâm
0
|