パスワードを忘れた? アカウント作成

bnezさんのトモダチの日記みんなの日記も見てね。 アカウントを作成して、スラッシュドットのモデレーションと日記の輪に参加しよう。

494271 journal

bnezの日記: FizzBuzz

日記 by bnez

FizzBuzz問題で遊ぶ。 拡張性重視版。

#! /usr/bin/perl

sub make_fizzbuzz(@)
{
  my $table = \@_;
  sub {
    my $input = shift;
    join ' ', map { $_->[1] } grep { $_->[0]->($input) } @$table or $input;
  };
}

my $fizzbuzz = make_fizzbuzz(
  [ sub { $_[0] % 3 == 0 }, 'Fizz' ],
  [ sub { $_[0] % 5 == 0 }, 'Buzz' ],
);
print join(', ', map { $fizzbuzz->($_) } 1 .. 100), "\n";

Perl最短は TAKESAKO氏の記述 かな。こりゃすごい。

523280 journal

bnezの日記: [C++] ポリシクラス

日記 by bnez

使用者が複数の対象に同一の操作を行う状況を考える。操作に対する振る舞いが対象によって異なっているとき、この性質を多態性(polymorphism)という。C++は、動的多態性(dynamic polymorphism)と静的多態性(static polymorphism)の2つを扱う能力を備える。ポリシクラスは、静的多態性の実装手段のひとつである。

動的多態性は、実行時に解決される多態性である。C++では仮想関数が主な実装手段である。以下に例を示す。

class Base
{
public:
  virtual ~Base() throw() {}
  virtual void func() = 0;
};

class DerivedA : public Base
{
public:
  virtual void func()
    { std::cout << "DerivedA" << std::endl; }
};

class DerivedB : public Base
{
public:
  virtual void func()
    { std::cout << "DerivedB" << std::endl; }
};

void do_func(Base& obj)
{
  obj.func();
}

void foo()
{
  DerivedA A;
  DerivedB B;
  do_func(A);  // "DerivedA" を表示
  do_func(B);  // "DerivedB" を表示
}

do_func()の引数 obj は、静的な型は Base& である。動的な型が DerivedA であるか DerivedB であるかによって、実行時に異なる振る舞いを示す。

一方、静的多態性はコンパイル時に解決される多態性である。クラステンプレートとテンプレートパラメータによる静的多態性の実装例を示す。

template <class Policy>
class Host
  : public Policy
{
public:
  void func()
    { Policy::func(); }
};

class PolicyA
{
protected:
  ~PolicyA() throw() {}
  void func()
    { std::cout << "PolicyA" << std::endl; }
};

class PolicyB
{
protected:
  ~PolicyB() throw() {}
  void func()
    { std::cout << "PolicyB" << std::endl; }
};

void foo()
{
  Host<PolicyA> A;
  Host<PolicyB> B;
  A.func();         // "PolicyA" を表示
  B.func();         // "PolicyB" を表示
}

foo()内の A と B は、どちらもクラステンプレート Host を用いているが、テンプレートパラメータに与える型が PolicyA であるか PolicyB であるかが異なっている。クラステンプレート Host のメンバ関数 func の振る舞いは、テンプレートパラメータに与える型によってコンパイル時に決定される。

振る舞いを定義する PolicyA や PolicyB のようなクラスをポリシクラス、それを受け入れる Host のようなクラステンプレートをホストクラスと呼ぶ。

静的多態性はコンパイル時に解決されるため、コンパイル時にオーバヘッドが発生するが、実行時のオーバヘッドは発生しない。

参考:

449988 journal

bnezの日記: [Perl] ハッシュ内の値''を'-'に変更する方法

日記 by bnez

Perlのハッシュ内の値のうち、''を'-'に書き換える方法あれこれ。

お試し用プログラム。@FILLHERE@に中身を入れることで完全なPerlスクリプトになる。

#! /usr/bin/perl

use strict;
use warnings;
use integer;

# main
{
 my $h = { a => 'foo', b => '', c => 'bar' };
 @FILLHERE@
 print join(', ', map { $_ . ' => ' . $h->{$_} } sort keys %$h), "\n";
}
0;

