この記事は、Perl Advent Calendar 2020 1日目の記事です。
もう師走ですね。世界はコロナの影響で生活様式がびっくりするほど変わってしまいました。時間の流れを、いっそう早く感じます。みなさまはいかがでしょうか?
コロナの影響かどうか不明ですが、自分は転職先で適応障害という病気になりました。だいぶ回復してきましたが、まだ投薬はつづけております。
人生とはわからないものです。療養中に人生をふりかえっていたら面白い発見がありました。今回は、そんな話を書きます。具体的には、SPVMという新しい言語を使って、オブジェクト指向的な実装をためしたら面白かっったという内容です。さあ、1日目のアドベントカレンダー、はりきってまいりましょう!
SPVMとは?
本編に入る前に解説しておきましょう。SPVMとは、Static Perl Virtual Machine の略です。静的型付けプログラミング言語として実装されています。
SPVMが作られた目的
Perlの高速処理が求められる分野は、具体的にはバイオインフォマティクスの分野です。大量の文字列情報であるDNAの塩基配列を計算するために、文字列処理の得意なPerlがよく使われます。ただ、大量の情報を高速に処理するためには、CやC++などの言語のほうが向いています。そこで、Perlを使い馴れた人が覚えやすく、かつ、CとC++へのバインディングを容易に実装できるSPVMが作られました。
言語設計者による解説
SPVMの実装サンプル
コードの内容はGitHubのものと殆ど同じなので、リンクの掲載にとどめます。
SPVM/benchmark at master · yuki-kimoto/SPVM · GitHub
実行にあたり、変数名とループカウントだけ調整しました。結果が以下の通りです:
~/github/perl_test 🍌 perl benchmark-synopsys.pl Benchmark: timing 1000000 iterations of perl5_sum, spvm_sum, spvm_sum_native, spvm_sum_precompile... perl5_sum: 33 wallclock secs (33.24 usr + 0.02 sys = 33.26 CPU) @ 30066.15/s (n=1000000) spvm_sum: 10 wallclock secs (10.11 usr + 0.01 sys = 10.12 CPU) @ 98814.23/s (n=1000000) spvm_sum_native: 1 wallclock secs ( 0.55 usr + 0.00 sys = 0.55 CPU) @ 1818181.82/s (n=1000000) spvm_sum_precompile: 1 wallclock secs ( 0.55 usr + 0.00 sys = 0.55 CPU) @ 1818181.82/s (n=1000000) Rate perl5_sum spvm_sum spvm_sum_native spvm_sum_precompile perl5_sum 30066/s -- -70% -98% -98% spvm_sum 98814/s 229% -- -95% -95% spvm_sum_native 1818182/s 5947% 1740% -- 0% spvm_sum_precompile 1818182/s 5947% 1740% 0% --
spvm_sum_native
はCを呼び出したもの、spvm_sum_precompile
はCに変換して実行したものです。Perlと比較して、かなり高速に動作します。
SPVMでオブジェクト指向的な実装を試す
さて、本題です。
SPVMでオブジェクト指向的な実装がどこまで再現できるか、検証してみます。
検証の動機は、多角的な視点を開発者へ提供するためです。SPVMは、記事の執筆時点でver. 0.0927
であり、本記事でも同じバージョンを使っています。ベータ版ということで、さまざまなユースケースを開発者へ提供することが必要だと自分は考えているのです。
オブジェクト指向設計の教材

オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
- 作者:Sandi Metz
- 発売日: 2016/09/02
- メディア: 大型本
だいたい、5章くらいまでのコードを参考にします。
では、さっそく行ってみましょうー
オブジェクトへのアクセサメソッドを設定
直接フィールド変数へアクセスさせないよう、アクセサを設定します。
SPVM
sub rim : int ($self : self) { return $self->{rim}; } sub tire : float ($self : self) { return $self->{tire}; }
$self->rim
で、アクセサを呼び出します。$self->{rim}
と書くと、フィールド変数への直接アクセスになります。
引数の呼び出し順を抽象化
ハッシュを使って抽象化できます。
SPVM
sub new : PracticalCode::Wheel ($args : SPVM::Hash) { my $self = new PracticalCode::Wheel; $self->{rim} = $args->get_int("rim"); $self->{tire} = $args->get_float("tire"); return $self; }
Test::Moreで検証
my $args = SPVM::Hash->new; $args->set_int(rim => 26); $args->set_float(tire => 1.5); subtest "new" => sub { my $wheel = new PracticalCode::Wheel($args); isa_ok($wheel, 'PracticalCode::Wheel'); done_testing; };
use SPVM 'SPVM::Hash';
と書いて、PerlからSPVMのモジュールを呼び出すことができます。
自分で作ったSPVMモジュールを、ハッシュ経由で渡す
$self->{wheel} = (PracticalCode::Wheel)$args->get("wheel");
と書いてSPVM側で取り出します。
Perlで引数を設定するには、以下のように書きます。
my $args_gear = SPVM::Hash->new; $args_gear->set(wheel => new PracticalCode::Wheel($args_wheel));
文字列を、ハッシュ経由で渡す
$self->{tape_color} = (string)$args->get("tape_color");
と書いてSPVM側で取り出します。
Perlで引数を設定するには、以下のように書きます。
my $strs = SPVM::StringList->new_len(0); my $args = SPVM::Hash->new; $strs->push('green'); $args->set(tape_color => $strs->shift);
マニュアルを見ると、$args->set(tape_color => 'green');
と書いても渡せるはずですが、自分の環境ではエラーになりました。開発者へ連絡しました。
外部プログラムの呼び出しを抽象化
PracticalCode::Vendor::Wheel
を外部プログラムだと仮定します。ラッパーモジュールをつくり、外部プログラムの呼び出しはラッパーを使うようにします。
SPVM
package PracticalCode::WheelWrapper { use SPVM::Hash; use PracticalCode::Vendor::Wheel; sub wheel : PracticalCode::Vendor::Wheel ($args : SPVM::Hash) { return PracticalCode::Vendor::Wheel->new( $args->get_int("rim"), $args->get_float("tire"), ); } }
引数の最後がカンマ,
で終わっても、エラーにならないのがうれしいですよね。
Test::Moreで検証
my $args = SPVM::Hash->new; $args->set_int(rim => 20); $args->set_float(tire => 1.8); subtest "new" => sub { isa_ok(PracticalCode::WheelWrapper->wheel($args), 'PracticalCode::Vendor::Wheel'); done_testing; };
ポリモーフィズムは、できない
ポリモーフィズムという概念は、存在しないそうです。開発者の方に聞きました。
動かないけれど、それっぽいコード
ファクトリ
package PracticalCode::Trip { use SPVM::Hash; has bicycles : int; has customers : int; has vehicle : int; sub new : PracticalCode::Trip ($args : SPVM::Hash) { my $self = new PracticalCode::Trip; $self->{bicycles} = $args->get_int("bicycles"); $self->{customers} = $args->get_float("customers"); $self->{vehicle} = $args->get_float("vehicle"); return $self; } # ... sub prepare : int ($self : self, $prepares : int) { for (my $i = 0; $i < 10; $++) { prepares->[i].prepare_trip($self); } } } }
呼び出し対象その1(ドライバー)
package PracticalCode::Driver { sub prepare_trip : void ($trip : int[]) { gas_up($trip->vehicle); fill_water_tank($trip->vehicle); } sub gas_up : int ($vehicle) { #... } sub fill_water_tank : int ($vehicle) { #... } }
呼び出し対象その2(旅行コーディネーター)
package PracticalCode::TripCoordinator { sub prepare_trip : int[] ($trip : int[]) { return buy_food($trip->customers); } sub buy_food : int ($customers) { #... } }
呼び出し対象その3(メカニック)
package PracticalCode::Mechanic { sub prepare_trip : int[] ($trip : int[]) { my $res = int[]; for (my $i = 0; $i < 10; $i++) { $res[$i] = prepare_bicycle($trip->bicycles->[$i]); } return $res; } sub prepare_bicycle : int ($bicycle) { #... } }
ダックタイピング、インターフェース、テンプレートメソッド、抽象クラスの概念はSPVMに存在しません。
まとめ
自分の書き方には改善の余地がたくさんあり、勉強になりました。そして、新しい言語の開発に携わる面白さを体感しています。純粋にプログラミングをたのしむことは、新鮮な体験ですね。
今回の検証コードは、GitHubに上げています。全文が見たい方は、こちらからどうぞ。 github.com