Ruby Metaprogramming Is Even Cooler Than It Sounds (1/2)

Bạn có thể thường nghe rằng metaprogramming là những thứ chỉ có "Ruby ninjas" sử dụng, và không đơn giản đối với những người mới bắt đầu với ruby. Nhưng sự thật thì metaprogramming không phải là thứ đáng sợ đến như vậy. Bài viết này sẽ thay đổi suy nghĩ đó để khoảng cách giữa metaprogramming và số đông những lập trình viên ruby trở nên gần hơn và chúng ta đề có thể khai thác những điều thú vị từ metaprogramming đang chờ đón.

Metaprogramming

Metaprogramming là một kỹ thuật code writing code, từ chính đoạn code chúng ta viết lại tự viết những đoạn code khác một cách tự động khi biên dịch chương trình. Điều này có nghĩa là bạn có thể định nghĩa phương thức hay class trong quá trình biên dịch. Metaprogramming có thể sửa đổi các class, tạo ra các phương thức nếu chúng chưa tồn tại trong khi biên dịch, hay viết code giúp tránh được vi phạm DRY và còn nhiều hơn thế nữa.

The basics

Chúng ta hãy hiểu rõ những kiến thức cơ bản trước khi đi sâu vào tìm hiểu về metaprogramming. Hãy bắt đầu bằng một ví dụ, bạn có lẽ đã đoán được đoạn code dưới đây làm gì:

class Developer
  def self.backend
    "I am backend developer"
  end

  def frontend
    "I am frontend developer"
  end
end

Một class được định nghĩa với hai phương thức. Phương thức thứ nhất là class method và phương thức còn lại là instance method (có thể hiểu đơn giản class method là phương thức được gọi từ chính class đó và instance method phải gọi qua một thực thể của class đó). Đây có thể là những đoạn code rất đơn giản bạn viết hằng ngày, nhưng đằng sau nó lại ẩn chứa những điều mà chúng ta cần phải hiểu rõ trước khi chúng ta tìm hiểu sâu hơn. Điều đáng chú ý ở đây đó là class Developer cũng chính là một object. Trong Ruby mọi thứ đều là một đối tượng nào đó, bao gồm cả class. Class Developer là một thực thể của class Class. Dưới đây là mô hình object của Ruby giúp chúng ta hiểu hơn

toptal-blog-image-1446120487914-384fae8f419347d455a43dab6e20cf25.jpg

p Developer.class # Class
p Class.superclass # Module
p Module.superclass # Object
p Object.superclass # BasicObject

Một điều quan trọng đó là ý nghĩa của self. Phương thức frontend là regular method, phương thức được sử dụng bởi các thực thể của class Developer, nhưng tại sao backend lại là class method. self luôn luôn tham chiếu tới một object nhưng đối tượng đó có thay đổi dựa trên code được thực thi. Ví dụ như trong một định nghĩa class, self tham chiếu tới chính nó hay là một thực thể của class Class.

class Developer
  p self
end
# Developer

Trong thực thể method, self tham chiếu tới một đối tượng của class đó.

class Developer
  def frontend
    self
  end
end

p Developer.new.frontend
# #<Developer:0x2c8a148>

Trong class method, self tham chiếu tới chính class đó theo một cách nào đó (sẽ được nhắc đến trong phần dưới bài viết)

class Developer
  def self.backend
    self
  end
end

p Developer.backend
# Developer

Vậy class method là gì? Trước khi trả lời câu hỏi này chúng ta phải nhắc đến một khái niệm khác được gọi là metaclass, được biết đến như cốt lõi của class và eigenclass. Phương thức của class frontend chúng ta định nghĩa phía trên không là gì cả nhưng lại là một thực thể method được định nghĩa trong metaclass cho đối tượng Developer. Một metaclass là những cơ bản mà Ruby tạo ra và đưa vào trong hệ thống cấu trúc kế thừa để lưu lại các phương thức của class, do đó không có quan hệ với thực thể mà được tạo ra từ class đó.

Metaclasses

Mỗi một đối tượng trong Ruby để có riêng metaclass của riêng nó. Nó là cái vô hình với lập trình viên nhưng nó luôn tồn tại và bạn có thể sử dụng nó rất dễ dàng. Và như vậy Developer là một đối tượng thuần và nó có metaclass của riêng nó. Ví dụ sau đây sẽ tạo ra một đối tượng của class String và điểu khiển metaclass của object đó:

example = "I'm a string object"

def example.something
  self.upcase
end

p example.something
# I'M A STRING OBJECT

Chúng ta vừa mới thêm vào một singleton phương thức something cho một đối tượng. Sự khác biệt giữa class method và singleton method đó là class method thì được gọi bởi mội thực thể của class đó còn singleton method thì chỉ được gọi bởi một thực thể duy nhất mà nó được thêm vào. Class method thì có phạm vi rộng rãi trong khi singleton thì ngược lại, nhưng cả hai kiểu đều được thêm vào một metaclass của một đối tượng.

Ví dụ ngay trên có thể được viết lại giống như sau:

example = "I'm a string object"

class << example
  def example.something
    self.upcase
  end
end

Cú pháp là khác nhau nhưng kết quả thì giống nhau. Bây giờ hãy quay lại ví dụ trước nơi mà chúng ta khởi tạo class Developer và viết lại theo cú pháp khác để định nghĩa một class method:

class Developer
  def self.backend
    "I am backend developer"
  end
end

Đây là định nghĩa căn bản mà hầu hết mọi người sử dụng.

def Developer.backend
  "I am backend developer"
end

Và đây cũng là đoạn code tương tự, chúng ta định nghĩa class method cho Developer. Chúng ta không sử dụng self nhưng có kết quả tương tự.

class Developer
  class << self
    def backend
      "I am backend developer"
    end
  end
end

Lần nữa chúng ta định nghĩa một class method, nhưng cú pháp giống như chúng ta đã từng định nghĩa một singleton method cho một đối tượng String ở ví dụ trên. Bạn có thể để ý rằng chúng ta sử dụng self ở ví dụ này thay vì trỏ tới thực thể Developer.

class << Developer
  def backend
    "I am backend developer"
  end
end

Bằng cách định nghĩa một block như trên, chúng ta làm cho self trỏ tới metaclass của Developer cho toàn bộ block. Do đó kết quả là chúng ta đã thêm backend vào metaclass của Developer thay vì chính class đó.

Hình ảnh sau sẽ cho chúng ta thấy ý nghĩa của metaclass trong đồ thị thừa kế:

toptal-blog-image-1446133438006-7199ada81b1807be9b8a73ab6c3ce1c3.jpg

Như bạn có thể thấy trong những ví dụ trước, chúng ta không có bằng chứng thực sự về sự tồn tại của metaclass. Nhưng chúng ta có thể bằng cách nào đó thấy được sự tồn tại của class vô hình này:

class Object
  def metaclass_example
    class << self
      self
    end
  end
end

Nếu chúng ta định nghĩa một thực thể method trong Object class( chúng ta có thể làm việc này bất cứ lúc nào và bất cứ class nào - đó là một thứ rất hay của metaprogramming), chúng ta sẽ có một tham chiếu self trỏ tới đối tượng Object bên trong nó. Chúng ta có thể sử dụng cú pháp class << self để thay đổi con trỏ của self tới metaclass của đối tượng hiện thời.

Như vậy chúng ta đã có thể hiểu được các khái niệm cơ bản cần thiết về class, các method, tham chiếu self, metaclass. Hẹn gặp lại các bạn ở bài viết sau sẽ phần nào cho thấy được metaprogramming thực sự là gì và sử dụng chúng ra sao.

Tham khảo Ruby Metaprogramming


All Rights Reserved