以下、@FILLHERE@に入れるべきコード片を示す。 操作対象の無名ハッシュへのリファレンスは$hである。 なお、操作対象を無名ハッシュへのリファレンスではなくハッシュにしたいなら、 "%$h"を"%h"へ、"$$h{$_}"を"$h{$_}"へと書き換えればよい。

古典的なコード。

foreach (keys %$h) {
 if ($$h{$_} eq '') {
  $$h{$_} = '-';
 }
}

デリファレンスにアロー演算子を使用。
この例題ではあまり効果的ではないが、入り組んだデータ構造を扱う時には可読性の点で有利。

foreach (keys %$h) {
 if ($h->{$_} eq '') {
  $h->{$_} = '-';
 }
}

if文のstatement-modifier化。
"if (EXPR)"は"if EXPR"でもよい。お好みで。

foreach (keys %$h) {
 $h->{$_} = '-' if ($h->{$_} eq '');
}

短絡演算子による文の式化。and演算子を使っている。
&&演算子を使ってもよいが、より優先順位の低いand演算子の方が慣用句として好まれる。 同種の慣用句に"or die"がある。

foreach (keys %$h) {
 $h->{$_} eq '' and $h->{$_} = '-';
}

foreachのstatement-modifier化。
変換前の"foreach EXPR BLOCK"のBLOCK内は、1つの式文でなければならない。 "for (EXPR)"と"for EXPR"のどちらを用いるのかはお好みで。
foreachとforは完全に等価な予約語なので、forをforeachへと書き換えても、あるいはその逆を行っても意味は変化しない。"foreach EXPR BLOCK"ではforeachが、"EXPR for EXPR"ではforが好まれるようである。

$h->{$_} eq '' and $h->{$_} = '-' for (keys %$h);

||=演算子による短縮。
ただし、ハッシュの値に0が入る可能性があり、それを書き換えたくないときには使用できない。

$h->{$_} ||= '-' for (keys %$h);

foreachのループ変数に渡される値がすべて左辺値であることを利用した短縮。
foreachの式にkeyではなくvaluesを使い、各値を$_経由で直接書き換える。

$_ eq '' and $_ = '-' for (values %$h);

ついでに||=演算子による短縮も合わせたもの。

$_ ||= '-' for (values %$h);

mapを用いた別解。
この例題のようにハッシュを上書きで更新するときはあまり有効ではない: 記述が長く、また実行時にハッシュを再構築するので性能面で不利である。 が、得られた結果を別のハッシュに格納するときなどは有効な場合がある。

$h = +{ map { my $v = $h->{$_}; ($_, $v eq '' ? '-' : $v) } keys %$h };
$h = +{ map { ($_, $h->{$_} || '-') } keys %$h }; # || operator version

580267 journal

bnezの日記: C++再履修(3)

日記 by bnez

C++再履修 の続き.下記一通り読了.

  • More Exceptional C++ (洋書)
    Exceptional C++の続刊.Effective C++・More Effective C++・Exceptional C++の3冊に対する補足的な話題とさらに進んだ話題を取り扱ったもの.STL,テンプレート,コード最適化,例外安全性,多重継承,スマートポインタ,名前空間などについて,比較的難易度の高い問題をクイズ形式で論じている.
  • C++プログラミングの処方箋
    C++プログラミングに関する99のアドバイス.プリプロセッサ,型変換,初期化とコピー,リソース管理,多相性,クラス設計,継承設計などについて論じている.Effectiveシリーズなどと比較するとコーディングスタイルにやや難があるなどいくらか問題はあるが,本質的な内容はおよそ的確.細かいテクニックの復習向けか.
  • C++の設計と進化
    C++の産みの親がC++の生い立ちと設計思想を綴った名著.原題は "Design and Evolution of C++",通称D&Eと呼ばれる.原本初刷は1994年3月.前半は時間軸に沿った歴史展望で,C++前史,誕生,言語設計方針,標準化,ユーザコミュニティと教育,標準ライブラリを扱う.後半は重要な言語機能ごとの各論で,メモリ管理,オーバロード,多重継承,高度なクラス概念,キャスト,テンプレート,例外処理,名前空間を扱う.また,日本語版出版にあたって執筆された39ページに及ぶ前書きは,1994年から2005年までのできごと,2005年現在の状況,2005年から先の展望を扱う.C++がなぜこの構成なのか(あるいはなぜこの機能を採用しなかったのか)を深く知ろうとするなら,本書は外せない.原本初刷からかなり時間が経過しているが,言語設計時あるいは執筆時に行われた考察は今も十分に的確である.
  • C++ Coding Standards (洋書)
    C++プログラミングに関する101のルール.著者はExceptional C++のHerb SutterとModern C++ DesignのAndrei Alexandrescu.C++プログラミングを行う際に守るべきルールや指針,実践的な技術を網羅的に列挙したもの.各項目は,内容を簡潔に表現した題名,要約,議論と例,ルールから逸脱すべき条件,参考文献からなる.どのルールも的確かつ明確.ただ,記述が簡潔なので,より詳細な議論を知りたいときは参考文献をあたる必要がある.また,初心者向けの指導のようなルールはない.「変数の命名ルールは?」などという問いに対してはルールゼロが簡潔に答えるのみである: 「何をルール化すべきでないかを知ろう」.なお,項目名だけならば C++ Coding Standards - Book Home Page にて参照できる.

