HanamiというRubyのWebフレームワーク向けにBlumaってCSSライブラリのラッパーgemを作ってみた
使い方は簡単で、Gemfileにhenami-blumaを追加するだけ。
あとは、<%= stylesheet 'bluma' %>のように使いたいテンプレートで呼び出すだけ
実装にあたっては以下のラッパーgemを参考にさせて頂きました。
この記事は最近僕が作っているRubyで型を使えるライブラリの紹介記事です。
対象読者としては、Rubyで簡単に型のようなものを使ってみたい人、またはC++でのRuby拡張を作ってみたい人を想定しています。
Tataraの紹介をしつつ、簡単なC++でのRuby拡張実装についても触れていきます。
TataraというRubyで簡単な型を使えるようにするRubyExtensionを作りました。RubyGemsにもリリースしており、v0.2.0が現在リリースされている最新バージョンになります。
v0.2.0まではRiceというRubyExtensionを簡単にC++で使用していました。しかし、GCへの管理追加が少しややこしかったり、RiceでC++のコードをRuby向けにラップしているため処理が遅くなったりするという問題がありました。そこで次期リリースではC++のみでの実装に書き直し、GCへの管理追加や処理速度向上を図っています。
ちなみに、次にリリースするv0.3.0は11月中にリリースする予定で実装を進めています。
例えば、Tataraを使うことで以下のようなコードを実行できます。
require 'tatara' @int_container = Tatara::IntVector.new (1..10).each{|i| @int_container << i} # Insert 1..10 to @int_container @int_container.map{|i| puts i # 1..10 value is shown }
@int_container
は整数値のみが保存できるVector
になります。浮動小数点数値なども代入できますが、整数値に暗黙的に変換されます。
@int_container = Tatara::IntVector.new @int_container << 4.2 # => Push 4
ちなみに、文字列を保存しようとするとエラーになります。
@int_container = Tatara::IntVector.new @int_container << "42" # => Error!
このようにTataraでは簡単な型が使えるようにしてあります。
Ruby自体は非常に書きやすく、楽しくコーディングができる素晴らしい言語です。
ですが、元々静的型付け言語(C/C++など)を触ることが多かったため「Rubyにも型のようなものが欲しいなぁ……」と思うこともありました。
そんな折、RiceやextppなどでRubyExtensionを作れそうだとわかり、「面白そうなので作ってみよう !」となり作ってみました。
開発初期のころはRiceでのRubyExtension作成についてドキュメントや記事などを漁りに漁っていました。で、Riceで実装できそうなことがわかったので少しづつ実装を進めていった感じですね。
最初はIntegerやFloatなどを実装し、その後VectorやMapなどを実装していきました。
v0.1.0をリリースした辺りでGCへの管理追加などがややこしいことが判明し、Riceを外す方法を模索し始めました。extppなどでのRubyExtension実装ついても調べたりしました。
結果として、Ruby自体の実装コードなどを読みつつ、以下の記事を参考にRiceを外していくことにしました。そのおかげでRubyの実装なども読むことができたので良い経験が積めたと思います。
Riceでは以下のようにC++で作成したクラスをラップしてRuby向けに使用できるようにできます。
#include <rice/Data_Type.hpp> #include <rice/Constructor.hpp> using namespace Rice; class Integer { int value = 0; public: Integer(); ~Integer(); int assignment(const int var); int return_value(); }; Integer::Integer() {} Integer::~Integer(){} int Integer::assignment(const int var) { return this->value = var; } int Integer::return_value() { return this->value; } extern "C" { void Init_tatara() { Module rb_mTatara = define_module("Tatara"); Data_Type<Integer> rbcInteger = define_class_under<Integer>(rb_mTatara, "Integer") .define_constructor(Constructor<Integer>()) .define_method("value", &Integer::return_value) .define_method("value=", &Integer::assignment); } }
RiceではC++で定義したクラスをData_Type
でRubyのクラスへとラップしてくれます。またモジュール名などはModule rb_mTatara = define_module("Tatara");
のように作成することができます。
Module rb_mTatara = define_module("Tatara"); Data_Type<Integer> rbcInteger = define_class_under<Integer>(rb_mTatara, "Integer")
上記のコードではTatara
モジュール内にInteger
クラスを作成しています。
.define_constructor(Constructor<Integer>())
またこのコードではC++のInteger
クラスのコンストラクタをnew
として利用しています。
あとは、以下のように必要なメソッドをdefine_method
でクラスに追加しています。
.define_method("value", &Integer::return_value) .define_method("value=", &Integer::assignment);
define_method
はメソッド名と実行するメソッドを引数として渡すことで、C++側で定義したメソッドをRuby側で使用することができます。
これにより、以下のようなRubyのコードが実行できます。
@integer = Tatara::Integer.new @integer.value = 42 puts @integer.value # => 42
このようにRiceを使うことでC++で定義したクラスをRubyで使用することができます。
ちなみに、テンプレートなどを使う場合は以下のように型を指定する必要があります。
Data_Type<CppArray<int>> rb_cIntArray = define_class_under<CppArray<int>>(rb_mTatara, "IntArray") .define_constructor(Constructor<CppArray<int>>());
先ほどRiceで書いていたコードは以下のように書き直すことができます。
#include <ruby.h> class Integer { int value = 0; public: Integer(); ~Integer(); int assignment(const int var); int return_value(); }; Integer::Integer() {} Integer::~Integer(){} int Integer::assignment(const int var) { return this->value = var; } int Integer::return_value() { return this->value; } static Integer *getInteger(VALUE self) { Integer *ptr; Data_Get_Struct(self, Integer, ptr); return ptr; } static void wrap_int_free(Integer *ptr) { ptr->~Integer(); ruby_xfree(ptr); } static VALUE wrap_int_alloc(VALUE klass) { void *p = ruby_xmalloc(sizeof(Integer)); p = new Integer; return Data_Wrap_Struct(klass, NULL, wrap_int_free, p); } static VALUE wrap_int_init(VALUE self) { Integer *p = getInteger(self); p = new Integer; return Qnil; } static VALUE wrap_int_return_value(VALUE self) { const int value = getInteger(self)->return_value(); VALUE result = INT2NUM(value); return result; } static VALUE wrap_int_assignment(VALUE self, VALUE value) { const int v = NUM2INT(value); const int r = getInteger(self)->assignment(v); VALUE result = INT2NUM(r); return result; } extern "C" { void Init_tatara() { VALUE mTatara = rb_define_module("Tatara"); VALUE rb_cInteger = rb_define_class_under(mTatara, "Integer", rb_cObject); rb_define_alloc_func(rb_cInteger, wrap_int_alloc); rb_define_private_method(rb_cInteger, "initialize", RUBY_METHOD_FUNC(wrap_int_init), 0); rb_define_method(rb_cInteger, "value", RUBY_METHOD_FUNC(wrap_int_return_value), 0); rb_define_method(rb_cInteger, "value=", RUBY_METHOD_FUNC(wrap_int_assignment), 1); } }
C++のクラスをそのままRuby側で呼び出すことはできないので、以下のような関数を作成し、Ruby側から呼び出せるようにしています。
static Integer *getInteger(VALUE self) { Integer *ptr; Data_Get_Struct(self, Integer, ptr); return ptr; } static void wrap_int_free(Integer *ptr) { ptr->~Integer(); ruby_xfree(ptr); } static VALUE wrap_int_alloc(VALUE klass) { void *p = ruby_xmalloc(sizeof(Integer)); p = new Integer; return Data_Wrap_Struct(klass, NULL, wrap_int_free, p); } static VALUE wrap_int_init(VALUE self) { Integer *p = getInteger(self); p = new Integer; return Qnil; }
モジュールはrb_define_module
で作成でき、以下のようにTatara
というモジュールを作成しています。
VALUE mTatara = rb_define_module("Tatara");
また、以下のコードではInteger
クラスをTatara
モジュール内に作成し、その基底クラスにObject
クラスを使用しています。
VALUE rb_cInteger = rb_define_class_under(mTatara, "Integer", rb_cObject);
Rubyのソースコード内ではVALUE
型を使うことでインスタンスの値などを取得することができています。
getIntegr
ではインスタンス自身からC++のクラスのポインタを取得し、そのポインタを使ってクラスで定義されているメソッドを実行しています。
例えば以下のようにreturn_value
メソッドを呼び出すことができます。
static VALUE wrap_int_return_value(VALUE self) { const int value = getInteger(self)->return_value(); VALUE result = INT2NUM(value); return result; }
またwrap_int_free
ではRubyのGCでメモリが解放される際の処理を実装しています。
static void wrap_int_free(Integer *ptr) { ptr->~Integer(); ruby_xfree(ptr); }
wrap_int_alloc
はRubyのコードで新しくnew
でインスタンスを作成した際のアロケーションを実装しています。
static VALUE wrap_int_alloc(VALUE klass) { void *p = ruby_xmalloc(sizeof(Integer)); p = new Integer; return Data_Wrap_Struct(klass, NULL, wrap_int_free, p); }
rb_define_alloc_func
でアロケーション時に実行する関数を指定できます。
rb_define_alloc_func(rb_cInteger, wrap_int_alloc);
wrap_int_init
はinitialize
を実装しています。
static VALUE wrap_int_init(VALUE self) { Integer *p = getInteger(self); p = new Integer; return Qnil; }
最後に、以下のようにC++で定義されたクラスのメソッドを実行するラップ関数を作成します。
static VALUE wrap_int_return_value(VALUE self) { const int value = getInteger(self)->return_value(); VALUE result = INT2NUM(value); return result; } static VALUE wrap_int_assignment(VALUE self, VALUE value) { const int v = NUM2INT(value); const int r = getInteger(self)->assignment(v); VALUE result = INT2NUM(r); return result; }
INT2NUM()
やNUM2INT()
はCとRubyとの間でデータの変換を行う処理になります。この処理を間に挟むことでRuby内の変数の値などをC++側に渡すことができます。
あとは、作成したラップ関数を以下のようにメソッドとして追加します。
rb_define_method(rb_cInteger, "value", RUBY_METHOD_FUNC(wrap_int_return_value), 0); rb_define_method(rb_cInteger, "value=", RUBY_METHOD_FUNC(wrap_int_assignment), 1);
rb_define_method
は第一引数にメソッドを追加するクラスのVALUE
を渡します。第二引数にはメソッド名、第三引数には実行する関数を渡します。
RUBY_METHOD_FUNC
では渡している関数ポインタをRubyのメソッドとしてキャストしています。
第四引数は、そのメソッドが受け取る引数の数になります。0
の場合は引数はなく、1
などの場合はその数だけ引数を受け取ります。なお、-1
の場合は可変長引数として受け取っているようです(RubyのArrayクラスの実装などで見る限り)。
このようにしてC++でRubyのクラスを作成することができます!
今回作成したTatara
の紹介をしつつ、C++でのRuby拡張の実装などについても触れてみました。普段使っているRubyの内部コードについても少し触れましたので、興味が湧いた方もおられるかもしれません。
なお、東京近隣の方であればRuby Hacking Challenge HolidayというRubyの実装などをRubyコミッターから直々に聴けるというイベントがあります!
ちなみに、次回は「Ruby 2.7新機能紹介」や「Rails Girls Tokyo, More! 参加者の皆様と合同LT大会」を行うようです。
Ruby Hack Challenge Holiday #9 Ruby 2.7 + 年末LT大会
この記事を読んでRubyの内部に興味を持った方は是非参加してみるといいでしょう。
ちなみに、島根県浜田市ではRuby Hacking Challenge in Hamada.rbをHamada.rb内で開催しています。
僕のほうで内部のコードなど解説を行いますので、興味のある方は是非ご参加ください(なお、わかる範囲になりそうですが……)
Ruby Hacking Challenge in Hamada.rb
今後もゆっくりとTatara
の実装を行いつつ、Ruby自体の実装への理解を深めていきたいと思います。
Mastodon v3.0.0rc1がBump up されたので早速追従した際の出くわしたエラーの話
対象読者としては、さくらのスタートアップスクリプトでMastodon鯖を構築した人らになるかな?
Mastodon v3.0.0rc1がBump upされたとのことでいつものようにmaster追従しようとしたところ
Error: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found
マイグレーション実行時に上記のエラーに出くわした。
調べてみるとMastodonで使用しているcharlock_holmesのバージョンが上がったっぽく、その影響で使用しているGCCのバージョンが変わったことが原因の模様
以前もcld3関連で同様のエラーに遭遇しており、「どうしたもんかなぁ……」と考えていた
そんな中、以下の記事を見つけ、最新のGCCをインストールすることでget kotonakiとなった
なので、もし同様のエラーで苦しんでいる鯖缶がいたら上記の記事を参考にGCCをインストールするとよいかもしれない
でも、以前のcld3の時のエラーも同様の解決方法したんだけどうまくいかなかったんだよね……。なんか手順間違えてたのやら……
CLI11というコマンド入力をよしなにしてくれるライブラリを使って、sol new などの必要ようなコマンドを作成
https://github.com/CLIUtils/CLI11
入力されたコマンドをパースさせて、作成したテンプレートをC++17で追加されたfilesystem使ってコピーできるようにした
sol server や sol buildなどの開発用のコマンドなども追加したね
並行して、最低限度動くサンプルをcpp-httplib/Stimulus/Svelteで作成したり
ORマッパーとかないのでよさげなライブラリを探しつつ、実装していこうかなぁって感じ
この記事は、Mastodon Golden Week Calendar の7日目の記事です
これまでとこれからの記事は下記URLより読むことができます
https://gw-advent.9wick.com/calendars/16
ちなみに、去年書いた記事はこちら
個人的にこの二年間で思ったことを徒然と書くだけです
えー、なんやかんやでCreatodonも無事2周年を迎えることができましたー
これも日頃の鯖缶の皆さんの知見であったり、ユーザーの皆様のおかげだと思います
この場を借りて感謝申し上げます
この二年でMastodonをめぐる環境ってかなり変わってきたよなーとか思いますねー
二コフレが鯖を閉鎖したり、JP鯖の管理者がぬるかるさんからきぼうソフトに移行したりと
でも、一ユーザーからしたらそんなことは大きな影響ではないんだよね
実際、ユーザー視点から言えば、他なサーバが止まったからといって何かがあるわけでもないしね(まあ、そのサーバ使ってるユーザーからすれば大問題だろうけども
ユーザーからすればサービスが安定して運営されてて、楽しければすべてOKなんだよね
だから、鯖缶がアレコレMastodonやFediverse全体のこととか話してても大して興味はないんじゃないかな?
というよりも、むしろ聞きたくないんじゃないかなぁと
テーマインスタンスとかだと、そのテーマについて話しがしたいのであって、Fediverseの話がしたいわけでもないしねー
まあ、そう言いつつ僕もアレコレ話をしているので矛盾してる感はあるんだけれどもねー
他なテーマインスタンスとかどうされてるのか気になるところですが、Creatodonは基本的に放置してます
まあ、自主性を重んじるというか、単に管理するのが面倒くさいというか……
一番は面倒くささと「管理されるの嫌だよなぁ……」という個人的な思いでしょうかね
こう話さねばならないとか七面倒だし、別に自由に呟けばいいんじゃないんですかね?とかは思う
なので、特にジャンルの規定とかはしないし、好きなように使っていただければとは思う
とはいえ、サーバ運営に影響する内容とかだと処置しなきゃいけないし
その辺の尺度は、まあユーザーさんの自主性に任せてます
後、別にCreatodon以外にもサーバはあるので合わないと感じたら別なサーバに引っ越ししてもいいと思うんですよねー
「最近、あんまり浮上してないなー」とかを気にしないでもらいたいですし、合わないと感じてもTwitter見たく他に行き場所がないとかもないわけですし
好きなところに行ける自由さはFediverseにあるので、その自由を行使すればいいんじゃないかな
個人的なところ、Fediverse全体は極端な人から柔軟な人まで幅広くいるカオスだと思ってる
で、「お一人鯖最高」の民もいれば、「LTL最高」の民もいる
その雑多なカオスな感じが好みではあるので、その状態を維持できれば面白そうだよなーとは思う
とはいえ、鯖缶の視点から行くと問題行為を働いたユーザーの話とかを呟いちゃったりするので完全なカオスとは言い難くなるかもだけれどね
あと時たまローカルルール的に「ブロックは負け」とかいう話も出ているけども別にいいんでない?
合わない人と何を話しても合うことはない時ってあるし、相手のためにも先にブロックしとけばいいんでない?
その辺も自由でいいんじゃないかと思ってる
今のFediverseは自由をうたってはいるけれど、一方ではローカルルール的なものを強いていたりとある種のユートピアさとディストピアさを重ねもった世界観かなぁと思う
ブレードランナーとかの感じに近いものを感じるので、個人的にはそういう感じも好きだけどねー
今後、MastodonなどのAPなSNSが流行することがあるかはわからないけど、あったとしてもそれは大したことではないと思う
ユーザーからすれば今までのTwitter見たくあれこれ知り合いと話しやすくなるだけで、管理人からすればユーザー数が増えるだけ
それに今はPleromaやMisskeyなどMastodon以外にもAPでつながることのできるSNSも存在している
だから、Mastodonだけにユーザーが一極集中するわけでもないし、あんまし気にしなくても良いんじゃないかなと思ってる
まあ、それは少人数のMastodonサーバだからの話で、大所帯のところとかは大変だろうけどねー
あと、Twitterがつぶれるとかなっても新しい他なSNSに移行するだろうしねー
まあ、何となくな感じでアレコレ書いて支離滅裂な感じもありますが、思ったことは大体書いたのでこれでいいかな
まあ、今後もゆるく気張らずにCreatodonの運営をやっていこうと思います
まあ、ハーモニーのトゥアレグ族のセリフにあるように「ほどほど」な感じでやっていこうと思いますー
たまたま東京に行っているときだったので、Ruby Hack Challenge Holiday というRuby自体のコードをHackしてみようというイベントに参加してきた!
Rubyのコミッター(Ruby自体の開発をされている人のこと)である笹田さんと遠藤さんのお二人がサポートについているという超豪華なイベントなのよね
以前、Twitterでこのイベントの記事をみたこともあって前から機会があれば参加したかったのよね
で、実際に参加してみた感じはいわゆるもくもく会に近いかな?
今日やってみたいこととしてRubyをHackしてみるという感じ
ちなみに僕はrubyhackchallengeという演習があるのでそれをやってた
使用している環境がWindowsだったのでHyper-VとDocker使ってコンテナ内でHackしてた(ちなみに、Dockerコンテナ使えばWindowsでもHackする環境ができるので、その辺の記事はまたQiitaかブログに上げとく予定)
予定の関係もあって一時間くらいしか参加できなかったんだけど、かなり濃ゆい経験ができたー
Hack自体としてはRubyのArrayクラスにsecondってメソッドを実装したりしてた
あとRubyKaigi2019のタイムテーブルが前日に発表されていることもあってRubyKaigi2019のタイムテーブル解説を聞けた!
いやぁ、機会があればぜひ次も参加したい!普段使っているRubyの内部のコードをコミッターの人に質問出来たりするんだもの
今回は演習進めてる感じだったので、次回以降はぜひRubyの新メソッド実装とかやってみたいね!
それと、引き続き演習のほうも継続してやっていこうと思うね!
以前、QiitaにC++/StimulusでのWeb開発をまとめた以下の記事を書いた
それからもC++/Stimulusでアレコレ試していて、ふと「Vue.jsとStimulusって併用できるんだろうか?」と思ったので試してみたときの備忘録をまとめてみた
ソースとコミットを見ていただければわかりますが、基本的にはC++側でHTMLやJavaScriptをブラウザで表示できるように処理しています
そこへプラスアルファでチャット用APIを作成しているくらいです
フロントエンドはVue.jsで実装を行っています
vue-routerを使い、SPAとして利用できるようにしています
あとはVueコンポーネントでdata-controller="chat"を追加して、Stimulusを使用しているくらいですね
普通にStimulusとVue.jsが併用できたなぁと……
HTMLにdata-controllerを追加することでStimulusは使えるので、Reactとかの仮想DOMでも同じようにStimulusが併用できるんじゃないかと思いますね
おそらく、Rails/Vue.jsとかにもStimulusが併用できそう
ひとまずは、Rails/Vue.js/Stimulusでのリアルタイムチャット&SPAアプリでも作ってみようかなと思います
あと、C++/Stimulus/Vue.js/FireBaseでの爆速リアルタイムチャットアプリとか作ってみたいですねー