Location : Home > Languages > Perl > Package
Title : Algorithm::Diff::Apply
Toolbox Logo

名称

 Algorithm::Diff::Apply -- Algorithm::Diff の diff の適用


概要

## シングル diff 形式
use Algorithm::Diff::Apply qw{apply_diff};
my @ary = ...;
my @diff = ...;   # Algorithm::Diff::diff() への呼び出し

my @changed_ary = apply_diff(\@ary, \@diff);
my $changed_ary = apply_diff(\@ary, \@diff);


## >1 diff を一度に適用するために複数形式を用いる
use Algorithm::Diff::Apply qw{apply_diffs};

@newary = apply_diffs(\@ary,
                      diff1_name => \@diff1,
                      diff2_name => \@diff2,
                      ...
                      diffN_name => \@diffN);

# または
@newary = apply_diffs(\@ary, %named_diffs);

# スカラコンテキストでは
$newary = apply_diffs(\@ary, %named_diffs);

# 拡張引数構文では
$newary = apply_diffs(\@ary, {
                      resolver => \&some_sub,
                      optimisers => [\&foo, \&bar],
                      key_generator => \&anothersub,
                      opt4 => ...,
                      opt5 => ...,
                     }, %named_diffs);

説明

最悪でも、ユーザはコンピュータよりましだと感じさせることだ。 -- R. E. Mullen [1]

 本モジュールは diff で記述されたあらゆる変化を新たにマージされた配列と関連する新しい配列を再生成することをめざした目標の配列に対し Algorithm::Diff で生成された diff を適用するためのサブルーチンを含んでいる。
 異なる diff から生成された2つのハンクが同じ行に影響を与えていたらコンフリクトが検出され、その解消のためのヘルパサブルーチンが起動して取り除くことができる。

apply_diff ARRAY, DIFF

 ARRAY のコピーに対する diff により記述された変化に適用し、結果を返す。DIFF は Algorithm::Diff::diff() で生成された diff であり、  スカラコンテキストでは apply_diff() は生成されて並び替えられたコピーへの参照を返す。配列コンテキストでは配列値として返されて並び替えられたコピーを返す。
 アルゴリズムのこのバージョンでは十分な複数形式(the full-blown plural form)よりも簡素で高速であり、1つの diff しか適用しないのであれば使うべきである。

apply_diffs ARRAY,HASH
apply_diffs ARRAY,OPTIONS,HASH

 同時に配列のコピーに1つ以上の diff を適用し、コンフリクトを制御し、並び替えられた配列をコンテキストによって参照または配列で返す。
 ARRAY は適切な長さの配列の値への参照でなければならない。渡された参照以降の配列は並びかえされない。.
 HASH パラメータは Algorithm::Diff::diff() によって生成された異なるソースからの diff を含んでいる。diff はそのソースを反映する任意の文字列によりキー付けされている。
 反映されるべき文字列について詳細は DIFF ラベル を見よ。
 OPTIONS は、もし指定されれば、オプションのキーワードと対応するパラメータのハッシュ参照でなければならない。以下のオプションを認識する。

Option "optimisers" (a.k.a. "optimizers")

 コンフリクトオプティマイザの配列への参照。通常は apply_diffs() は本モジュールでドキュメント化された最適化の全てを実行する。このオプションは以下の変更に用いられる。空の配列を私、最適化を相互に行う。これらのサブルーチンどのように呼び出されるかについて詳細は コンフリクトオプティマイザコールバック を見よ。

Option "resolver"p>

 このオプションはコンフリクトが検出された場合に呼び出されるサブルーチンを供給する際に用いられる。どのようにリゾルバサブルーチンが呼び出されるかについて詳細は コンフリクト解消コールバック を見よ。

Option "key_generator"

 目標の配列における要素と全ての diff は eq (すなわち参照タイプ)によって意味のある比較ができない場合、ハッシュする関数がこのオプションを用いて供給される。詳しくは下記の キー生成コールバック を見よ。