最後の2冊をもって,「ある問題が与えられたとき,それをC++で記述するべきかどうかの判断がつき,また記述するべきであればほぼ的確なC++コードに変換できる」という感触が得られた....何となくだが.

標準ストリームライブラリとロケールまわりを飛ばしてしまったが,必要に迫られたらざっと見ればいい気がするので,再履修はここで一段落としよう.ずいぶん時間がかかってしまった.

581805 journal

bnezの日記: [C,C++] const と volatile

日記 by bnez

CおよびC++のconstとvolatileは,型やオブジェクトの宣言に付加することでその意味の一部を変更する修飾子である.両者をまとめてcv修飾子などと呼ぶ.

オブジェクトを修飾したときの以後の文脈あるいは言語実装系への指示は次の通り.

  • const
    そのオブジェクトへの代入を禁ずる.
  • volatile
    そのオブジェクトの値についての積極的な最適化を抑制する.

見方を変えれば,cv修飾子は次のようなオブジェクトに対して付加することができる,あるいは付加する必要がある,と考えるとよい.

  • const
    「私」が値を変更しないオブジェクト.
  • volatile
    「私以外の誰か」がその値の変化に関与しうるオブジェクト.

ここで言う「私」とは,ごく大ざっぱに言えば動作中のプロセッサのことだ.もう少し丁寧に言えば,コードが定めた実行順序にしたがってプログラムを順に処理する者,のことである.

シングルスレッドの通常のプログラムでは,入出力処理を除けば登場人物は「私」だけである.したがって,const修飾子はもっぱら定数であることを意味する.volatile修飾子を使う必要はない.

しかし,マルチスレッドプログラムではそうではない.複数のスレッドが同時に存在しうる文脈では,別のスレッドが「私以外の誰か」として登場する.スレッド間で共有する定数でないオブジェクトは,別のスレッドにより任意のタイミングで値が変更されうるため,volatile修飾子を使って最適化を抑制しなければならない.volatile修飾子を使わないと,例えば2回目の値の読み込みを行わないという最適化が行われることで,結果的に別のスレッドによる値の更新をうまく関知できなくなるなどといった問題が起きることがある.
(ただし,スレッド処理系によってはvolatile修飾子以外の手段で値の更新の同期化を行っている場合もあるので,何がなんでもvolatile修飾子が必要というわけではない.また,volatile修飾子が面倒を見てくれるのはあくまでも言語の認識範囲内までである: スレッド処理系が明示的な同期化を要求しているならば,volatile修飾子を使った上でさらにその同期化操作を行わなければならない.)

また,同じことがデバイスドライバなどのハードウェア制御プログラムにも当てはまる.このようなプログラムでは,ハードウェアが「私以外の誰か」として登場する.したがって,データバッファやメモリにマップされた制御レジスタなどといった共有資源にはvolatile修飾子を使う必要がある.また,必須ではないが,const修飾子も併せて使うとよい.具体的には次のようにする:

  • ハードウェアが書き込み,プロセッサが読み出す領域: const volatile
  • ハードウェアが書き込み,プロセッサが読み書きする領域: volatile
  • プロセッサが書き込み,ハードウェアが読み出す領域: volatile

