新しいWebサイトとブログを作った
新しいWebサイトとブログを作りました。 今後はこちらへの投稿をメインにしていきたいと思います。
mruby + Qt (QObject・QML) で色々やってみた
Garnet というmrubyとQt (QObject) のバインディングを開発して、Qtとmrubyの連携を色々試してみました。
QMetaObject
Qtには、C++自体にはないリフレクション機能をサポートするためのQMetaObjectというものがあります。
QMetaObjectは、QObject (Qtの様々なクラスの継承元になっている) から派生した各クラスごとにビルド時に作成されるオブジェクトで、QObjectに対する動的なメソッドの呼び出しやプロパティのアクセス、シグナル・スロットのシステムなどを可能にしています。
動的なUI記述言語であるQMLも、このQMetaObjectのシステムを基盤にしていて、C++で書かれた機能にQML・JavaScriptからアクセスできるのはこのためです。
Garnet
Garnetは、このQMetaObjectを使ってmrubyからQtのオブジェクトにアクセスするためのライブラリで、 元々はh2so5さんが開発されていたプロジェクトなのですが、ここ最近は私が開発をしていました。
このライブラリを使って、以下のことができるようになりました。
- QtらしいAPIでmrubyのスクリプトを実行させる
- C++・Qtの値とmrubyの値の相互変換 (StringやArrayなど)
- QObject派生のオブジェクトのメソッドやプロパティへのmrubyからのアクセス
C++とmrubyの連携
Garnetのエンジンにオブジェクトを登録する(正確には、Kernelモジュールのメソッドに追加する)ことによって、mrubyからアクセスできるようになります。
また、クラスを登録して、mruby内でnewすることもできます。
ちなみに、メソッドをmrubyからアクセスできる(QMetaObjectの対象になる)ようにするためには、前にQ_INVOKABLEを付けるか、slotにする必要があります。
// Person.h class Person : public QObject { public: Q_INVOKABLE void greet() { qDebug() << "Hello!!"; } }
#include <Garnet/Engine> Garnet::Engine engine; Person person; engine.registerObject("person", &person); engine.evaluate("person.greet")
QMLとmrubyの連携
QML内で作られたオブジェクトに対しては、動的にQMetaObjectが生成されます。 それを使うことで、QMLのプロパティやメソッドへのアクセスが可能になります。
ある意味では、mruby - C++ - JavaScript の二重のバインディングになっています。
// C++ #include <Garnet/Engine> qmlRegisterType<Garnet::Engine>("Garnet", 1, 0, "Engine");
// QML import Garnet 1.0 as Garnet Rectangle { width: 360 height: 360 Text { id: resultText anchors.fill: parent } Garnet.Engine { Component.onCompleted: { registerObject("result_text", resultText) engine.evaluate("result_text.text = 'Hello, world!'") } } }
思ったこと
mrubyは、記述力が高くかつアプリケーションに組み込めるのが魅力だと感じ、今後が楽しみになりました。
QMetaObjectを使うと、特別なバインディングを書かなくても、スクリプト言語などから、Qtの機能にアクセスすることができます。
QtのC++向けAPIでは、QMetaObjectからアクセス可能になっていないメソッドが多いので、実用するのは難しいですが、QMLではすべての機能がQMetaObjectからアクセスできるので、QMetaObjectによるバインディングはmrubyに限らず有用ではないかと感じました。
C++で、Ruby、LINQライクなコンテナの操作とモナド内包表記のライブラリを作った(紹介編)
C++11用の、Ruby、LINQライクなコンテナの操作、および(モナド)内包表記を提供するライブラリ「Amulet」を作りました(現在も開発中です)。
GitHubリポジトリはこちらです: https://github.com/iofg2100/amulet
なお、Boostが必要になります。
なぜ作ったか?
このライブラリの様に、C++でRuby、LINQライクな(関数型ライクな)コンテナ処理を実現する既存のライブラリは幾つもあります。
- boost.range
- Oven
- LINQ for C++
- などなど…
今回このライブラリを開発した理由は
- 普通のメソッドとして書きたい
- 拡張メソッド形式だと、using namespaceしない限りすべてのメソッドにnamespaceを書かなければいけなくなったり、メソッドと他の識別子が衝突して面倒だったりと不便
- 内包表記のために、クラスはモナドであってほしい
- map, filterなどで返された値はrange-based forでも使えるようにしたい
これらの条件を満たすものが見つからなったからです。また、単純に、自分でこのようなライブラリを作るのが面白そうだったからでもあります。
コンテナ用の便利なメソッド
(ここでは簡単のためにメンバ関数をメソッドと呼ぶことにします)
Amulet::RangeExtension
は、指定した型(std::vector
などのコンテナ型)にRuby、LINQのような便利なコンテナ操作用メソッドを追加するテンプレートクラスです。(メソッド名は、RubyやScalaを意識しています)
指定した型を継承して、C++11のInheriting constructorでコンストラクタも同時に継承すると同時に、 新たなメソッドを定義することで、既存の型に様々なコンテナ処理メソッドを追加する仕組みになっています。
これによって追加されたメソッドを使うことによって、メソッドチェーンでコンテナを変形していくような直感的な処理をできるようにしました。
ちなみに、mapやfilterなど、新たな変形されたコンテナを返すメソッドは、遅延評価されるようになっています(それらのコンテナの要素の値は実際に参照された時に初めて評価されます。) 内部的には、Boost.Iteratorで提供される遅延評価のイテレータを使用しています。 ちなみに、内部で元のコンテナをムーブあるいはコピーしているので、元のコンテナが削除されても問題はありません。
#include <amulet/range_extension.hh> RangeExtension<std::vector<int>> xs = {1,2,3}; // それぞれの値に与えられた関数を適用して、得られた値のコンテナを得る xs.map([](int x){ return x * 2; }); // => {2,4,6} // 条件を満たす値のみのコンテナを得る xs.filter([](int x){ return x % 2 == 0; }); // => {2} // 逆順のコンテナを得る xs.reverse(); // => {3,2,1} // 和を計算 xs.foldLeft(0, [](int sum, int x){ return sum + x; }); // => 6 // それぞれの値に与えられた関数を適用して、得られた値の各要素を集めて、コンテナを得る xs.flatMap([](int x){ return RangeExtension<std::vector<int>>{x, x}; }); // => {1,1,2,2,3,3} // それぞれの値にindexを付加し、std::pairにする xs.withIndex(); // => {{0,1},{1,2},{2,3}}
既存のコンテナの値をラップ
Amulet::extend
関数を使うことで、既存のコンテナをラップして、RangeExtension
で追加されるメソッドを使えるようにすることもできます。
#include <amulet/range_extension.hh> std::vector<int> vec = {1,2,3}; auto twices = Amulet.extend(vec).map([](int x){ return x * 2; });
整数のRange
Amulet::intRange
は、RangeExtensionのメソッドが使える整数値のRangeを返します。
#include <amulet/int_range.hh> auto fizzbuzz = Amulet::intRange(0, 100).map([](int x)->std::string{ if (x % 15 == 0) return "fizzbuzz"; else if (x % 3 == 0) return "fizz"; else if (x % 5 == 0) return "buzz"; else return std::to_string(x); }); std::copy(fizzbuzz.begin(), fizzbuzz.end(), std::ostream_iterator<std::string>(std::cout, " ")); std::cout << std::endl;
LINQクエリ式っぽい内包表記(モナド内包表記)
プリプロセッサマクロを使って(乱用して)、LINQクエリ式・Scalaのfor内包表記・Haskellのdo記法のような内包表記を実現してみました(個人的に作っていて最も面白かった部分です)。 コンテナ(RangeExtension)から新たなコンテナを構築する際などに使用することができます。 これを"Query macro"と呼んでいます。
ちなみに、この内包表記は、RangeExtensionだけでなくモナド一般に対して使えるように設計されています(モナド内包表記)。
#include <amulet/short_query_macro.hh> #include <amulet/range_extension.hh> template <typename T> using ExVector = Amulet::RangeExtension<std::vector<T>>; auto xs = ExVector<int>{1,2}; auto ys = ExVector<int>{3,4}; auto product = _do( _from(x, xs), _from(y, ys), _select(std::make_pair(x, y)) ); // => {{1,3},{1,4},{2,3},{2,4}} ExVector<std::pair<std::string, int>> prices = { {"orange", 50}, {"apple", 100}, {"banana", 150}, {"carrot",90}}; // 内包表記版 auto fruitPrices1 = _do( _from(pair, prices), _where(pair.first != "carrot"), _select(pair.second) ); // メソッドチェーン版 auto fruitsPrices2 = prices.filter([](const std::pair<std::string, int> &pair){ return pair.first != "carrot"; }).seconds();
Option型
Amulet::Option
は、無効かもしれない値を格納するコンテナのような値です(boost::optional
などと同じ)。
boost::optionalはポインタに似せた作りになっている一方、Amulet::Optionはコンテナに似せた作りになっているのが特徴です。
(イテレータもあるのでrange-based forでも使えます。ただしRangeExtensionではありません。)
全体的には、ScalaのOptionを意識した設計になっています。
Optionも、RangeExtensionと同様に、内包表記を使用することができます(モナドになっています)。 この内包表記を使うことで、シンプルにOptionの合成(元のOptionが無効であれば、できるOptionも無効にするといった)を行うことができます。
#include <amulet/short_query_macro.hh> #include <amulet/option.hh> auto divide = [](int x, int y) -> Amulet::Option<int>{ if (y) return Amulet::some(x / y); // 成功 else return Amulet::none; // 失敗、無効値を返す }; auto a = Amulet::some(0); auto b = Amulet::some(1); auto divided = _do( _from(x, b), _from(y, a), divide(x, y) ); // divideが失敗するので無効値を返す auto added1 = _do( _from(x, a), _from(y, b), _select(x + y) ); // Amulet::Optional<int>(1) auto added2 = _do( _from(x, a), _from(y, divided), _select(x + y) ); // dividedが無効値なのでこれも無効値を返す
モナド
モナド(Monad)とは、Haskell、Scalaなのでよく使われているデザインパターンのようなものです(詳細の説明は詳しい人に譲ります)。 このライブラリは、内包表記を書けるようにするための仕組みとしてモナドを使っています。
このライブラリでは、クラスがモナドであること(Monadコンセプト)を次のように定義しています:
value_type
型を持つtemplate <typename F> flatMap(F f)
メソッドを持つtemplate <typename T> fromValue(const T &vaue)
staticメソッドを持つ
これらの条件を満たすクラスのインスタンスは、内包表記内で使用できるようになります。すなわち、内包表記のマクロはこれらのメンバの組み合わせに展開されます(ただし、_where
句を使う際にはfilter
メソッドが必要になります)。
RangeExtensionやOptionは、これらの条件を満たしてるので、内包表記で使えるようになっているのです。
問題点:コンテナのコピーコスト
map, filterなどのメソッドは、*this
がlvalueだった場合に*this
のコピーを内部に持つ遅延評価コンテナを返す設計になっています(rvalueであればムーブします)。
このコピーコストを抑えるためには、std::move
を使うことができます。
auto mapped = std::move(xs).map([](int x){ return x * 2; }); // => {2,4,6}, xsは無効になる(mappedに内部的に格納される)
ただし、ムーブしたくない場合(xsがconstである、xsをあとで使いたい等の場合)は、どうしてもコピーが発生してしまいます。
また、内包表記内ではコピーキャプチャのラムダ式を生成しているので、ここでもコンテナのコピーコストが発生するという問題があります。(遅延評価を行うため、参照キャプチャを行うとすでに存在しない値を参照するなどの問題が発生してしまいます。変数ごとにキャプチャの方法を選択できるようにすることもできますが、非常に複雑になってしまいます。)
なお、Qtのコンテナ(QList、QVector、…)などのコピーオンライトなコンテナではこういった問題は発生しません。
これからやりたいこと
RangeExtensionにはまだまだメソッドが足りないので、もっと追加して実用的で便利なライブラリを目指したいです。
また、Amuletではモナドが使えるので、さらにモナドを使った機能(Parsecライクなパーサライブラリなど)を追加していきたいです。
C++勉強会 @ tkb #2で「QtとC++でGUIプログラミング」という発表をした
C++勉強会 @ tkb #2で、「QtとC++でGUIプログラミング」という内容で発表をしました (リンク先には他の方々の発表資料もあるので是非御覧ください。)
1ヶ月が経ってしまいましたが一応こちらに載せておくことにします。
Qtにペンタブレット関連のパッチを送った
先日、ペンタブレット関連のパッチをQtに送ったので、(遅れてしまいましたが)記事にすることにしました。
パッチの内容
今回送ったパッチの内容は、Qt5へのMacでのペンタブレットのイベント(QTabletEvent)のサポートの追加です。
devブランチにマージされ、おそらく、Qt 5.2から追加される予定となります。
https://codereview.qt-project.org/#change,62740
もともと、Qt4では問題なくQTabletEventはサポートされていました。しかし、Qtのバージョンが5になったときに、ネイティブのウィンドウをラップする機構が書き換えられた関係で、 Qt5.1まで、WindowsとMacにおいてQTabletEventのサポートがはずされている状態になっていました。
つい最近、WindowsでのQTabletEventのサポートを追加するパッチはマージされて、Qt 5.2に追加される予定となったのですが、Macではまだでした。
現在私が開発しているPaintFieldは、この問題によってQt 4.8を使っているのですが、これによってQt 5にようやく移行できそうになってきました。
手順
http://qt-project.org/wiki/Code_Reviews
このページで細かい手順が説明されていて、それに従ってすんなりコミットをすることができました。
基本的な手順は
- バグレポート・コードレビュー用のアカウントを作る
- gitリポジトリをクローンする
- 実際に変更を加える
- コミットする
- コードレビューを受け、必要なら修正する
でした。
RubyとParsletで算術演算のパーサを作った
はじめに
Ruby用のパーサジェネレータ、Parslet というものを見つけたので、 簡単な練習として算術演算のパーサを作ってみました。
加減乗除と剰余算・累乗(+-*/%^)、および括弧を使った数式の文字列を入力して、値を浮動小数点数で計算できます。
Parsletの基本的な使い方は Get Started が参考になりました。
方針
- パーサで文字列をHashとArrayの組み合わせに分割
- パース結果からASTを構築
- ASTを評価
パーサ
まず、Parslet::Parserクラスを継承して、パースを行うクラスを用意します。
ルールはBNFの様に記述することができます。
Parsletの仕組みとして、パースされた結果はHashとArrayの組み合わせとして出力されます。
class Parser < Parslet::Parser #ルールを指定する rule(:lparen) { str('(') >> space? } rule(:rparen) { str(')') >> space? } rule(:space) { match('\s').repeat(1) } rule(:space?) { space.maybe } rule(:numeral) { match('[0-9]').repeat(1) } rule(:number) { ( numeral >> ( str('.') >> numeral ).maybe ).as(:number) >> space? } rule(:factor) { number | lparen >> expression.as(:expression) >> rparen } rule(:expression) { factor >> ( match['+-/*%^'].as(:operator) >> space? >> factor).repeat(0) } root :expression end
実行例
parsed = Parser.new.parse("(1 + 2) * 3.5 + 4 * (2 / 4) ^ 2") pp parsed
出力結果
[{:expression=>[{:number=>"1"@1}, {:operator=>"+"@3, :number=>"2"@5}]}, {:operator=>"*"@8, :number=>"3.5"@10}, {:operator=>"+"@14, :number=>"4"@16}, {:operator=>"*"@18, :expression=>[{:number=>"2"@21}, {:operator=>"/"@23, :number=>"4"@25}]}, {:operator=>"^"@28, :number=>"2"@30}]
ASTのノード
ASTのノードとして、数値リテラルを表すNumberLiteralと、二項演算を表すBinaryExpressonを用意します。
evalで評価ができます。
class NumberLiteral < Struct.new(:value) def eval value.to_f end end class BinaryOperation < Struct.new(:left, :operator, :right) def eval case operator when '+' left.eval + right.eval when '-' left.eval - right.eval when '*' left.eval * right.eval when '/' left.eval / right.eval when '%' left.eval % right.eval when '^' left.eval ** right.eval end end end
演算子の優先順位
定数のハッシュとして、演算子の優先順位を用意します。 ASTの構築時に使用します。
OP_PRECEDENCE = { '+' => 10, '-' => 10, '*' => 20, '/' => 20, '%' => 20, '^' => 30 }
ASTの構築
construct_ast_recursiveで、パーサで得られた ((数値), (演算子, 数値), (演算子, 数値), ...) のようになった構造から、演算子の優先順位を考慮してASTを構築します。
#戻り値: 構築されたAST, 余ったexpression def construct_ast_recursive(expression, precedence_limit) if expression.length == 0 return nil, nil end first = expression[0] #最初の要素は値として取得 case when first.has_key?(:number) lhs = NumberLiteral.new(first[:number]) when first.has_key?(:expression) lhs = construct_ast(first[:expression]) else raise "no expression or integer literal in expression item" end expression = expression[1..-1] if expression.length == 0 expression = nil end while expression op = expression[0][:operator].to_s precedence = OP_PRECEDENCE[op] #演算子の優先順位が再帰で上位の優先順位以下なので中断 if precedence <= precedence_limit return lhs, expression end #現在の演算子に対して右辺になるASTを再帰的に構築 rhs, expression = construct_ast_recursive(expression, precedence) lhs = BinaryOperation.new(lhs, op, rhs) end return lhs, nil end def construct_ast(expression) construct_ast_recursive(expression, -1)[0] end
評価
実際に文字列から値を評価します。
def parse(str) parsed = Parser.new.parse(str) ast = construct_ast(parsed) p ast.eval end parse("3.4 * 2 + 5 * 3") # 21.8 parse("(1 + 2) * 3.5 + 4 * (2 / 4) ^ 2") # 11.5