mark_conflicts HASH

 これはデフォルトのリゾルバコールバックである。このインタフェースの詳細は コンフリクトオプティマイザコールバック を見よ。apply_diffs() は以下のような配列を返す。

[ @part_before_conflict,
  ">>>>>> diff1_name\n",
  @lines_1,              # diff1 により並び替えられた行(のみ)
  ">>>>>> diff2_name\n"
  @lines_2,              # diff2 により並び替えられた行
  "<<<<<<\n",
  @part_after_conflict,
]

 配列を行ごとに出力するというのはおそらく正しいことだ。

optimise_remove_duplicates HASH

 Conflict optimiser: コンフリクトブロックから固有の diff ハンクを検出し、重複しているハンクを取り除く。


コールバック・インタフェース

 apply_diffs() の汚れ仕事の多くはコールバックサブルーチンで処理され、Algorithm::Diff::Apply のユーザは apply_diffs() の適切なオプションを渡すことでデフォルトの振る舞いをオーバライドすることができる。(上記を見よ。)

コンフリクトオプティマイザコールバック

 オプティマイザコールバックは人間が扱うよりも容易にコンフリクトを解消する方法である。これは同じ事象に対する異なるソースからの diff の結果を結合することで共通の変化を検出し、他の厄介な仕事を処理する。これでコンフリクト状況を除去できる。
 これらのコールバックは互いにコンフリクトすること無しに一度に適用できない異なる diff シーケンスからのハンクのグループを検出したときに apply_diffs() により呼び出される。
コンフリクトしている diff の各ブロックについては、コールバックは以下のフォーマットのブロック内のあらゆる diff により呼び出される。

@ret = optimiser_callback (
       "conflict_block" => {
       "name_1" => [$hunk_1_1, $hunk_1_2, ..., $hunk_1_N],
       "name_2" => [$hunk_2_1, $hunk_2_2, ..., $hunk_2_N],
        :
       "name_M" => [$hunk_M_1, $hunk_M_2, ..., $hunk_M_N],
      },
);

 タグ名はユーザが apply_diffs() に渡した diff シーケンス でラベル付けしたものである。定義によりコンフリクトブロックは少なくとも2つの別々の diff の部分集合をとして含む。上記の引数内の $hunk_X_X スカラは以下の構造のハッシュ参照である。

{
   "start"   => N,
   "changes" => [[OP1, DATA1], ..., [OPn, DATAn]],
}

 "start" はこのハンクが適用されるべき場所を示す target 配列における行番号であり、"changes" は適用されるべき変化を示す。
 オプティマイザ・コールバックは渡されたものの並び替えられたコピー(permuted copy)を返すべきである。空の diff は自動的に捨てられる。処理後に diff が1つだけ残っていた場合にはコンフリクトは完全にオプティマイザされる。

コンフリクト解消コールバック

 リゾルバコールバックはコンフリクトが検出されたときに呼び出され、オプティマイザはコンフリクトブロックを完全には取り除くことができない。リゾルバコールバックの仕事はコンフリクトを起こしている部分配列の幾種類かの解決を返すことである。これらのサブルーチンは以下のように呼び出される。

@ret = resolver_callback (
       "alt_txts" => {
       "diff1_name" => ['m', 'n', 'o'],
       "diff3_name" => [],
       }
);

 alt_txts パラメータは、メインの apply_diffs() 呼び出しにおいて適用される diff の名称をキーとし、生成された部分列を含む配列を値とするハッシュ参照である。これらの各部分列はコンフリクトが発生したソースの配列のスライスのコピーに対する対応する diff からのハンクの集合を適用した結果である。
 リゾルバ・コールバックは apply_diffs() が返す配列へ割り込ませる配列を返す。(他のオプションもリゾルバのサブルーチンに渡されるが、これらはまだ変更されるためドキュメント化されていない。)