この状況下では,const修飾子は必ずしもそのオブジェクトが定数であることを意味しない.const修飾子の本来の意味は「自らによる代入を禁ずる」だけであり,他者が値を更新することには関与しない.「私は読み出すだけで書き込まないが,他者により値が更新される」という領域に対してconst修飾子を指定しておくと,誤ってその領域に書き込む記述をしてしまうことをコンパイルエラーとして検出できる.

cv修飾子は,キャスト(Cならばキャスト,C++ならばconst_castまたはCスタイルのキャスト)により取り外すことができる.しかし,生来のcv修飾子を取り外して操作を行ったときの動作は定義されていない.cv修飾子の除去が合法なのは,「生来はcv修飾されていないオブジェクトを指すcv修飾子付きのポインタまたはリファレンスを,cv修飾子を除去して使用するとき」だけである.例えば次の通り:

int i = 0;
const int ci = 1;
const int* cip1 = &i;
const int* cip2 = &ci;

*(int*)cip1 = 2; /* OK: cip1はiを指しており,iはconstオブジェクトではない */

*(int*)cip2 = 3; /* 誤り: cip2はconstオブジェクトを指している.コンパイルは通るが実行時の動作は未定義 */

C++では,メンバ関数の宣言に対してcv修飾子を指定できる.これは,メンバ関数の暗黙の引数であるthisポインタの型を self_type* から const self_type* や volatile self_type* などへと変更することを意味する.したがって,constオブジェクトに対してはconstメンバ関数だけを呼び出すことができ,そのメンバ関数内ではmutable指定のないデータメンバを変更できない.

参考:

586890 journal

bnezの日記: [C++] delete this

日記 by bnez

C++話.delete this,すなわち「自殺するクラス」について.

delete thisという操作は,不正ではないが注意深く行う必要がある.

ごく単純なコードは次のようになる.

(1)

class A
{
public:
 void suicide() { delete this; }
};

suicideは自分自身を破壊するpublicな非静的メンバ関数で,自らの意志で自 殺する際に他のメンバ関数から呼ばれる.あるいは,オブジェクト自身の手で は自殺が行われない場合に,自殺を行わせるために外部から呼び出される.

suicide内でdelete thisが行われると,thisポインタが指すオブジェクトは破 壊され,記憶領域は開放される.したがって,以後は非静的データメンバへの アクセスと仮想関数呼び出しを行ってはならない.どちらの操作もthisポイン タが指す先を参照するからだ.また,非静的非仮想メンバ関数を呼び出すこと, thisの値を評価することも控えた方が懸命である: これらの操作は通常は致命 的ではないが,意味がないし,その後に致命的操作を伴う可能性が高い.

ここでは,suicideを正しく実装できたとして,それをなるべく安全に用いる ようにする方法について扱う.

(1)のコードは,かなり注意深く使用する必要がある.例えば次のように用い ると,コンパイルには成功するが実行時エラーが発生する.

(1-a)

{
 A a;     // 構築
 a.suicide(); // 自殺による破壊
}        // 実行時エラー: 2度目の破壊

オブジェクトaはsuicideにより破壊され,ブロック終了箇所でもう一度破壊さ れる.2度目の破壊で実行時エラーが発生する.

見方を変えれば,suicideは「独自の方法でオブジェクトを破壊する関数」で ある.このような標準的方法とは相容れないオブジェクトの構築あるいは破壊 を明示的に行う関数を設けたということは,オブジェクトのライフタイム管理 をプログラマの責任で行うようにした,ということを意味する.したがって, グローバルオブジェクトやスタック上のローカルオブジェクトといった処理系 がライフタイムを管理するオブジェクトの存在を禁止するようコードに明示し, 不正をコンパイルエラーとして捕捉できるようにするべきである.

これらを禁止する方法として,コンストラクタすべてをprivateにする方法と, デストラクタをprivateにする方法がある.後者の方法は次の通り.

(2)

class A
{
public:
 void suicide() { delete this; }
private:
 ~A() {}
};

これで,処理系あるいは他のクラスがライフタイムを管理するオブジェクトは 記述できなくなった.

(2-a)

A a1;             // コンパイルエラー: ~A()を呼び出せない

foo()
{
 A a2;            // コンパイルエラー: ~A()を呼び出せない
 std::auto_ptr<A> p1(new A); // コンパイルエラー: ~A()を呼び出せない
 A* p2(new A);        // OK
 p2->suicide();        // OK
}

しかし,まだ抜け道がある.次のplacement newを用いたコードは,一見正当 に見えるが実行時エラーが発生する.

(2-b)

foo()
{
 void* c(operator new(sizeof(A))); // 領域を動的に確保
 A* p(new(c) A);          // 領域上にオブジェクトを構築
 p->suicide();           // 自殺
 operator delete(c);        // 領域開放: 実行時エラーが発生する
}

bar()
{
 char c[sizeof(A)];         // 領域を静的に確保
 A* p(new(c) A);          // 領域上にオブジェクトを構築
 p->suicide();           // 自殺: 実行時エラーが発生する
}

(2-b)のfooは operator delete(c) で実行時エラーが発生する.なぜなら, p->suicide() がオブジェクトの破壊と領域開放の両方を行ってしまっており, 領域cは開放済みであるからだ.また,barは p->suicide() で実行時エラーが 発生する.なぜなら,suicideはオブジェクトの破壊と領域開放の両方を行お うとするが,cは静的であり領域開放はできないからだ.

通常のnewを許可しつつplacement newだけを禁止する方法は,存在するが煩雑 である.それよりも,すべてのコンストラクタをprivateとすることですべて のnewを禁止し,代わりに独自のオブジェクト構築関数を静的メンバ関数とし て用意する方法の方がわかりやすい.

(3)

class A
{
public:
 static A* create() { return new A; }
void suicide() { delete this; }
private:
 A() {}
 A(const A&) {}
 ~A() {}
};

foo()
{
 A* p(A::create()); // OK
 p->suicide();   // OK
}

結局のところ,独自の破壊関数の使用を強制するならば,対応する独自の構築 関数を用意してその使用を強制するべきである,という結論に落ち着くようで ある.

なお,このような標準から逸脱する構築や破壊の使用を強制することは好まし いものではないので,必須の文脈でのみ用いるようにした方がよい.例えば, (2-a)に示したようにこのクラスはstd::auto_ptrのような所有権管理ツールと の併用が難しく,例外安全にするべき文脈で扱いにくいといった問題がある.

(3)のクラスを公開派生に対応させるには次のようにする.

(4)

class A
{
public:
 static A* create() { return new A; }
 void suicide() { delete this; }
protected:
 A() {}
 A(const A&) {}
 virtual ~A() {}
private:
 A& operator=(const A&);
};

class B : public A
{
public:
 static B* create() { return new B; }
private:
 B() : A() {}
 B(const B& v) : A(v) {}
 virtual ~B() {}
};

foo()
{
 A* pa(A::create()); // OK
 pa->suicide();   // OK
 A* pb(B::create()); // OK
 pb->suicide();   // OK
}

公開派生を許容するクラスAでは,まずデストラクタを仮想にする.Aのすべて のコンストラクタとデストラクタを,外部からの呼び出しを禁止しつつBから 呼び出せるようにするためにprotectedにする.派生クラスのオブジェクトが 混在しうる代入はスライスが発生するため,privateとして宣言し定義を与え ないことで禁止する.suicide内のdelete thisは仮想デストラクタにより仮想 的に機能するので,suicideの仮想化は不要である.

派生クラスBでは,これまでの議論の通りすべてのコンストラクタとデストラ クタをprivateにする.B用のオブジェクト構築関数createを定義し, A::createを隠蔽する.破壊関数suicideはA::suicideをそのまま用いる.

見ての通り,Aは派生クラスBに対しcreate/suicideインタフェースを完全には 強制できておらず,Bで問題のあるコーディングが行われることを防げない. こういった意味でAは不完全である: 構築と破壊は単純には継承されないので 無理もない話だが,不完全であることを認識した上で限定的に使用するべきだ ろう.

ここまで書いておいてなんだが,suicideをprivateにしてクラス内からだけ呼 び出せるようにし,クラス外部に対してsuicideの使用を強制しないようにす れば,上記のような苦労はしないで済む(構築と破壊を注意深く行わなければ ならないことに変わりはないが).あるいは,自らを破壊する行為をやめて, 破壊してほしい旨を所有者に依頼するような構成にする方法もあるだろう.そ の方がずっと優れているかもしれない.

参考:

417303 journal

bnezの日記: メモ: 長寿のコツは?

日記 by bnez

今日のNHKスペシャル「老化に挑む#2: 寿命はもっと延びる」より,長寿達成の方法を4つ.

  • 運動
    日々運動すること.有酸素運動でもそうでなくてもよい.運動を続けると,その結果は身体年齢(例えば筋力・骨密度・平衡感覚など)として現れる.
  • カロリー制限
    必要な栄養を維持しつつ摂取カロリーを制限すること.ただし低すぎてもだめ.基本代謝量と生活に必要なエネルギーの合計値が最適摂取カロリーの目安となるようだ.この具体的数値を知るには医療施設での測定が必要となるが,もっとわかりやすい基準として「若いときの体重を生涯維持する」というのが有効とのこと.ちなみに,よく考えれば当たり前だが成長期に行ってはならない.
  • ストレス耐性
    くよくよしないこと.
  • 生きがい
    仕事・趣味・娯楽など,生きがいとなるものあるいは目標を持つこと.

ただし,老化抑制効果があることが医学的に証明されているのは,今のところ「カロリー制限」だけだそうだ(これについても実証済みなのは動物実験まで).

前2つが老化を抑制するメカニズムを,番組では活性酸素と関連づけて説明していた.が,現段階では有力な仮説のひとつといったところだろう.

後2つは感覚的にはわかりやすいが,医学的根拠は特に提示されなかった.数値化が難しいだろうから科学的に扱うのも難しそうである.

613423 journal

bnezの日記: 技術習熟度チェック

日記 by bnez
問いかけによる技術習熟度チェック.
以下の○○に習熟度をチェックしたい技術の名前を入れ,答える.
  1. ○○とは何ですか?
  2. ○○が得意なことは何ですか?
  3. ○○が不得意なことは何ですか?

誰にでもわかる平易な言葉で,簡潔に,的確に.
難解な用語を用いたなら,その用語に対しても同様のチェックを.

452782 journal

bnezの日記: C++再履修(2)

日記 by bnez
すごく間隔が空いてしまったが、C++再履修、継続中。

「Effective C++ 改訂2版」と「More Effective C++」を読み終えたあと、そのまま「Effective STL」に突撃しようかと考えていたが... 冒頭を眺めてSTLの基礎知識が足りないことがわかったので、間に1冊はさむことにした。
  • 「STL --- 標準テンプレートライブラリによるC++プログラミング 第2版」
    原書名 "STL Tutorial and Reference Guide" の表記通り、STLのチュートリアルとリファレンスガイド。前半がチュートリアル、後半がリファレンス(のガイド)となっている。チュートリアル部が非常にわかりやすく、初めてSTLを学ぶにはうってつけ。STLとは何か、何をしてくれるのか、どのぐらいのことができるのかをわかりやすく示してくれる。また、リファレンス部も秀逸な出来で良い感じ。「ガイド」なのでリファレンスとしては完全ではないが、概要を知るのに必要な程度に簡略化されていて吸収しやすい。通して読むとSTLの全体像が掴める... たぶん。

で、準備が整ったところで「Effective STL」。

  • 「Effective STL」
    Effectiveシリーズの3冊目、STLについての50のアドバイス。STLコンテナそれぞれの正しい使い方、反復子、アルゴリズム、関数オブジェクト、その他様々な慣用句など。STLの基本は知っているが、具体的にどうやってコーディングするのが効果的なのかイメージが掴めない、という段階で読むのが良いだろう。swap技法、remove風アルゴリズムの正しい使い方、ptr_fun・mem_fun・mem_fun_refの使い方、一見意味が重複しているコンテナのメンバ関数とアルゴリズムの使い分けなど、実践的なSTL使用法を数多く解説している。

