Perlテックブログ

ITエンジニアの成長意欲を刺激する技術考察、モジュール開発の日記。Perlイベントや国内や海外のPerlの記事の紹介。

CライクなPerlのif文やfor文などの文法

Perlのifやforなどの文法は、C言語を元にして作られている。C言語JavaJavaScriptなどのCファミリーに慣れ親しんでいるなら、Perlの文法もスッと入ってくると思う。

// C言語のif文
if (条件) {
  ...
}
else if (条件) {
  ...
}
else {
  ...
}
# Perlのif文
if (条件) {
  ...
}
elsif (条件) {
  ..
}
else {
  ..
}
// C言語のfor文
for (int i = 0; i < length; i++) {
  ...
}
# Perlのfor文
for (my $i = 0; $i < $length; $i++) {
  ...
}

Perlの正体は、C言語 + シェルスクリプト + Lisp + 自然言語

一見独特に見えるPerlの文法は、すべてPerl独自でないものによって支えられている。

Perlは当時のUnix/Linuxで作業をしていていた人たちが慣れ親しんでいたもので構成されているんだ。サーバー管理者はプログラミング言語の専門家じゃない。だから、なるべく親しみやすいものが選ばれている。

Perlの独特さの正体は、Perlが独特でないものを組み合わせてしまったから。 なんて逆説的なんだろう。

SPVMに必要な機能の完成まで、あと5項目

SPVMの開発を2年くらい続けているのだけれど、だんだんとTODOリストが減ってきて、残り5項目になった。

  • C/C++言語の構造体を保持する仕組み
  • 複数の数値型の値を持つクラスを連続した配列領域に配置する仕組み(value型)
  • デフォルトでモータルで使えるC言語APIの整備
  • ループ展開
  • プリコンパイル済みのサブルーチンのより安全な読み込み

これができると、後は、標準関数の追加なんかの、比較的単純な作業と、実際に使ってみて、バグをとったり、パフォーマンスを測定して、改善したりという作業になる。

C/C++言語の構造体を保持する仕組み

SPVMはC/C++バインディングを簡単に行える仕組みを提供するので、C言語の構造体やC++のクラスを保持できる仕組みを作らないといけない。

今考えているのは、以下のように、パッケージのデスクリプターで、明示したらどうだろうかというもの。

package Point : struct;

structデスクリプターを持っていれば、それはC/C++の構造体やクラスとして扱うというもの。SPVMはPerlと同じで、コンストラクタは特別に持たずに、newメソッドを定義して、オブジェクト生成して、必要であれば、DESTROYでデストラクタを定義できる。このstructキーワードと、デストラクタを組み合わせれば、うまくC/C++の構造体/クラスを扱えるようになるかもしれない。

はっきりといいきれないのは、実際に手を動かして実装してみないと、わからないことがけっこうあるから。なんとなくこんな感じかなという感じで実装を進めていって、うまくいかなかったら、方向転換したり、微調整をしたりする。想像しているよりも、実装が複雑だったり、あっちが立てば、こっちが立たずということは、しょっちゅうだ。

複素数の関数のバインディング

C言語バインディングするときの、最も大きな壁は、複素数なんかの複数の数値型を持つ値型を受け取る関数だ。

オブジェクト指向言語では普通、オブジェクトは参照であって、配列は参照の配列になる。しかし、これは、メモリが飛び飛びなので、遅い。

f:id:perlcodesample:20180613214126j:plain

そこで、どうにか、連続したメモリ領域を確保して、値の集まりを扱えるようにする値型の仕組みを実装しないといけない。

これが実装できないと、C言語複素数に関する関数を実装したときに、思うようにパフォーマンスがでないだろう。

これは、Javaや他のスクリプト言語を参考にはできないので、独自に開発する必要がある。

デフォルトでモータルで使えるC言語APIの整備

メモリ管理はしたくない。うん、したくない、したくない。

C言語APIを呼び出すレベルでも、メモリ管理はしたくない。メモリの自己管理はバグの元。セグフォールトという苦しみは経験したくないんだもの。

