2016年9月8日から10日にかけて開催されたRubyKaigi 2016で「バインディング開発者を増やしたい!」という話をしました。
関連リンク:
話の流れは次の通りです。
-
バインディングについて説明
-
バインディングを作る方法として以下のを4つを紹介
-
拡張ライブラリー
-
SWIG
-
Ruby FFI
-
GObject Introspection
-
-
GObject Introspectionをオススメ
-
バインディング開発者になろう!
「バインディング」とは「Cで実装された機能をRubyから使うためのライブラリー」です。話している中で参加者に「バインディングを知っているか」聞いたところ、参加者の半分以上は「バインディング」について知りませんでした。「バインディング開発者」を増やすには「バインディング」に関する情報提供から頑張る必要がありそうです。
当日話し忘れたことがあります。たしかにGObject Introspectionがオススメなのですが、場合によっては他の方法が適切なケースもあるので、ケースバイケースで適切な方法を使えばよいです。「GObject Introspectionを使うバインディング開発者」を増やしたいのではなく、「バインディング開発者(やり方は問わない)」を増やしたいのです。
バインディングの作り方
発表中でのバインディングを作る方法はざっくりとした説明でした。雰囲気はわかるけどこの情報だけでは実際に作れるわけではないというものでした。これは、詳細まで説明するとそれぞれの方法の比較から意識が離れてしまうから、という判断でした。資料を用意していましたがあえて省略しました。しかし、このままだとバインディング開発者が増えにくいので、実際に作れるようになる情報をまとめます。
拡張ライブラリー
拡張ライブラリーはCで実装されたRubyライブラリーのことです。バインディングを作るために使われることが多いです。おそらく、バインディングを知らない人は拡張ライブラリーのことも知らないでしょう。バインディングと合わせて拡張ライブラリーに関する情報提供も頑張るとよさそうです。
拡張ライブラリーでバインディングを作る方法を説明する前に拡張ライブラリーを作る方法を説明します。次のRubyで書かれたライブラリーを拡張ライブラリーとして実装するとします。
class Hello
def to_s
"Hello"
end
end
この拡張ライブラリーを作るときに用意するものは次の2つで、どちらもこの発表のスライドを管理しているリポジトリーに入っています。
hello.c
は実装で次のようになります。コメントで解説を書いています。
/* Rubyが提供する拡張ライブラリーを作るためのAPIを使うため。 */
#include <ruby.h>
/* Hello#to_sの実体。 */
/* VALUEはRubyのオブジェクトのCでの表現。
すべてのメソッドはRubyのオブジェクトを返すので、Cでの実装ではVALUEを返す。 */
static VALUE
hello_to_s(VALUE self)
{
/* rb_str_new_cstr()はCの文字列からRubyのStringオブジェクトを作るAPI。 */
return rb_str_new_cstr("Hello");
}
/* 初期化関数。 */
/* 「hello.so」を「require」すると、Rubyは「Init_hello」を呼ぶ。 */
void
Init_hello(void)
{
VALUE hello;
/* 「class Hello」に対応。 */
hello = rb_define_class("Hello", rb_cObject);
/* 「def to_s」に対応。最後の「0」は引数が「0」という意味。 */
rb_define_method(hello, "to_s", hello_to_s, 0);
}
拡張ライブラリーを作ったことがない人でもCがわかればなんとなく読めますよね。
これをビルドするためのMakefile
を作るのがextconf.rb
です。
# Makefileを作るための便利ライブラリーを使う。
require "mkmf"
# 「hello.so」を作るMakefileを生成。
create_makefile("hello")
簡単ですね。
このhello.c
とextconf.rb
を用意すると次のようにビルドできます。
% ruby extconf.rb
% make
これを使うには次のようにします。
% irb -I . --simple-prompt
>> require "hello" # 「hello.so」を読み込む。このときにInit_hello()が呼ばれる。
=> true
>> hello = Hello.new
=> #<Hello:0x00000001cf2438>
>> hello.to_s # Cで実装したhello_to_s()が呼ばれる。
=> "Hello"
Rubyで書いた実装と同じように使えますね。
それでは拡張ライブラリーとしてバインディングを実装する方法を説明します。
次のhello.h
のようなAPIのCライブラリーのバインディングを作成することにします。
#pragma once
typedef struct hello_t Hello;
/* コンストラクター */
Hello *hello_new (void);
/* デストラクター */
void hello_free (Hello *hello);
/* メソッド */
const char *hello_message(Hello *hello);
このライブラリーのバインディングを拡張ライブラリーとして作るときに用意するものは次の2つで、どちらもリポジトリーに入っています。
このバインディングはRubyで書くと次のようなクラスを実現します。
class Hello
def initialize
@message = "Hello"
end
def message
@message
end
end
hello.c
は実装で次のようになります。コメントで解説を書いています。前述の「バインディングではない拡張ライブラリー」との大きな違いはライブラリー対象のHello
構造体をラップしているかどうかです。増えているコードの多くはこのためのコードです。
/* Rubyが提供する拡張ライブラリーを作るためのAPIを使うため。 */
#include <ruby.h>
/* バインディング対象のライブラリーのAPIを使うため。 */
#include <hello.h>
/* ラップしている`Hello`構造体を開放する関数。 */
static void
rb_hello_free(void *data)
{
Hello *hello = data;
/* ライブラリーが提供しているデストラクターを呼ぶ。 */
hello_free(hello);
}
/* Rubyにどのような構造体をラップしているかを伝えるための情報。 */
static const rb_data_type_t rb_hello_type = {
"Hello",
{
NULL,
rb_hello_free, /* ラップしている構造体を開放する関数。↑で定義。 */
NULL,
},
NULL,
NULL,
RUBY_TYPED_FREE_IMMEDIATELY,
};
/* 構造体をラップするオブジェクトを新しく作る関数。 */
/* Hello.newの中で暗黙的に呼ばれる。 */
static VALUE
rb_hello_alloc(VALUE klass)
{
/* ↑の「どのような構造体をラップしているか」情報を使ってオブジェクトを生成。 */
/* この時点ではラップ対象の構造体はまだ存在しないため最後の引数はNULL。 */
return TypedData_Wrap_Struct(klass, &rb_hello_type, NULL);
}
/* Hello#initializeの実装。 */
static VALUE
rb_hello_initialize(VALUE self)
{
Hello *hello;
/* ラップ対象の構造体をコンストラクターを使って生成。 */
hello = hello_new();
/* ラップ対象の構造体を設定。 */
DATA_PTR(self) = hello;
return Qnil;
}
/* Hello#messageの実装。 */
static VALUE
rb_hello_message(VALUE self)
{
Hello *hello;
const char *message;
/* ↑の「DATA_PTR(self) = hello」で指定した「hello」を取得。 */
/* ↑の「どのような構造体をラップしているか」情報は引数チェックのために利用。 */
TypedData_Get_Struct(self, Hello, &rb_hello_type, hello);
/* ライブラリーが提供しているメソッド用の関数を呼び出す。 */
message = hello_message(hello);
/* 結果(Cの文字列)をRubyのオブジェクトに変換して返す。 */
return rb_str_new_cstr(message);
}
/* 初期化関数。 */
/* 「hello.so」を「require」すると、Rubyは「Init_hello」を呼ぶ。 */
void
Init_hello(void)
{
VALUE hello;
/* 「class Hello」に対応。 */
/* 親クラスが「rb_cData」にすることがポイント。 */
hello = rb_define_class("Hello", rb_cData);
/* 構造体をラップするオブジェクトを新しく作る関数を
Hello.newの中で暗黙的に呼ばれるようにする。 */
rb_define_alloc_func(hello, rb_hello_alloc);
/* 「def initialize」に対応。最後の「0」は引数が「0」という意味。 */
rb_define_method(hello, "initialize", rb_hello_initialize, 0);
/* 「def message」に対応。最後の「0」は引数が「0」という意味。 */
rb_define_method(hello, "message", rb_hello_message, 0);
}
量が増えているのは構造体をラップするためです。この状態から新しくメソッドを追加する場合に増えるコード量は「バインディングではない拡張ライブラリー」とほとんど変わりません。そのため、最初だけコードが多くなりますが、その後はそれほど多くなりません。
これをビルドするためのMakefile
を作るextconf.rb
は次のようになります。バインディング対象のlibhello.so
を検出するためのコードが増えています。
# Makefileを作るための便利ライブラリーを使う。
require "mkmf"
# libhello.soとhello.hを見つけるための引数を受けつける。
# --with-libhello-XXXという引数を指定できるようになる。
dir_config("libhello")
# hello.hを探す。
exit(false) unless have_header("hello.h")
# libhello.soを探す。「hello_new」があるかもチェックする。
exit(false) unless have_library("hello", "hello_new")
# 「hello.so」を作るMakefileを生成。
create_makefile("hello")
このhello.c
とextconf.rb
を用意すると次のようにビルドできます。バインディング対象のlibhello.so
の実装はlibhelloとしてリポジトリーに入っています。hello.h
は/tmp/local/include/hello.h
にインストールされ、ビルドされたlibhello.so
は/tmp/local/lib/libhello.so
にインストールされているとします。
% ruby extconf.rb --with-libhello-dir=/tmp/local
% make
これを使うには次のようにします。
% LD_LIBRARY_PATH=/tmp/local/lib irb -I . --simple-prompt
>> require "hello" # 「hello.so」を読み込む。このときにInit_hello()が呼ばれる。
=> true
>> hello = Hello.new # rb_hello_initialize()経由でhello_new()を呼ぶ。
=> #<Hello:0x000000018feca0>
>> hello.message # rb_hello_message()経由でhello_message()を呼ぶ。
=> "Hello"
Rubyのリポジトリーには拡張ライブラリーを作り方を説明したdoc/extension.ja.rdocが入っているので、このドキュメントも参照すると捗るでしょう。
SWIG
SWIGはラッパーを生成するツールです。Ruby専用のツールではありませんが、拡張ライブラリーとして実装されたRubyのバインディングを生成することができます。
SWIGは.i
ファイル(「i」は「interface」の「i」)を入力としてCのソースを出力します。.i
ファイルの中にはどのようにバインディングを生成するかという情報を記述します。
前述のhello.h
のようなAPIのCライブラリーのバインディングを作成する場合は次のようなhello.i
になります。
/* Helloモジュール以下にクラスやメソッドを定義する。 */
%module hello
%{
/* 出力したCのソースでhello.hをinlcudeする。 */
#include <hello.h>
%}
/* SWIGがhello.hをパースして関数を検出してバインディングを生成する。 */
/* 「#」includeではなく「%」includeなことに注意。 */
%include <hello.h>
これでhello_wrap.c
という名前の2000行強のCプログラムが生成されます。このhello_wrap.c
をビルドするためのMakefile
を作るextconf.rb
は次のようになります。hello.i
からhello_wrap.c
を生成するためのコードが増えています。
# Makefileを作るための便利ライブラリーを使う。
require "mkmf"
# libhello.soとhello.hを見つけるための引数を受けつける。
# --with-libhello-XXXという引数を指定できるようになる。
# swigコマンドにヘッダーファイルがある場所を指定するので
# dir_configの戻り値にinclude_dirと名前を付けている。
include_dir, library_dir = dir_config("libhello")
# hello.hを探す。
exit(false) unless have_header("hello.h")
# libhello.soを探す。「hello_new」があるかもチェックする。
exit(false) unless have_library("hello", "hello_new")
# この時点ではまだhello_wrap.cが存在しないため、
# mkmfはソースとして認識できない。そのため、手動で設定する。
$srcs = ["hello_wrap.c"]
# 「make clean」時に削除される設定も手動で行う。
$cleanfiles << "hello_wrap.c"
# 「hello.so」を作るMakefileを生成。
create_makefile("hello")
# Makefileにはhello.iからhello_wrap.cを生成するルールがないので追記する。
File.open("Makefile", "a") do |makefile|
makefile.puts(<<-MAKEFILE)
hello_wrap.c: hello.i
# -rubyを指定してRuby用のコードを生成する。
swig -Wall -I"#{include_dir}" -ruby -o $@ $<
MAKEFILE
end
このhello.i
とextconf.rb
とSWIGを用意すると次のようにビルドできます。バインディング対象のlibhello.so
の実装はlibhelloとしてリポジトリーに入っています。hello.h
は/tmp/local/include/hello.h
にインストールされ、ビルドされたlibhello.so
は/tmp/local/lib/libhello.so
にインストールされているとします。
% ruby extconf.rb --with-libhello-dir=/tmp/local
% make
これを使うには次のようにします。
% LD_LIBRARY_PATH=/tmp/local/lib irb -I . --simple-prompt
>> require "hello" # 「hello.so」を読み込む。
=> true
>> hello = Hello.hello_new # hello_new()を呼ぶ。
=> #<SWIG::TYPE_p_hello_t:0x00000002c703d8 @__swigtype__="_p_hello_t">
>> Hello.hello_message(hello) # hello_message()を呼ぶ。
=> "Hello"
>> Hello.hello_free(hello) # hello_free()を呼ぶ。
=> nil
Cの関数をそのまま呼べるようなバインディングが自動生成されています。自動生成なので関数が多くても手間がかかりませんが、使い勝手はよくありません。hello.message
のように書けなかったり、GCにメモリーの解放を任せるのではなく手動でHello.hello_free(hello)
を実行しないといけないからです。
使いやすくする方法は2つあります。
1つはRubyで使いやすいラッパーを作る方法です。たとえば次のようなコードを用意します。ObjectSpace.define_finalizer
を使って自動的に解放するようにしているところとWrappedHello#message
でラップしているところがポイントです。
require "hello.so"
class WrappedHello
class << self
def finalizer(hello)
lambda do |id|
Hello.hello_free(hello)
end
end
end
def initialize
@hello = Hello.hello_new
ObjectSpace.define_finalizer(self, self.class.finalizer(@hello))
end
def message
Hello.hello_message(@hello)
end
end
次のように使います。
% LD_LIBRARY_PATH=/tmp/local/lib irb -I . --simple-prompt
>> require "hello" # 「hello.so」を読み込む。
=> true
>> hello = WrappedHello.new # ラップしたバージョンを使う。
=> #<WrappedHello:0x00000002c30418 @hello=#<SWIG::TYPE_p_hello_t:0x00000002c304b8 @__swigtype__="_p_hello_t">>
>> hello.message # ラップしたバージョンを使う。
=> "Hello"
もう1つの方法は.i
ファイルでオブジェクト指向なAPIにする方法です。たとえば、次のようなhello.i
にします。
/* Helloモジュール以下にクラスやメソッドを定義する。 */
%module hello
%{
/* 出力したCのソースでhello.hをinlcudeする。 */
#include <hello.h>
%}
/* Hello::Helloクラスを定義する。 */
typedef struct hello_t {
%extend {
/* Hello::Hello#initializeで呼ぶCの関数を定義する。 */
/* C++のコンストラクターと同じ構文。 */
hello_t() {return hello_new();}
/* Hello::HelloオブジェクトがGCされたときに呼び出す関数を定義する。 */
/* C++のデストラクターと同じ構文。 */
~hello_t() {hello_free($self);}
/* Hello::Hello#messageで呼ぶCの関数を定義する。 */
const char *message() {
return hello_message($self);
}
}
} Hello;
extconf.rb
は前述のものと同じです。そのためビルド方法も同じです。
% ruby extconf.rb --with-libhello-dir=/tmp/local
% make
使い勝手は次のようになります。オブジェクト指向なAPIになっています。
% LD_LIBRARY_PATH=/tmp/local/lib irb -I . --simple-prompt
>> require "hello"
=> true
>> hello = Hello::Hello.new
=> #<Hello::Hello:0x000000018d8c58 @__swigtype__="_p_hello_t">
>> hello.message
=> "Hello"
Ruby FFI
Ruby FFIはRubyでバインディングを作れるようにするライブラリーです。libffiを使って実現しています。
拡張ライブラリーでバインディングを実装するよりも非常に短い記述になります。前述のhello.h
のようなAPIのCライブラリーのバインディングは次のhello.rb
のようになります。
# Ruby FFIを読み込む。
require "ffi"
module LibHello
# このモジュールでRuby FFIを使う。
extend FFI::Library
# バインディング対象の共有ライブラリーを指定する。
ffi_lib "/tmp/local/lib/libhello.so"
# 「Hello *hello_new(void)」に対応。
attach_function :hello_new, [], :pointer
# 「const char *hello_message(Hello *hello)」に対応。
attach_function :hello_message, [:pointer], :string
# 「void hello_free(Hello *hello)」に対応。
attach_function :hello_free, [:pointer], :void
end
拡張ライブラリーと違ってビルドする必要はないのですぐに使えます。使い勝手は次のようになります。
% LD_LIBRARY_PATH=/tmp/local/lib irb -I . --simple-prompt
>> require "hello" # 「hello.so」を読み込む。
=> true
>> hello = LibHello.hello_new # hello_new()を呼ぶ。
=> #<FFI::Pointer address=0x000000012835f0>
>> LibHello.hello_message(hello) # hello_message()を呼ぶ。
=> "Hello"
>> LibHello.hello_free(hello) # hello_free()を呼ぶ。
=> nil
単純にSWIGを使った場合と同じくCの関数をそのまま呼べるだけなので使い勝手はよくありません。
使い勝手をよくするには次のようなコードを使ってラップします。SWIG用のラッパーと同じことをしています。
class Hello
def initialize
# GC時に自動で解放してくれる便利オブジェクトでラップ。
# 内部ではObjectSpece.define_finalizerを使っている。
@hello = FFI::AutoPointer.new(LibHello.hello_new,
LibHello.method(:hello_free))
end
def message
LibHello.hello_message(@hello)
end
end
このラッパーを使うと次のような使い勝手になります。
% LD_LIBRARY_PATH=/tmp/local/lib irb -I . --simple-prompt
>> require "hello" # 「hello.so」を読み込む。
=> true
>> hello = Hello.new # ラップしたバージョンを使う。
=> #<Hello:0x000000031f03f8 @hello=#<FFI::AutoPointer address=0x000000031f7b40>>
>> hello.message # ラップしたバージョンを使う。
=> "Hello"
オブジェクト指向なAPIになりました。
GObject Introspection
GObject IntrospectionはCで実装されたライブラリーの言語バインディングを簡単に実装できるようにするライブラリー・ツール群です。SWIGと同じように様々な言語に対応したソフトウェアです。「Ruby専用」ではなく「Rubyでも使える」ソフトウェアです。
バインディング対象のライブラリーがGObject Introspectionに対応している場合(たとえばWebブラウザーライブラリーであるWebKitGTK+)はgobject-introspection gemを使うと次のようにすればバインディングができあがります。(このAPIは次回リリースの3.1.0から使えるようになる予定です。そのため、最新リリースの3.0.9では動きません。)
# gobject-introspection gemを読み込む書き方の1つ。短いバージョン。
require "gi"
# GObject Introspectionを使ってWebKitGTK+のバインディングを生成する。
# Moduleが返ってくるので「WebKit」という定数に設定。
WebKit = GI.load("WebKit2")
# ↑は以下の書き方のシンタックスシュガー。
# module WebKit
# end
# loader = GObjectIntrospection::Loader.new(WebKit)
# loader.load("WebKit2")
次のように使えます。自動生成なのに自然に使えるようになっていることがわかります。
% irb --simple-prompt
>> require "gi" # gobject-introspection gemを読み込み。
=> true
>> WebKit = GI.load("WebKit2") # WebKitGTK+のバインディングを作成。
=> WebKit
# WebKitGTK+のバインディングの動作を確認するためにGTK+ 3を利用。
# GTK+ 3はGUIを提供するライブラリー。
>> require "gtk3"
=> true
# GTK+ 3のウィジェットを表示するにはイベントループを回す必要がある。
# irb上でブロックせずにイベントループを回すために別スレッドでイベントループを実行。
>> Thread.new {c = GLib::MainContext.default; loop {sleep 0.01 unless c.iteration(false)}}
=> #<Thread:0x00000002172b98@(irb):4 run>
# ウィンドウを作成。
>> w = Gtk::Window.new
=> #<Gtk::Window:0x2d85d68 ptr=0x3f7c280>
# ウィンドウを表示。
>> w.show
=> #<Gtk::Window:0x2d85d68 ptr=0x3f7c280>
# Webブラウザーウィジェットを作成。
# GI.load("WebKit2")で作成したバインディング。
>> web_view = WebKit::WebView.new
=> #<WebKit::WebView:0x2d6ab30 ptr=0x3f87420>
# ウィンドウにWebブラウザーウィジェットを追加。
>> w << web_view
=> #<Gtk::Window:0x2d85d68 ptr=0x3f7c280>
# Webブラウザーウィジェットを表示。
>> web_view.show
=> #<WebKit::WebView:0x2d6ab30 ptr=0x3f87420>
# Webブラウザーウィジェットでページを表示。
# GI.loadが引数の処理を自動で実装していることを確認できる。
>> web_view.load_uri("http://rubykaigi.org/")
=> #<WebKit::WebView:0x2d6ab30 ptr=0x3f87420>
# 現在表示しているページのスクリーンショットを取得。
# 非同期で取得するのでブロックで処理完了時の処理を指定。
# GI.loadがenumの値を名前(:full_documentと:none)で指定できるような
# バインディングを生成している。1や2のように数値で指定しなくてもよい。
# GI.loadがブロックに対応したバインディングを生成している。
>> snapshot = nil; web_view.get_snapshot(:full_document, :none) {|_, result| snapshot = web_view.get_snapshot_finish(result)}
=> #<WebKit::WebView:0x2d6ab30 ptr=0x3f87420>
# スクリーンショットとして返ってきたオブジェクト(Cairo::ImageSurface)は
# GI.loadで作成されていないクラスのオブジェクトだが連携できている。
>> snapshot
=> #<Cairo::ImageSurface:0x00000002c6c850>
# ↑で:full_documentと指定していたものは
# WebKit::SnapshotRegion::FULL_DOCUMENTに相当する。
>> WebKit::SnapshotRegion::FULL_DOCUMENT
=> #<WebKit::SnapshotRegion full-document>
このようなオブジェクト指向なAPI、名前でenum
の値を指定する、ブロック対応(、この例では出てきていない例外)などに対応するためにはGObjectというCで実装されたオブジェクトシステムを利用する必要があります。(WebKitGTK+は利用しています。)バインディングを作りたいライブラリーがGObjectを利用していない場合はGObjectを使うようにしたラッパーを作成すればGObject Introspectionを使えるようになります。たとえば、GroongaはGObjectを使っていませんが、GObjectを使ったラッパーであるGroonga GObjectを使うと、GObject IntrospectionでGroongaのバインディングを使えます。
GObject Introspectionの重要なポイントは、共有ライブラリーだけでなく、共有ライブラリーのメタデータも利用するところです。メタデータとはたとえば関数のシグネチャー(名前・引数・戻り値の情報)です。Ruby FFIでは共有ライブラリーしか利用していないため関数のシグネチャーをバインディング開発者が指定(attach_function
で指定)していましたが、GObject Introspectionの場合はそれを利用できるのでバインディング開発者は指定する必要はありません。このため、自動でいい感じのバインディングを生成できるのです。
GObjectがGTK+用(もう少し言うとGIMP用)で利用するために開発されているライブラリーのため、GObjectを活用しているGObject IntrospectionもGUI用(GTK+はGUI用のライブラリー)の仕組みと勘違いする人がいます。しかし、GObjectはGUIに関係ない下回りの機能を提供するライブラリーなため、GUIを使わないライブラリーでも利用されています。たとえば、PDFを処理するPoppler、PNGやJPEGなどを含む様々な画像を統一的に処理する機能を提供するGdkPibuf、マルチメディアを処理するGStreamerなどはGUIを使わない(必須ではない)ライブラリーですが、GObjectを利用しています。つまり、GObject IntrospectionはGUIを使わないサーバー上でも活用できる仕組みです。
GObject Introspectionに対応したライブラリーの作り方はGObject Introspectionに対応したライブラリーの作り方を参考にしてください。今回の発表のためにlibgobject-helloとlibgobject-calcという実装も用意しました。これらも参考になるはずです。
実際に作ろうとしてつまづいた人はruby-gnome2/ruby-gnome2のissuesで相談してください。日本語で構いません。
まとめ
RubyKaigi 2016でバインディングの作り方を紹介し、バインディング開発者を増やそうとしました。バインディング開発者が増えるとRubyを活用できる場面が広がると考えているからです。
発表中ではバインディングの作り方の詳細を省略したのでここで補足しました。
Windowsでのバインディングのインストールを簡単にするための仕組みとしてfat gemというものがあり、fat gemの説明資料も用意していたのですが、今回のRubyKaigi中では説明する機会がありませんでした。いつか、機会があれば。。。Rubyの拡張ライブラリーやバインディングに特化したイベントを開催するときはぜひ声をかけてください。
発表中に参加者に「バンディングという概念を知っていたか?」と聞いたところ、参加者の半分以上は知らないということでした。「拡張ライブラリー」と合わせてバインディングの情報を提供していった方がRubyを活用できる場面が広がりそうです。