12/09/2019, 14:13

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 ...

Meta Programming 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 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.
  Tìm hiểu về Meta programming trong Javascript
  6 thay đổi trong ruby 2.7 có thể bạn đã bỏ qua

Đị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ụ:

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ụ:

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:

Và nói singleton_method được gói trong singleton_class cũng cần có bằng chứng:

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:

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ụ:

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ó:

Vậy chúng ta cũng có thể định nghĩa singleton_method của Person.

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:

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:

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.

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ụ:

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:

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:

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:

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ụ:

Với method #send, mình sẽ viết method Person#hello gọn lại như thế này:

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: