iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 14
0

現在大多數的人在入門程式語言的時候大多是從物件導向語言開始入門,而工作也是以使用物件導向語言為主。但是在 C 語言或者函數式語言中,並沒有「物件」的概念,又是如何保存狀態的呢?

透過前面幾篇文章的實作,大家應該都能猜到 mrb_state 就是其中的關鍵,我們稍微停下手邊的工作思考一下這個問題。

狀態

我們在使用物件導向語言的時候,跟函數式語言的差異是什麼呢?

class Car
  def initialize
    @running = false
  end
  
  def run
    @running = true
  end
  
  def stop
    @running = false
  end
  
  def running?
    @running == true
  end
end

在 Ruby 裡面我們使用了「實例變數」(Instance Variable)保存了 running 的狀態,因此我們可以透過方法的輔助去操作這個封裝的狀態,所以我們在設計物件的時候要考慮的是該封裝什麼跟他的職責是什麼。

我們切換到 Elixir 來處理的話,上面的程式碼則會變成這樣

defmodule Car do
  defstruct running: false, name: ''

  def run(car) do
    %{ car | running: true }
  end

  def stop(car) do
    %{ car | running: false }
  end

  def running?(car) do
    car.running == true
  end
end

跟 Ruby 不同的是我們每次操作的時候都會需要再把 car 傳遞進去,這跟我們在使用 mruby API 進行操作或處理,或者我們前面利用 mrb_state 保存 Method Table 的機制是很類似的,雖然函數式語言跟物件導向語言還有很多概念上的差異,不過在「狀態」的管理上我們可以從這一點看出來差異。

物件本身還有很多不同的特性,不過「狀態」是一個相對容易理解的概念,我們將狀態包裝成一個物件之後就能夠透過方法對「內部狀態」做處理,而不需要重新將要處理的資訊傳遞進去。

Method Table

除了狀態之外,我們還需要另一個資訊來告訴我們有哪些「方法」可以使用,在上一篇我們的實作中其實已經完成類似於 C 或者 Elixir 的機制,只要能找到一個方法並且把狀態傳遞進去,再回傳改變後的狀態就已經算是符合條件,但作為物件會有他自己的方法,也因此我們會需要類似像下面這樣的結構。

struct mrb_value {
  int value;
  struct kh_mt_s* mt;
}

value 紀錄這個物件(實體)所擁有的數值,並且指向一個 Method Table 來處理對應的操作。

舉例來說,我們在 Ruby 裡面有一個整數物件要被改變,我們會像這樣呼叫

value = 1
value.increment

在 C 語言裡面的處理,就會變成類似這樣

// OP_LOADSELF
reg[a] = ...

// OP_SEND
reg[a] = func(mrb, reg[a])

在 mruby 的處理中會先設置 OP_LOADSELF 的操作,將 value 變數找出來安排給 reg[a] 紀錄,然後呼叫 OP_SEND 的操作將 reg[a] 也就是當下的 self 傳遞給 C 定義的方法中作為參考。

我們會在後面實作,現階段我們先有一個概念。

不過在一個語言中並不會這個單純,因此 mrb_value 在 mruby 實作實際指向的並不是一個 Method Table 而是一個 RClass*(類別)的資訊,也就是說一個變數對 mruby 來說在 C 語言會有著這樣的資訊。

mrb_value
├── value_type
├── value
|    ├── void*
|    ├── ...
|    └── int
└── RClass*
     └── kh_mt_s*

當我們呼叫一個方法的時候,會依照他所屬的 RClass 去尋找對應的方法表,再回來對這個數值進行處理。

也因此在 mruby 去定義方法跟物件的時候會類似下面這樣

// 把 RClass* 登記到 `mrb_state` 裡面
RClass* app = mrb_define_class(mrb, "App");

// 把 mrb_func_t 登記到 `RClass*` 裡面
mrb_define_method(mrb, app, "start", mrb_start_app);

回到我們目前的實作,我們的 mrb_state 目前是這樣的狀態

typedef struct mrb_state {
  int exc; /* exception */
  struct kh_mt_s *mt;
} mrb_state;

如果用比較寬鬆的定義來看,他現在其實也可以被看作一個「物件」

小結

上面的說明可能還是相當模糊不清處的,我自己是在開發 mruby 的 Gem 的過程中反覆的閱讀原始碼,才慢慢能夠理解 mruby 是如何在 C 語言的基礎下,去設計出一個物件導向的語言。仔細了解的話,其實也會發現所謂的「實例變數」其實也就是一個 khash 的 Map 結構以 mrb_value 為單位作儲存,這些語言設計者運用各種資料結構和語言的特性做出的變化,是非常有趣的東西,深入瞭解後也能更加幫助自己在使用語言上能用更清晰的方式去考慮一些問題。

下一篇我們會開始改善原本實作的方法,將參數的機制導入。


上一篇
Day 13 - 實作方法(二)
下一篇
Day 15 - 方法呼叫資訊
系列文
拿到錘子的我想在微控制器上面執行 Ruby30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言