Ruby のクラス作成チュートリアル
Ruby で定義
作業ディレクトリの準備
$ mkdir ~/test $ cd test
クラス定義の最も簡単な方法は,1 つのファイル内に全部書くことである.
$ vi main.rb 1 # クラス定義.先頭に書く. 2 class Greeter 3 def hello #メソッド 4 puts "Hello World" 5 end 6 end 7 8 #インスタンス作成 9 greeter = Greeter.new 10 11 # 繰り返し hello メソッドを実行 12 while true 13 greeter.hello 14 sleep 1 15 end
実行.止めるときは Ctrl-c を押す.
$ ruby main.rb
クラス定義を別ファイルで行う
クラス定義は別ファイルに分けることができる. クラス定義は greeter.rb に以下のように書く.
$ vi greeter.rb 1 # クラス定義. 2 class Greeter 3 def hello #メソッド 4 puts "Hello World" 5 end 6 end
メインプログラムでは,先頭でクラスの定義ファイルを require する.
$ vi main.rb 1 # クラスの定義ファイルの読み込み 2 require "./greeter" 3 4 #インスタンス作成 5 greeter = Greeter.new 6 7 # 繰り返し hello メソッドを実行 8 while true 9 greeter.hello 10 sleep 1 11 end
実行.ruby コマンドの引数としてメインプログラム (main.rb) のみ与えれば良い. なお,止めるときは Ctrl-c を押す.
$ ruby main.rb
C 言語で定義 (Ruby 拡張ライブラリ)
Ruby のモジュールやクラスを C 言語で書くことができる. 高速に処理したい部分を C 言語で実装することは良くあることである.
準備
Ruby のコンパイルに用いたものと同じものが必要となる.バイナリパッケージ を導入した場合は,どのコンパイラでビルドされたものか知る必要があるが, UNIX 系ではたいてい gcc が使われているので,gcc パッケージをインストールしておけば良い.
Ruby をソースコードから自分でビルドした場合はインストールされているが, バイナリパッケージを導入した場合は ruby.h や mkmf.rb があるか確認する必 要がある.Debian (Linux) では,ruby.h や mkmf.rb は開発用パッケージで提供されてい るので,apt install コマンドで ruby-dev パッケージをインストールすること.
$ sudo apt update $ sudo apt install ruby-dev
利用する関数
モジュールを定義する場合
モジュールを作成するには以下の関数を用いる.
VALUE rb_define_module(const char *name);
- 引数はモジュールの名前
- VALUE 型構造体は Ruby のオブジェクトを格納している構造体
C 言語の関数を Ruby のモジュール関数として登録するにはいくつかの方法があるが, 以下が最も簡単である.
void rb_define_module_function (VALUE module, const char *name, VALUE (*func)(), int argc);
- 第 1 引数:関数を追加するモジュールを格納したVALUE型構造体
- 第 2 引数:Ruby におけるメソッド名
- 第 3 引数:実際に呼び出す C 言語の関数を与える.関数の型は VALUE 型.
- 第 4 引数:呼び出す関数の引数の数.self というオブジェクトが第 1 引数として渡されるため、 ラッパー関数に実際に渡される引数の数はこれより 1 つ多くなる.
クラスを定義する場合
モジュールを作成するには以下の関数を用いる.これはクラス super の下位クラス name を作成するという意味である.
VALUE rb_define_class(const char *name, VALUE super);
- 第 1 引数:モジュールの名前
- 第 2 引数:上位クラス
- VALUE 型構造体は Ruby のオブジェクトを格納している構造体
C 言語の関数を Ruby のクラスメソッドとして登録する.
void rb_define_method (VALUE class, const char *name, VALUE (*func)(), int argc);
- 第 1 引数:関数を追加するクラスを格納した VALUE 型構造体
- 第 2 引数:Ruby におけるメソッド名
- 第 3 引数:実際に呼び出す C 言語の関数を与える.関数の型は VALUE 型.
- 第 4 引数:呼び出す関数の引数の数.self というオブジェクトが第 1 引数として渡されるため、 ラッパー関数に実際に渡される引数の数はこれより 1 つ多くなる.
C と Ruby の間で型変換するためのマクロや関数の例
- FIX2INT:RubyのIntegerオブジェクト -> int型
- NUM2INT:Rubyの任意のオブジェクト -> int型
- INT2FIX:31bit以内に収まるint型 -> RubyのIntegerオブジェクト
- INT2NUM:整数 -> RubyのInteger/Bignumオブジェクト
- NUM2DBL:Rubyの任意のオブジェクト -> double型
- rb_float_new(float):float型 -> RubyのFloatオブジェクト
拡張ライブラリ作成の具体例 (1) : "Hello World"
準備
一番簡単な "Hello world" プログラム (hello.c) で Ruby からの呼び出し方を練習する. まずは hello.c を作成してみる.
$ mkdir ~/ruby-ext-1 $ cd ~/ruby-ext-1 $ vi hello.c #include <stdio.h> void main() { printf("Hello, world!\n"); }
作成したら,実行して動作確認する.
$ gcc hello.c $ ./a.out Hello, world!
"Hello World" プログラムの拡張ライブラリ化 (1) : モジュール版
"Hello World" プログラム (hello.c) を拡張ライブラリ化する. ここでは,C 言語で Test という名のモジュールを定義し, そこに hello というメソッドを加えることにする. hello メソッドの中身を前述の hello.c と同様な内容とする.
C 言語でモジュールを定義する場合は,以下のようにプログラム (hello.c) を書き換える.
$ vi hello.c 1 #include "ruby.h" 2 3 /* C 言語での "Hello World"*/ 4 void c_hello(){ 5 printf("Hello, world!\n"); 6 } 7 8 /* ラッパー関数 */ 9 VALUE ruby_hello(VALUE self){ 10 c_hello(); 11 return Qnil; 12 } 13 14 /* 初期化 */ 15 void Init_test(){ 16 VALUE module = rb_define_module("Test"); 17 rb_define_module_function(module, "hello", ruby_hello, 0); 18 }
- 1 行目 : ruby.h のインクルードは必須
- 4-6 行目:Hello World プログラムの本体.
- 9-12 行目:ラッパー関数
- 関数の型は常に VALUE
- 関数の引数の型は常に VALUE.第一引数の名前は self にしておくこと.
- 11 行目: 今は特に何も戻り値が必要でないので,Rubyの「nil」に 対応する「Qnil」という定数を与えている.
- 16-18 行目:ライブラリの初期化関数
- Ruby はライブラリを読み込んだらまず 「Init_ライブラリ名」 という関数を実行するため,今回は Init_test とする.
- 関数の型は常に void
- rb_define_module でモジュール名を定める
- Ruby ではモジュール名の先頭は大文字にするので Test とする (一方で,ファイル名などは全部小文字で良いが).
- rb_define_module_function でメソッド名と,メソッド名に対応するラッパー関数名を登録する.
この hello.c プログラムをコンパイルする.Ruby には拡張ライブラリをコン パイルするための Makefile を作る仕組みがあるので,それを利用する. そのために,まず,必要なソースコードを 1 つのディレクトリにまとめ, 同じディレクトリに extconf.rb という名前のファイルを作る.
$ vi extconf.rb require "mkmf" create_makefile("test")
この extconf.rb では test という名前で拡張ライブラリを作るための Makefile を作成するように指示している. 次に,Makefileを作るために extconf.rb を実行し,さらに make する.
$ ls extconf.rb hello.c $ ruby extconf.rb $ ls Makefile extconf.rb hello.c $ make $ ls Makefile extconf.rb hello.c hello.o test.so
make した結果として,extconf.rb 内で test を作るよう指定しているので, test.so が作成される.これを Ruby から実行してみる. そのためのファイル main.rb を作成するが,test.so を使うために Ruby スクリプトの最初で require している.
$ vi main.rb require "./test" Test.hello $ ruby main.rb Hello, world! (<-- 無事に出力された)
"Hello World" プログラムの拡張ライブラリ化 (2) : クラス版
"Hello World" プログラム (hello.c) を拡張ライブラリ化する. ここでは,C 言語で Test という名のクラスを定義し, そこに hello というメソッドを加えることにする. hello メソッドの中身を前述の hello.c と同様な内容とする.
C 言語でクラスを定義する場合は,以下のようにプログラム (hello.c) を書き換える.今回はラッパー関数内で Hello, World を出力するようにしている.
$ vi hello.c 1 #include "ruby.h" 2 3 /* C 言語での "Hello World". ラッパー関数内で定義 */ 4 VALUE ruby_hello(VALUE self){ 5 printf("Hello, world!\n"); 6 return Qnil; 7 } 8 9 /* 初期化 */ 10 void Init_test(){ 11 VALUE class = rb_define_class("Test", rb_cObject); 12 rb_define_method(class, "hello", ruby_hello, 0); 13 }
- 1 行目 : ruby.h のインクルードは必須
- 4-7 行目:ラッパー関数
- 関数の型は常に VALUE
- 関数の引数の型は常に VALUE.第一引数の名前は self にしておくこと.
- 11 行目:今は特に何も戻り値が必要でないので,Rubyの「nil」に 対応する「Qnil」という定数を与えている.
- 10-13 行目:ライブラリの初期化関数
- Ruby はライブラリを読み込んだらまず 「Init_ライブラリ名」 という関数を実行するため,今回は Init_test とする.
- 関数の型は常に void
- rb_define_class でクラス名を定める
- Ruby ではクラス名の先頭は大文字にするので Test とする (一方で,ファイル名などは全部小文字で良いが).
- rb_define_method でメソッド名と,メソッド名に対応するラッパー関数名を登録する.
この hello.c プログラムをコンパイルする.Ruby には拡張ライブラリをコン パイルするための Makefile を作る仕組みがあるので,それを利用する. そのために,まず,必要なソースコードを 1 つのディレクトリにまとめ, 同じディレクトリに extconf.rb という名前のファイルを作る.
$ vi extconf.rb require "mkmf" create_makefile("test")
この extconf.rb では test という名前で拡張ライブラリを作るための Makefile を作成するように指示している. 次に,Makefileを作るために extconf.rb を実行し,さらに make する.
$ ls extconf.rb hello.c $ ruby extconf.rb $ ls Makefile extconf.rb hello.c $ make $ ls Makefile extconf.rb hello.c hello.o test.so
make した結果として,extconf.rb 内で test を作るよう指定しているので, test.so が作成される.これを Ruby から実行してみる. そのためのファイル main.rb を作成するが,test.so を使うために Ruby スクリプトの最初で require している.モジュール版と異なり,最初にインスタンス obj を作成している.
$ vi main.rb require "./test" obj = Test.new obj.hello $ ruby main.rb Hello, world! (<-- 無事に出力された)
拡張ライブラリ作成の具体例: 数の足し算
ここでは,以下のような2 つの数の足し算を行う C プログラムを Ruby の拡張ライブラリ化する.
int add(int a, int b){ return( a + b ); }
準備として,作業ディレクトリを作成しておく.
$ mkdir ~/ruby-ext-2 $ cd ~/ruby-ext-2
足し算プログラムの拡張ライブラリ化 (1) : モジュール版
足し算プログラムを拡張ライブラリ化する. ここでは,C 言語で Test という名のモジュールを定義し, そこに add というメソッドを加えることにする.
C 言語でモジュールを定義する場合は,以下のような プログラム (add.c) を作成する.
$ vi add.c 1 #include "ruby.h" 2 3 int add(int a, int b){ 4 return( a + b ); 5 } 6 7 VALUE wrap_add(VALUE self, VALUE aa, VALUE bb){ 8 int a, b, result; 9 10 a = FIX2INT(aa); 11 b = FIX2INT(bb); 12 result = add(a,b); 13 return INT2FIX(result); 14 } 15 16 void Init_test(){ 17 VALUE module = rb_define_module("Test"); 18 rb_define_module_function(module, "add", wrap_add, 2); 19 }
- 1 行目 : ruby.h のインクルードは必須
- 3-5 行目:足し算プログラムの本体.
- 7-14 行目:ラッパー関数
- 関数の型は常に VALUE
- 関数の引数の型は常に VALUE.第一引数の名前は self にしておくこと.
- データの変換が行われていることに注意.Ruby では数値を含めすべてのものがオブジェクトとして扱われており,個々のオブジェクトは C の構造体として実装されている.そのため,C 言語で書いたライブラリと数値のやりとりをするには,構造体から数値を取り出したり,数値を構造体に入れる必要がある.整数型や浮動小数点型は簡単に相互変換するマクロ、関数があるので,それを利用する.
- 16-19 行目:ライブラリの初期化関数
- Ruby はライブラリを読み込んだらまず 「Init_ライブラリ名」 という関数を実行するため,今回は Init_test とする.
- 関数の型は常に void
- rb_define_module でモジュール名を定める
- Ruby ではモジュール名の先頭は大文字にするので Test とする (一方で,ファイル名などは全部小文字で良いが).
- rb_define_module_function でメソッド名と,メソッド名に対応するラッパー関数名を登録する.
この add.c プログラムをコンパイルする.Ruby には拡張ライブラリをコン パイルするための Makefile を作る仕組みがあるので,それを利用する. そのために,まず,必要なソースコードを 1 つのディレクトリにまとめ, 同じディレクトリに extconf.rb という名前のファイルを作る.
$ vi extconf.rb require "mkmf" create_makefile("test")
この extconf.rb では test という名前で拡張ライブラリを作るための Makefile を作成するように指示している. 次に,Makefileを作るために extconf.rb を実行し,さらに make する.
$ ls extconf.rb add.c $ ruby extconf.rb $ ls Makefile extconf.rb add.c $ make $ ls Makefile extconf.rb add.c add.o test.so
make した結果として,extconf.rb 内で test を作るよう指定しているので, test.so が作成される.これを Ruby から実行してみる. そのためのファイル main.rb を作成するが,test.so を使うために Ruby スクリプトの最初で require している.
$ vi main.rb require "./test" sum = Test.add(1,2) puts "1 + 2 = #{sum}" $ ruby main.rb 1 + 2 = 3 (<-- 無事に出力された)
足し算プログラムの拡張ライブラリ化 (2) : クラス版
足し算プログラムを拡張ライブラリ化する. ここでは,C 言語で Test という名のクラスを定義し, そこに add というメソッドを加えることにする.
C 言語でクラスを定義する場合は,以下のような プログラム (add.c) を作成する. 今回はラッパー関数内で足し算を行っている.
$ vi add.c 1 #include "ruby.h" 2 3 VALUE wrap_add(VALUE self, VALUE aa, VALUE bb){ 4 int a, b, result; 5 6 a = FIX2INT(aa); 7 b = FIX2INT(bb); 8 result = a + b ; //直接計算 9 return INT2FIX(result); 10 } 11 12 void Init_test(){ 13 VALUE class = rb_define_class("Test", rb_cObject); 14 rb_define_method(class, "add", wrap_add, 2); 15 }
- 1 行目 : ruby.h のインクルードは必須
- 3-10 行目:ラッパー関数
- 関数の型は常に VALUE
- 関数の引数の型は常に VALUE.第一引数の名前は self にしておくこと.
- データの変換が行われていることに注意.Ruby では数値を含めすべてのものがオブジェクトとして扱われており,個々のオブジェクトは C の構造体として実装されている.そのため,C 言語で書いたライブラリと数値のやりとりをするには,構造体から数値を取り出したり,数値を構造体に入れる必要がある.整数型や浮動小数点型は簡単に相互変換するマクロ、関数があるので,それを利用する.
- 12-15 行目:ライブラリの初期化関数
- Ruby はライブラリを読み込んだらまず 「Init_ライブラリ名」 という関数を実行するため,今回は Init_test とする.
- 関数の型は常に void
- rb_define_class でクラス名を定める
- Ruby ではクラス名の先頭は大文字にするので Test とする (一方で,ファイル名などは全部小文字で良いが).
- rb_define_method でメソッド名と,メソッド名に対応するラッパー関数名を登録する.
この add.c プログラムをコンパイルする.Ruby には拡張ライブラリをコン パイルするための Makefile を作る仕組みがあるので,それを利用する. そのために,まず,必要なソースコードを 1 つのディレクトリにまとめ, 同じディレクトリに extconf.rb という名前のファイルを作る.
$ vi extconf.rb require "mkmf" create_makefile("test")
この extconf.rb では test という名前で拡張ライブラリを作るための Makefile を作成するように指示している. 次に,Makefileを作るために extconf.rb を実行し,さらに make する.
$ ls extconf.rb add.c $ ruby extconf.rb $ ls Makefile extconf.rb add.c $ make $ ls Makefile extconf.rb add.c add.o test.so
make した結果として,extconf.rb 内で test を作るよう指定しているので, test.so が作成される.これを Ruby から実行してみる. そのためのファイル main.rb を作成するが,test.so を使うために Ruby スクリプトの最初で require している.また,モジュール版と異なり,最初にインスタンス obj を作成している.
$ vi main.rb require "./test" obj = Test.new sum = obj.add(1,2) puts "1 + 2 = #{sum}" $ ruby main.rb 1 + 2 = 3 (<-- 無事に出力された)
参考
課題
数の繰り返しの足し算など,計算時間を要するプログラムについて,(1) Ruby 単体で書く場合,(2) 計算部分を C 言語で書き,それを Ruby の拡張ライブラリにした場合, の 2 通りの方法で完成させよ.完成後に,それぞれの計算時間を測り,比較した結果を報告せよ.