キー生成コールバック

 Algorithm::Diff::Apply が最適化フェーズの間異なるハンクから2つの行を比較する必要が生じたときには、デフォルトでは "eq" 演算子を使う。これは 多くの Perl 変数にとっては無意味である。例えばオブジェクト参照は文字列化するためにオブジェクトの内部表現を把握するわけではない。例:"SomeClass=HASH(0x813aa8c)"
 これはただの ARRAY, HASH, SCALAR 参照または他の型の参照にも適用できる。理念的には同じ型の他の変数と比較される形式で表現されたスカラ変数により示されるオブジェクトの内部状態を指す。
 これはキー生成コールバックが必要なところだ。key_generator サブルーチンはハッシュするルーチンである。スカラ変数を引数としてとり、key_generator に対する他の呼び出しにより返される他の文字列と比較される文字列を返す。返り値は効率化のために apply_diffs() への呼び出しの内部にキャッシュされている。
  Algorithm::Diff::Apply で用いられるキー生成は Algorithm::Diff で用いられるものと同じ構文である。 Algorithm::Diff/KEY GENERATION FUNCTIONS を見よ。
 ユーザがオブジェクトを処理するときには、オブジェクトクラスのメソッドへのシンプルなラッパがよくトリックを起こす。

# 出版社により与えられた題名を示す Book オブジェクトは ISBN が固有ならばユニークと見なされる。

my @books = ...;
my $booksdiff1 = ...;
my $booksdiff2 = ...;
apply_diffs(\@books, {
            key_generator => sub {
            my $book = shift;
            return $book->isbn();
           },
        },
            bookdiff1 => $booksdiff1,
            bookdiff2 => $booksdiff2
);

 配列では異なる種類がある? 問題ない。

#...
key_generator => sub {
	my $arg = shift;
	local $_ = ref($arg) or return $arg;
	/Book/     and return $arg->isbn();
	/Product/  and return $arg->serial_number();
	/UKPerson/ and return $arg->nat_ins_number();
	/Person/   and return $arg->uid();
	# ... フォールバックの場合 ...
	return "$arg";    # 文字列化:常にユニーク
},
#...

 Algorithm::Diff::Apply はオプティマイザを試みるかコンフリクト解消の際にのみこの型のコールバックを使うことを思い出そう。これは単一形態(singular form) apply_diff() がこのオプションをとらないためである。


diff ラベル

 apply_diffs() はコンフリクトが生じたときにそれぞれの変更の原因に関係付けられるように意味のある名前になるよう、ユニークにラベルを付ける。この diff ラベルは変化がどこからもたらされたかを反映する。例えば以下のように:バージョン・URL・ファイル名・ユニークな番号。Algorithm::Diff::Apply はユーザが使うタグ名の構文など気にしない。それがどんなユニークな文字列であっても。
 この風習はインタフェースが上記の コールバック・インタフェース で記述されたコールバックルーチンであるからだ。


To-Do

 オプションのための呼び出しの風習は醜くて混乱を生じ、プロトタイピングをぶち負かす。Algorithm::{Diff,Patch} ファミリの他のモジュールと対応するように処理するが、代替的な構文もおそらく認められている。
 コンフリクトオプティマイザをさらに追加すること。誰かがもっといいルーチンを書いてくれればそれをライブラリに追加し、これを組み込もう。


参考資料

トラッキング

 本モジュールに関するバグは http://rt.cpan.org/NoAuth/Bugs.html?Dist=Algorithm-Diff-Apply で報告するか直接著者にメールを送って欲しい。

他のモジュール

 もちろん Algorithm::Diff。その他 Algorithm::Merge, VCS::Lite。

論文・ソフトウェア・議論

 フル VCS を構築するための興味深い情報は http://kt.zork.net/kernel-traffic/kt20030323_210.html#1


著者

 Andrew Chadwick, <andrewc-algodiffaply@piffle.org>.


ライセンス

 Copyright (c) 2003 Andrew Chadwick.

 本プログラムはフリーソフトウェアであり、Perl 本体と同等の条件で修正/再配布してもよい。

Toolbox Logo
Updated : 2007/09/20