Funny Walking

人の個性への好奇心

SPVMを使って、オブジェクト指向的な実装を検証する

この記事は、Perl Advent Calendar 2020 1日目の記事です。

もう師走ですね。世界はコロナの影響で生活様式がびっくりするほど変わってしまいました。時間の流れを、いっそう早く感じます。みなさまはいかがでしょうか?

コロナの影響かどうか不明ですが、自分は転職先で適応障害という病気になりました。だいぶ回復してきましたが、まだ投薬はつづけております。

人生とはわからないものです。療養中に人生をふりかえっていたら面白い発見がありました。今回は、そんな話を書きます。具体的には、SPVMという新しい言語を使って、オブジェクト指向的な実装をためしたら面白かっったという内容です。さあ、1日目のアドベントカレンダー、はりきってまいりましょう!

SPVMとは?

本編に入る前に解説しておきましょう。SPVMとは、Static Perl Virtual Machine の略です。静的型付けプログラミング言語として実装されています

SPVMが作られた目的

Perl数値計算と配列処理を高速に行うことです

Perlの高速処理が求められる分野は、具体的にはバイオインフォマティクスの分野です。大量の文字列情報であるDNAの塩基配列を計算するために、文字列処理の得意なPerlがよく使われます。ただ、大量の情報を高速に処理するためには、CやC++などの言語のほうが向いています。そこで、Perlを使い馴れた人が覚えやすく、かつ、CとC++へのバインディングを容易に実装できるSPVMが作られました。

言語設計者による解説

SPVMの目的

SPVMでPerlのforループを100倍高速化する

SPVMの実装サンプル

GitHubの情報 を参考に、ベンチマークをしてみました。

コードの内容は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であり、本記事でも同じバージョンを使っています。ベータ版ということで、さまざまなユースケースを開発者へ提供することが必要だと自分は考えているのです。

オブジェクト指向設計の教材

だいたい、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');と書いても渡せるはずですが、自分の環境ではエラーになりました。開発者へ連絡しました。

metacpan.org

外部プログラムの呼び出しを抽象化

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

明日はkfly8さんの、perl-users.jpについてのお話です!