Perl6の内部表現
|
Takahiro Shimizu
|
このセッションの内容
- Perl6の主要な実装であるRakudoの内部構造を探ります
- Rakudoの内部で利用されているVMや, Perl6のサブセットなどについて探索します
- スクリプト言語で主に使われているバイトコードインタプリタの気持ちになります
内容
- Perl6とは?
- スクリプト言語処理系の動き
- Perl6の内部構造
- MoarVMのバイトコード実行
- まとめ
Perl6とは
- 当初Perl5の時期バージョンとして開発されていたプログラミング言語
- 仕様と実装が分離しており, 現在はテストが仕様となっている
- 実装は歴史上複数存在しているが,主流な実装はRakudo
- 言語的にはスクリプト言語であり, 漸進的型付き言語
- 動作環境は、独自のVMのMoarVM, JVM、一部JavaScript上で動作する
現在のPerl6
- 現在のバージョンは
6.d
- ブラウザ上で実行可能な環境が存在する
- IDEが開発されている
- WebApplicationFrameworkなども開発されており、 Perl5のモジュールを移行したものがいくつか存在する
- 日本では趣味のプロダクト以外社会では使用されていない
- 処理速度では一部Perl5に勝っているが、それでも大分遅い
[参考]Perl5のソースコード
- Perl5時代
- スカラ、配列、ハッシュの3種類
- それぞれの変数への参照であるリファレンスが使用可能
use ustrict;
use warnings;
my $scalar_value = "hello!";
print "$scalar_value\n";
my @array = (1..10);
print "$array[0]\n";
my %hash = ( this_is_key => "this_is_value");
print "$hash{this_is_key}\n";
my $hash_ref = \%hash;
print "$hash_ref->{this_is_key}\n";
Perl6のソースコード概要
- Perl5の文法とは比較的変更が多い
- 変数がオブジェクトと化した事により, 変数からsayメソッドを呼ぶことが可能
my $str_value = 'hello world!';
$str_value.say; # hello world!
- Perl5と同様に,変数にはデフォルトでは型がないような振る舞いをする
my $sample_value = 'hello world!';
$sample_value.say; # hello world!
$sample_value = '31';
$sample_value.say; # 31
say($sample_value * 3);
Perl6の言語的な特徴
- 漸進的型付き言語である為, 型を強制することも可能となる
my Int $int_value = 31;
$int_value = "hello"; # Compile error!
$ perl6 type_invalid.p6
Type check failed in assignment to $int_value; expected Int but got Str ("hello")
in block <unit> at type_invalid.p6 line 4
Perl6の言語的な特徴
- 型を独自に定義することも可能
- 入力の型によって実行する関数を変える事などができる
my subset Fizz of Int where * %% 3;
my subset Buzz of Int where * %% 5;
my subset FizzBuzz of Int where Fizz&Buzz;
my subset Number of Int where none Fizz|Buzz;
proto sub fizzbuzz ($) { * }
multi sub fizzbuzz (FizzBuzz) { "FuzzBuzz" }
multi sub fizzbuzz (Fizz) { "Fizz" }
multi sub fizzbuzz (Buzz) { "Buzz" }
multi sub fizzbuzz (Number $number) { $number }
fizzbuzz($_).say for 1..15;
スクリプト言語
- Perl6は現状コンパイルすることはできない
- 現在広く使われているスクリプト言語(Perl,Python,Ruby…)などとPerl6の構成は類似している
- 今回はPerl6の実装を追いながら、最近のスクリプト言語処理系の大まかな実装を理解する
スクリプト言語処理系
- スクリプト言語は入力として与えられたソースコードを、 直接評価せずにバイトコードにコンパイルする形式が主流となっている
- その為スクリプト言語の実装は大きく2つで構成されている
- バイトコードに変換するフロントエンド部分
- バイトコードを解釈する仮想機械
Perl6以外のスクリプト言語
- 現在使われているプロセスVMは言語に組み込まれているものが多い
- JVMやElixirなどのVMは複数の言語で使用されている
- Java
- Ruby
- Python
- Elixir
Perl6の処理系の構成
- Perl6の処理系で現在主流なものはRakudoと呼ばれる実装である(歴史上複数存在する)
- Rakudoは3つのレイヤーから構成されている
- Perl6インタプリタ
- Perl6インタプリタを記述するPerl6のサブセットNQP
- Perl6のバイトコードを解釈するMoarVM
- Perl6/NQPがフロントエンドに相当し、MoarVMがバックエンドに相当する
Rakudoの構成図
(http://brrt-to-the-future.blogspot.com/2015/03/advancing-jit-compiler.html)
Perl6とNQP
- NQP(NotQuitPerl Perl)
- Perl6、 NQP自体がNQPで記述されている
- NQPもNQPで記述されている為、 セルフビルド(自分自身で自分自身をコンパイルする)を行う
- NQPはPerl6の文法をベースにしているが、 制約がいくつか存在する
- 元々はPerl6の主力実装がParrotだった時代に登場
- 文法がアップデートされており、当時の資料は古くなっている
my $value := "hello!";
say($value);
NQPスクリプト
- 変数は束縛
:=
を使う
- 関数の間に空白を入れてはいけない
- 再帰呼び出しを使うフィボナッチ数列
#! nqp
sub fib($n) {
$n < 2 ?? $n !! fib($n-1) + fib($n - 2);
}
my $N := 29;
my $z := fib($N);
nqp::say("fib($N) = " ~ fib($N));
NQPスクリプト(nまでの整数の和)
sub add_test($n){
mu $sum := 0;
while ( $n > 1) {
$sum := $sum + $n;
--$n;
}
return $sum;
}
say(add_test(10000));
NQPとオペコード
- NQPはPerl6の中で一番レイヤーが低い言語
- その為、 実行するVMのオペコード(処理単位)を使用することができる
NQPとMoarVM
- NQPそのものは実行することはできない
- NQPの実行にはMoarVM/JVMが必要となる
- NQPコンパイラが各VMに対応したバイトコードに変換する
Perl6のVM
- MoarVM, JVM , JavaScriptが選択可能
- メインで開発されているのはMoarVMであり、 他のVMは機能が実装されていないものが存在する
rakudo-star
というPerl6のパッケージ環境では、 MoarVMがデフォルトでインストールされる
MoarVM
- C言語で記述されているPerl6専用の仮想機械
- レジスタマシン
- 型情報を持つレジスタに対しての演算として処理される
- Rubyなどはスタックマシンとして実装されている
- LuaJITなどを利用したJITコンパイルなども可能
- Perl6やNQPは、MoarVMに対してライブラリなどを設定して起動する
バイトコード
- Perl6も、Rakudo/NQPはバイトコードに変換され、 バイトコードをVMが実行する
- バイトコード実行部分は、 命令に対応するバイト列を読み込み、 解釈し、 次の命令を読み取ることを繰り返す