さらに3冊。

  • 「Exceptional C++」
    Effectiveシリーズに似たスタンスで、実践的なC++プログラミングのためのアドバイスを列挙したもの。いろいろ並んでいるが、印象に残っているのは例外安全な設計のための指標とPimplイディオム(クラス内部実装を隠蔽しコンパイル依存を断ち切る手法、Bridgeパターンの一種)かな。例外に関する記述が秀逸で、例外処理について正しく理解するなら必読かも。
  • 「Modern C++ Design」
    テンプレートの実践的な使い方あれこれ。実例はLoki。テンプレート特殊化、ポリシクラスとホストクラスによる設計/実装手法、汎用スマートポインタ、デザインパターンのC++向け実装、マルチディスパッチなど。こうなるともうCとは似ても似つかない設計になる。かなり難易度が高いが、テンプレートの潜在能力を知ろうとするなら読んでおいた方がいいかもしれない。
  • 「マルチパラダイムデザイン」
    和書名からはわからないが、原書名 "Multi-Paradigm Design for C++" にはC++に関する書籍であることが示されている。C++が備える複数のプログラミングパラダイムを、設計者の感覚に頼ることなく分析に基づいて選択し、うまく組み合わせて使おうというもの。分析には、アプリケーションドメイン(問題空間)とソリューションドメイン(実装空間)に対する共通性/可変性分析というものを使う。共通性/可変性分析はシンプルだが非常に強力で、GoFの23のデザインパターンの多くを正規化して扱えるだけの能力を備えている。読解にはC++およびソフトウェア設計に関する広範な知識が必要とされる。消化不良だ...

きりがないような気もするが、さらに継続して履修中。

403776 journal
Linux

bnezの日記: 学習の3レベル

日記 by bnez
Alistair Cockburn著、「アジャイルソフトウェア開発」(原題: "Agile Software Development")を読んだ。ソフトウェア開発方法論に関する本だが、方法論の基礎をなす人間の性質やコミュニケーションなどのポイントをきちんと押さえてあり、各論それぞれについて見ても面白い本だと思う。

この本の冒頭に、「学習の3レベル」という話題がある。曰く、「新しいスキルを学習し習得する者は、従うレベル、取り外すレベル、流れるレベルの3レベルを経る」のだそうだ。3つのレベルの意味は以下の通り:

従うレベル(レベル1)は、新しいスキルに触れ始めた初期段階である。この段階では、成功するひとつの手順を学ぶ。必要とされるものはひとつだけの明解な指示であり、有効な教材は手順を列挙した分厚いマニュアルである。

取り外すレベル(レベル2)では、従うレベルで学んだ各手順の能力とその限界を知る。成功する手順が複数並んでいるときに、各々の性質を判断して適切なものを選択することを学ぶ。

流れるレベル(レベル3)では、もはやどの手順を用いているのかは問題でなくなる。知識は統合され、ある時点でどの手順を用いているのかを意識しなくなる。流れるレベルにいる者に「今どの手順を用いて作業をしているのか」と尋ねても、首を傾げるだろう。特定の手順にどう対応しているのか、どう組み合わせているのか、あるいは何か新しい手順を生み出しているのかなど、彼にとってはどうでもよいことだ。

レベル間の隔たりは大きい。レベル3の知識は、レベル2にいる者にとっては戸惑うような話であり、レベル1にいる者にとっては役に立たない。

対象を特定せずに学習という活動を捉えたものなので、個別の技術領域に当てはめるならそれなりの読み替えが必要だろうが... 大きなものの捉え方として、納得できる部分が多いように感じる。本の内容通りソフトウェア開発プロジェクト管理のスキルによく当てはまるし、あるいはオブジェクト指向プログラミングのスキル、htmlでWebページを作成するスキルなどに当てはめてみてもおもしろそうだ。

スキルを上記の3レベルに分けて考えることは、何かを相手に教えるとき、あるいは伝えるときの指標になる。初心者にはレベル1の知識を、ある程度の経験者にはレベル2の知識を、熟練者にはレベル3の知識を提示するのがよい。適切でないレベルの知識を提示すると、理解されなかったり、誤解されたり、退屈に思われたりする。例えば初心者(レベル1の者)に複数選択肢の中から適切なものを選択する方法(レベル2の知識)を教えようとすると消化不良になるし、経験者(レベル2の者)に作業を依頼するときに詳細な作業手順(レベル1の知識)を指定すると足枷になる。意思疏通がうまく行っていないと感じたなら、想定しているレベルが正しいかどうか確認してみるといいかもしれない。またこの指標は、自らの学習方法を選択する際にも利用できそうである。

typodupeerror

未知のハックに一心不乱に取り組んだ結果、私は自然の法則を変えてしまった -- あるハッカー

読み込み中...