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 言語で実装することは良くあることである.

準備

Cコンパイラ

Ruby のコンパイルに用いたものと同じものが必要となる.バイナリパッケージ を導入した場合は,どのコンパイラでビルドされたものか知る必要があるが, UNIX 系ではたいてい gcc が使われているので,gcc パッケージをインストールしておけば良い.

ruby.h, mkmf.rb

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 通りの方法で完成させよ.完成後に,それぞれの計算時間を測り,比較した結果を報告せよ.