Perlにはモータルという仕組みがあって、これで十分なんだけど、これは、自分でコード書かないといけないんだな。だからSPVMでは、これを入れ替えて、使いやすい短い名前のAPIの方をモータルに、長い名前のほうを非モータルにしようと考えている。

ループ展開

SPVMの配列アクセスは、undefのチェックをするし、インデックスの範囲チェックも行う。

そして、さらに、例外が発生しているかどうかというチェックも行います。

一度配列にアクセスするたびに、4回も、条件チェックが入るんですよ。

でもループする回数などがわかっていれば、ループ展開をして、チェックを10回に1回に減らすことができそう。初回だけ4回のチェックが入って、残りの9回は、チェックなしで配列アクセス。

こうすると速そうな予感がする。

プリコンパイル済みのサブルーチンのより安全な読み込み

SPVMは、SPVM自体がバージョンアップしたとしても、前のバージョンでプリコンパイルされたコードが、正しく動くように設計しようと思っている。つまり、プリコンパイルされたコードのバイナリ互換性を守るというわけだ。

Perlの場合はバージョンアップに対しては、それ以前いコンパイルされたコードは、壊れることが多い。Perlはバージョンアップに対して、バイナリ互換性を保証しておらず、コンパイルされたコードに対しては、再コンパイルが必要となる。

もっともperlbrewやplenvを使えば、作業ディレクトリで、バージョンアップして、モジュールの再インストールができるので何ら欠点ではない。

でも、SPVMはCPANにリリースされるモジュールなので、プリコンパイルされたコードが、SPVMのバージョンアップで壊れるのは、よくない。モジュール依存で、バージョンアップをしたら、なんだかよくわからない部分が壊れてよくわからん、ということになる。

バイナリ互換性を維持するために必要なことは、二つあって、インターフェースAPIを準備して、そこから関数を呼び出すこと。そして、インターフェースAPI後方互換性を守り続けること。

これが安全だけれど、パフォーマンスが必要な場合には、インターフェースから、呼び出せない場合もある。

それができない場合は、関数定義を仕様化して、その後方互換性を、ずっと守るということになる。

コアの数学関数だけは、仕様化して、それ以外は、インターフェースAPIの互換性を守るということになると思う。

undefined symbol: Perl_xs_apiversion_bootcheck

undefined symbol: Perl_xs_apiversion_bootcheck

というエラーXSを使っているときに出たので忘備録。

このエラーは、.soファイルをコンパイルをしたときのperlと異なるPerlを使ったときにでる。

perlbrewやplenvでインストールしたPerlで.soをビルドしたのに、システムPerlを使って、.soを呼び出そうとしたとかね。

(参考)
undefined symbol: Perl_xs_apiversion_bootcheck
OSとコンパイルの不一致のようです。

Perl入学式 in 札幌(北海道) 第一回環境構築編 2018年7月14日(土)

f:id:perlcodesample:20180612081337p:plain

Perl入学式 札幌が去年に引き続いて2年目の開催。第一回のカリキュラムが、7月14日から始まります。


7月14日 Perl入学式 in札幌 第1回 〜環境構築編〜


「プログラミングに少し興味があるんだけど」という北海道で札幌近くお住いの皆様、この機会に、参加してみるのはいかがでしょうか。

Perl入学式の独自カリキュラムで、プログラミングがまったく初心者の方でも、print文から初めて、最後には簡単なWebサイトが作れるようになります。😀

7月14日 Perl入学式 in札幌 第1回 〜環境構築編〜
Perl入学式って?
「プログラミングに興味があるけど, ちょっと難しそう...」と思っている貴方!

「他の言語使いだけど, ちょっとPerlも使ってみよっかな?」と思っている貴方!

「仕事や研究でPerlを使い始めたけど, ちょっと自信ないな...」と思っている貴方!

「プログラミング未経験者」から「Perl初心者」を対象としたワークショップ, 「Perl入学式」で一緒にPerlで学びましょう! プログラミングの「プ」の字も知らないあなたでも大丈夫. 経験豊富な講師とサポーターが, あなたの学びを全力でサポートします.

さあ, 私達と一緒にプログラミングの楽しさを体感しましょう!

どんなことをするの?
Perl入学式 in札幌」は, 2018年7月から翌年3月までの期間, 計5回開催する予定です. 開催日時は土曜日の午後、4時間から5時間です.

全5回のカリキュラムは, プログラミング未経験者の人が, 「Perlを使って簡単なWebサービスを作り上げる(例: Twitter風一行掲示板)」ところまで達成できるという所を目標にして構築しています.

ただし, 目標を達成するために基礎をおろそかにする, ということは絶対にしません. プログラミング未経験者の方でもしっかり理解できるよう, 基礎から一歩ずつ進めていきます.

授業風景

http://www.perl-entrance.org/static/images/slide/slide-01.jpg

(Perl入学式公式サイトより)

モジュール単位でプリコンパイルすることができるようになる予感

SPVM開発が順調に進んでおります。

もう少しで、モジュール単位でプリコンパイルすることができるようになる予感というところです。ドキドキ。

モジュール単位でプリコンパイルできると何がうれしいかというと、Perlの文法でソースコードを書いて、CPANからインストールしてもらうときに、プリコンパイルして、それを実行時に読み込んで、プリコンパイルされたサブルーチンを呼びだすことができるようになります。

つまり、ほぼPerlの文法で書いたソースコードを、ほぼC言語の速度で呼び出すことができるようになるということです。パチパチパチ

実行時コンパイルではなくって、プリコンパイルなので、実行時の読出しもメッチャ速いですよ。プリコンパイルの時間的なコストは、CPANからのインストール時点に押し込めます。

Perlに対する不満をたくさん解決

Perlに対する不満っているのは、たくさん聞くじゃないですか。SPVMは、その、あのっ、非常にシンプルに構成されています。

  • カラコンテキストだけだよ
  • 最後に1;書く必要ないよ
  • 配列は、いわゆるリファレンス記法だけだよ [] のやつね。
  • AST(抽象構文木)を作れるから、ソースコード解析が簡単だよ
  • デフォルトで、クラスを書く構文があるよ
  • クラスに関して、フィールドとサブルーチンのメタ情報を持っているよ
  • 数値計算と配列処理速いよ
  • 記号は少なく$と@だけだよ。
  • サブルーチン呼び出し速いよ
  • C言語ソースコードをはけるからLLVMにもWebAssemblyにも(きっと)変換可能だよ

Perl6が言語の表現力の拡大を目指した言語だとすると、SPVMは、言語の小ささと速度にフォーカスを当てた言語です。

SPVMは、Perlのモジュールとして利用できて、配列操作と数値計算C/C++バインディングを強化します。

まぁ、SPVMは言語としては、とってもつまらない言語だ。

git rebaseの使い方をやっと理解したので忘備録

git rebaseの使い方をやっと理解したので忘備録を書いておこうっと。

他の人とのやりとりはgit mergeだけで十分

普段の作業はgit mergeだけで十分。ほぼすべての場合は、git mergeだけで十分。これを肝に命じておく。

git rebaseはコミット粒度を上げるためにgit branchとセットで使う

git rebaseとは、小さな多数のコミットを、ひとつのコミットにまとめたいときに使う。

少し修正して、commit、試験。少し修正して、commit、試験。少し修正して、commit、試験。普段の作業はこんな感じじゃないのかな。

でも、コミットのメッセージを書く単位よりも小さく、コミットしたいときはよくある。ここ一行だけ変えて、試験したいとかね。コミット粒度を小さくしておけば、間違った部分だけを、git revertで戻せるし、git resetで戻れるから。

git branchで作業

今やる作業をgit branchで作る。

git branch mywork

そしてチェックアウト

git checkout mywork

こうして、作業ブランチを作る。

そして何回も何回も小さくコミットする。

git add .
git commit "remove part of memory pool"
git push origin mywork

git add .
git commit "cleanup"
git push origin mywork

git add .
git commit "cleanup2"
git push origin mywork

リベース用のブランチの作成

git rebaseはコミット改変という危険な作業だ。だから、リベース用の特別なブランチを作成する

git branch mywork_rebase

そして、このブランチにチェックアウト。

git checkout mywork_rebase

リベースの作業

そして、意味のある単位で、ほかの人が見てもわかるようあ単位で、rebaseする。今いるブランチはmywork_rebaseで、基準のブランチはmasterだ。リベースすると、基準のブランチを元に、今あるブランチで伸びているコミットを一つにまとめてくれる。iオプションを使う

git rebase -i master

そうすると以下のようにviが立ち上がる。

pick 98ad9ee remove part of memory pool
pick 681ff04 cleanup
pick a9c8dd1 cleanup2

# Rebase 274bce5..a9c8dd1 onto 274bce5
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell

今はpickと3行、表示されているけれど、2行目以降のpickをすべて「s」に変更する。こうすると一つのコミットにまとめられる。

pick 98ad9ee remove part of memory pool
s 681ff04 cleanup
s a9c8dd1 cleanup2

# Rebase 274bce5..a9c8dd1 onto 274bce5
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell

こうして「wq」で保存。そうすると再びviが立ち上がるので、新しくコミットメッセージを書く。これで完了。

このブランチは最後にmasterにマージされるブランチになる。

git rebaseしていいのは、プライベートな作業ブランチだけ

git rebaseはプライベートな作業ブランチだけ

だから、git rebaseはプライベートな作業ブランチで、プライベートな小さなコミットを、ひとつのコミットにまとめるときに使うと。

例外をどうやって伝播させるか?

例外の実装について調べたり考えたりしていた。

一番多い内部実装はなんだと思いますか?

「うーん、ちょっと内部まではわからないなぁ」「何らかの方法でジャンプしてたりするのかな」

僕が調べた範囲では、setjmp、longjmpを使っている実装が多かった。

Java, C++, rubyなど。

setjmp、longjmpの魔

setjmp、longjmpは気をつけてつかわないといけない関数だ。

setjmpは、関数を超えて、ジャンプできる。

関数から、他の関数へピヨーンとひっととび。

setjmp, longjmp とは
setjmp, lognjmp は古典的に標準ライブラリ関数として実装されているCライブラリ関数である。setjmp, longjmp はペアになって、関数の外にジャンプする機構を実現する。よろしいか、goto 文がたかが関数内部での制御の移動を実現するのに引き換え、この setjmp, longjmp は関数の間でのジャンプができるのである。だから、濫用すると、すぐに収拾がつかなくなるので、goto 文同様に禁止現場は多い。

Javaは安全に例外をつかえるのは、言語としてラッピングして、setjmp、longjmpの危険をなくしているからだ。内部実装は、かなり複雑になる。

戻り値で判定する

そもそも、例外なんていらないじゃん、戻り値で判定しちゃえば。こう考える言語もあります。C言語やgoだね。欠点は、戻り値にエラー判定を使っちゃたら、本来、求めたい値があった場合に、参照引数を渡さないといけない点。goだと多値返却だから、この点は問題がない。

int error = foo();

グローバル変数に設定

あっ、これはPerlね。グローバル変数の欠点は、すぐに保存しておかないと、ほかの処理によって、書き換えられてしまうのよ。例外をキャッチしたら、すぐにレキシカルに保存。これをきちんと書いておかないと。

my $error = $@;

SPVMにおける例外の実装は?

今、それを考えているところ。SPVMはPerlとCを簡単につなぐという目標があるんだ。Perlらしくもあり、Cらしくもある。

今考えていることは、次のこと。

例外処理をサポートする。例外が発生したかどうかは、戻り値で判定できる。本来必要な値は、スタックを使って返す。

C言語バインディングは次のような書き方になる。

int32_t SPVM__Foo__bar(SPVM_ENV* env, SPVM_VALUE* args) {
  (void)env;
  
  int64_t value = (int64_t)labs(args[0].lval);
  
  // 本来の戻り値は、第一引数に設定して返す
  args[0].lval = value;
  
  // 例外が発生したら1、そうでなければ0を返す
  return 0;
}

これで例外処理をかけて、GCもうまくいくかどうかということを、今実装して試しているところなんだ。

XSとは異なって、SPVMのCバインディングは、本当にただのC言語関数なんだ。