Hatena::ブログ(Diary)

コード書きの記録

2010-02-18

新しいプログラミング言語を覚えるときの壁

構文の文法が違うというのはまあ教科書があるから頑張ればどうにかなるとして、それ以外で難しかったこと。

個人的に思い出せる壁を並べてみます。

関数を(変数に入れて)渡し、それを使うという考えがない

クロージャも関数ポインタもコールバックもわかりません。関数が関数を返すこともありうることに思い至れません。

実行コンテキストが認識できない

マルチスレッドが使えません。コールバックの非同期呼び出しとかさっぱりです。ここがわからないとコード記述全体がバッチ実行のようにしか見えません。

再帰の記述がわからない

ローカル変数と引数が呼び出し前と呼び出し先で違う(同じこともありうるが)ということに思い至れず混乱します。

現在の実行位置で実行されないコード記述というものがわからない

無名関数、無名クラス、クロージャ、遅延評価がこれに当たります。そこにあるからここを通れば即実行されるんだろう、ではなく、その時点では定義されているだけ、ということです。

他にも、

  • ローカル変数がわからない
  • 関数の実引数と仮引数の違いがわからない
  • 参照渡しと値渡しの違いがわからない
  • ポインタがわからない
  • 構造体(と共用体)がわからない

といったことがありうるとは思いますが、こちらは私にとってはそうでもありませんでした。

あー、、、ポインタはちょっと難しかったかな。ハコと中身とハコにつけたタグの比喩でどうにかしたかなぁ…タグをつけなおすと当然中身は変わるわけで。

Clojure でテキストファイルを読んでみる。(遅延評価)

遅延評価タイプにしながらも途中で閉じることができるようにすると、こういう感じになるようです。

(use '[clojure.contrib.duck-streams :only (reader)])
(defn file-reader [target]
  (let [r (reader target)]
   {:close (fn [] (.close r))
    :seq
      (letfn [(readline []
        (try
          (let [line (.readLine r)]
            (if line
              (lazy-seq (cons line (readline)))
              (.close r)))
          (catch java.io.IOException _ (try (.close r) (catch Exception ignore nil)))))]
        (readline))}))

;-- 開いて閉じる
(def readme (file-reader "readme.txt"))
((readme :close))

;-- 開いて3行読んで閉じる
(def readme (file-reader "readme.txt"))
(take 3 (readme :seq))
((readme :close))

゚▽゚)なんか面白くなってきた。

Clojure でテキストファイルを読んでみる。

ファイルI/Oが少しなりともわかると応用が効きそうなので試してみました。まずは with-open を使ったものです。

;-- ファイルを行リストにする
(use '[clojure.contrib.duck-streams :only (reader)])
(defn file-to-lines [target]
  (with-open [r (reader target)]
    (loop [buf () line (.readLine r)]
      (if line
        (recur (cons line buf) (.readLine r))
        (reverse buf)))))

;-- 行番号をつける
(map vector (iterate inc 0) (file-to-lines "readme.txt"))

;-- "this" を含む要素だけを取り出す
(filter
  #(not= -1 (.indexOf (% 1) "this"))
  (map vector (iterate inc 0) (file-to-lines "readme.txt")))

;-- "this" を含まない要素だけを取り出す
(filter
  #(= -1 (.indexOf (% 1) "this"))
  (map vector (iterate inc 0) (file-to-lines "readme.txt")))

次に遅延評価でやってみます。途中で失敗したときに閉じてやらなければならないので try - catch を入れています。最後まで読まずに放置するとファイルが開きっぱなしになるので注意が必要です。

(use '[clojure.contrib.duck-streams :only (reader)])
(defn file-to-lines [target]
  (let [r (reader target)]
    (letfn [(readline []
      (try
        (let [line (.readLine r)]
          (if line
            (lazy-seq (cons line (readline)))
            (.close r)))
        (catch java.io.IOException _ (try (.close r) (catch Exception _ nil)))))]
      (readline))))

こういうのを途中で読み終える場合はどうしたらいいんでしょうか。さらに一段ラップして close 用のメソッドを提供するような作りにすればいいのかな。

2010-02-17

Closureによる部分リスト検索(遅延評価)

前エントリの部分リスト検索ですが、別に :all を使わなくとも遅延評価してしまえばいいやということでやってみたら、こんな感じになりました。

(defn indexof
  [coll subcoll & from]
  (let [sub (seq subcoll) length (count sub)
        head (first from) head (cond (nil? head) 0 (< head 0) 0 :else head) ]
    (letfn [(func [i coll]
      (lazy-seq
        (when (not (empty? coll))
          (if (= sub (first (partition length coll)))
            (cons i (func (inc i) (next coll)))
            (func (inc i) (next coll))))))]
      (if (empty? from)
        (func 0 coll)
        (func head (drop head coll)))
    )))

(indexof "たけやのたけやぶにたけたてかけた" "け")
=>(1 5 10 14)

(class (indexof "たけやのたけやぶにたけたてかけた" "け"))
=>clojure.lang.LazySeq

まだサクッとは書けないなぁ…

2010-02-16

プログラミングClojureを読んでみた。これは良い

JVM上で動く言語にはいろいろと興味があって、覚えたり QuickReflector 上に載せてみたりといろいろやっていたのは過去の話。

他のことで忙しくなってしまって熱も冷めてしまったのですが、Clojure は気になっていたので日本語訳が出たと聞き早速購入して読んでみました。

まずは、とりあえず、Java と簡単に繋ぐことができて嬉しい。

部分文字列の検索などは以下のような感じで済みます。

 (.indexOf "abcdefgabcdefg" "cd" )

これだけなら特に言うこともなし。でも、これが部分リストの検索ということになるとちょっと面倒で、見た限りでは見つけられなかったので自分で書いてみました。

(defn indexof
  "Returns position of subcoll in coll. if specified all option, returns list of all positions."
  [coll subcoll & all]
  (let [sub (seq subcoll)
        length (count sub)
        func (fn [coll subcoll from all]
          (let [parts (map vector (iterate inc 0) (partition length 1 (drop from coll)))
                result (map #(+ from %) (for [[idx elm] parts :when (= elm sub)] idx))]
            (if all result (first result))))
        head (first all)]
    (cond
      (nil? head) (func coll subcoll 0 false)
      (= (count all) 1)
        (if (keyword? head)
          (func coll subcoll 0 true)
          (func coll subcoll head false))
      :else
        (func coll subcoll head (first (next all)))
    )))

(defn subcoll?
  "Returns whether subcoll contained in coll."
  [coll subcoll]
  (not (nil? (indexof coll subcoll))))

呼び出すときは以下のような感じで。

(indexof "abcdefgabcdefg" "cd")
(indexof "abcdefgabcdefg" "cd" :all)
(indexof "abcdefgabcdefg" "cd" 4 :all)

とはいえ再帰の最適化の話がまだよくわかってなくて、:all をつけると

 (indexof (range 1 20000) '(2000 2001) :all)

なんかが落ちてしまうわけですが。

それはともかく、SISC で頑張ってみたけどすでに忘れてしまった身としてはこれから楽しみが一つできた気分です。

*PHPとActionScriptにどっぷりな1年でした。

気が回らなくて、こちらいつのまにか1年近く放置していました。

仕事で PHP/ActionScript にどっぷり漬かりつつ REST やら OSGi やら

眺めていたら QuickReflector もすっかり忘れている今日この頃。

ところで PHP で、以下のようなコードを実行してみます。

$a = 1;
$b = 1;
for ( $i = 0; $i < 64; $i++ ) {
  $c = $a == $b ? "matched" : "unmatched";
  echo "{$i}:{$a},{$b}:{$c}\n";
  $a <<= 1;
  $b *= 2;
}

$a と $b がずいぶん違うのですがどうにかなりませんかね。

2009-03-20

JavaScriptでメソッド参照したときの奇妙な動作(解決?)

先日メソッド参照とメソッド呼び出しをした場合に this の値が違う、ということを書きました。(http://d.hatena.ne.jp/serene/20090319/1237451298)

状況が分かりやすいように整理して書き直したコードはこうなります。

function Foo() {};
Foo.prototype.test = function() { return this; };
var foo = new Foo();
var func = foo.test;
alert([
    '1'.concat(func == foo.test    ), // true.
    '2'.concat(foo == foo.test()   ), // true.
    '3'.concat(func() == foo.test()), // false.
    '4'.concat(func() == this      ), // true.
    '5'.concat(this == foo.test()  ), // false.
]);

1 と 2 は期待通りとして、3が変だと思っていたのですが、4 と 5 の結果を考えると意味が分かってきます。testメソッドの中の this は、実際に呼び出されるまでは確定しておらず、呼び出される時点のコンテキストを参照して上が何であるかを決定しています。

func() については呼び出されて初めて中の this が宣言されていることに気づいて、上位の this 即ちグローバルコンテキストを返すことになっているわけです。ただ、だからと言って 単純に func は実は this.func なんだ!と思うとそれは以下の結果のごとく、違います。

function Foo() {};
Foo.prototype.test = function() { return this; };
var foo = new Foo();
var func = foo.test;
alert( func == this.func );  // false.

それでは上位の this をとるなら、以下のように書くとどうなるでしょうか?

function Foo() {};
Foo.prototype.test = function() { return this; };
var foo = new Foo();
var func = foo.test;

function Bar() {};
Bar.prototype.test = function(lambda) { return lambda(); };

var bar = new Bar();
alert([
    '1'.concat(bar.test(func) == bar     ), // false. 上位は bar ではない
    '2'.concat(bar.test(func) == this    ), // true.
    '3'.concat(bar.test(foo.test) == this), // true. 上位が foo ではない
]);

funcあるいはfoo.testが「参照された状況」でコンテキストを拾って this に割り当て、「実際に呼び出された状況」でそれを返すという動作をしていることがわかります。


それでは、あらかじめコンストラクタで設定した値を返すとどうなるか。

function Foo() { this.val = this; }
Foo.prototype.test = function() { return this.val; };
var foo = new Foo();
var func = foo.test;
alert([
    '1'.concat(func == foo.test    ), // true.
    '2'.concat(foo == foo.test()   ), // true.
    '3'.concat(func() == foo.test()), // false.
    '4'.concat(func() == null      ), // true.
    '5'.concat(foo == foo.test()   ), // true.
]);

結局のところ、this が何を指すかというと今のブロックが「参照されたとき」のブロックコンテキスト、ってことになりますね。それが関数なのかグローバルコンテキストなのかによって違いが出てくるということです。

参照と実行で分けて考えるとちょっとすっきりしたかも。

2009-03-19

メインPC復活

電源を買い込んで交換することで復活しました。

  • 電圧異常が起きていた
  • 電源のFANに障害が起きて壊れたため、熱放散がうまくいかなくなっていた
  • 何度か取り付け/取り外しをしていたCPUファンとCPUの間のグリスが固まってしまい、熱が逃げにくくなっていた。

しかし電圧は正常範囲に収まったものの、マザーボードの温度が高いなぁ…うーん

文字列を処理する方法

文字列を処理するといえば、検索、抽出、比較、置換、整形ということに集約できそうですが、どういう手段を使うかについてまとめておきます。

検索

  • 正規表現
  • indexOf メソッド
  • lastIndexOf メソッド
  • カスタム Scanner クラスを利用(自作なりDLなり)

抽出

  • substring メソッド
  • split メソッド
  • trim メソッド
  • java.util.StringTokenizer を利用
  • カスタム Scanner クラスを利用(自作なりDLなり)

比較

  • equals メソッド
  • equalsIgnoreCase メソッド
  • startsWith メソッド
  • endsWith メソッド
  • matches メソッド
  • java.text.RuleBasedCollator を利用(大小関係のルールを任意に定義できる)

置換

  • toUpperCase メソッド
  • toLowerCase メソッド
  • replaceFirst メソッド
  • replace メソッド
  • replaceAll メソッド
  • カスタム Scanner クラスを補助的に利用(自作なりDLなり)

整形

  • 単純に + する
  • concat メソッド
  • StringBuilder クラスを利用
  • StringBuffer クラスを利用
  • java.text.MessageFormat を利用
  • カスタムフォーマッタを利用(自作なりDLなり)

とりあえずそれぞれの目的ごとに最もよく使うであろうものから順に並べてみています。手に負えなくなると下の方が使われていくような感じです。


JavaDoc はいつでも読めるようにしておこう。何がどこにあるかの大体の目処を頭に入れておこう

これを何のために挙げておくかというと、以下のような切り口の主張を私が好まないためです。

  • 「全部正規表現で処理します。応用範囲広いし統一できるから」
  • 「全部 StringBuilder 使います。わかりやすいから」
  • 「全部 + でつくります。最適化されて速いから」

「それで何が悪いの?」と思う方には、今何をしようとしているかを表現するにはもっとうまく役割を理解して構成した方がいいんじゃないの?と問いかけてみたいところではありますが、多分、JavaDoc を参照するような環境が揃っていないとか、本を開く余裕がないとか、そういう事情があるんでしょうが、気がかりです。

せめて java.lang.*、java.io.*、java.text.*、java.util.* はひと通り眺めて、いつでも JavaDoc 開いてサンプルコード書いて使えるようにしておくだけで、複雑なコードを書いてしまったり大きな jar をダウンロードしてきて依存しまくったりせずに済むケースが多いのです。


Reader クラスを活用しよう

文字列を1文字ずつ取り出して何かをするような状況で、

String s = "abcdefg";
for (int i = 0; i<s.length(); i++) {
    switch (s.charAt(i)) {
    case 'a':
      …
      …
    default:
      …
      break;
    }
}

というようなコードを見かけることがありますが、文字構成の前後関係を気にして何か処理をするときにはこのやり方は条件分岐を多数記述することになりがちで、困ったことになることが多いです。どうリファクタリングしてもなかなかうまくいかない。

そんなとき、以下のような記述を行うとうまくいくことが結構あります。慣れると応用範囲がとても広いのですが、上のやり方に慣れすぎると無自覚にも「やってみようとすら思わない」というような壁ができていることがあります。

StringReader r = new StringReader("abcdefg");
int ch;
while ((ch = r.read())!=-1) {
    switch (ch) {
        …
    }
}

「IOException を catch する記述が必要になるからインデント深くなって嫌だ」とかあれこれ理由をつけて遠慮されるものですが、結局のところ「恩恵感じるまでやりこまないと聞いたところでわからない」ってこともあるのかもしれません。

というのはさておき、Readerクラスには以下のような特徴があります。

  • 「文字」およびその配列の読み込みに適したクラスである
  • 文字列、ファイル、ストリームなどいろいろなデータソースから文字を読み込んで処理できる
  • FilterReader を派生させることで、あらかじめデータソースの内容を加工できる
  • 文字列を読みすぎた場合に「読まなかったことにし」「やりなおす」ことができる

これのメリットは以下の通り。

  • 文字列中の位置を示す具体的なインデックスがないため、読み出す順番を間違えない。
  • 途中まで読み出した結果、他のメソッドに処理を移して後続の文字列の処理を任せたいというようなときに、文字列だとsubstringなりインデックス渡しなりをする必要があるが、Reader クラスを使っていれば単に Reader を引き渡すだけとなる。
  • 余分な空白はあらかじめ除去する、とか途中の # から LF まではコメントとみなしてなかったことにする、などといったことを Filter で前処理として抜き取ることができる
  • 複数の文字で特別な意味を持つケース(-- でコメント、など)の場合に「読み戻す」のにインデックスを操作することなく、unread メソッドで文字を「押し戻し」、処理を切り替えたり、予めマークしておいた位置に reset メソッドで戻ったあとで処理をやり直すことができる。

こうやって書いたコードは、まさに文字を処理する記述だけで構成され、現在読んでいる位置の情報が雑味として混じることがありません。


Writer クラスを活用しよう

StringBuffer/StringBuilder のように途中を加工する必要がない場合には、StringWriter を使うことで確定した出力文字列はもう操作できないから意図がはっきりする、というような言い回しで個人的には利用を推奨していましたが、実際には中身が StringBuilder を利用していることもあり、同期がかかって若干処理速度が落ちるというのがあって良いことばかりではありません。

しかし、StringWriter はともかくとして、Writer クラスの活用にはやはり以下のようにメリットがあります。

  • 出力先としてファイル、ストリーム、文字列などがあるが利用する側は与えられた Writer オブジェクトを使うだけである。
  • FilterWriter の派生としてクラスを作る(他の Writer クラスでもかまいはしませんが)ことで、ある出力のついでに他の出力(ヘッダやフッタなど)を追加したり、不適切な出力を抑制したり、バッファリングを行うことができる。

要は本来の処理に関係ない出力される側の事情を Writer クラスが担うことでコードの役割の分離を行い、見通しをよくできるということです。

JavaScriptでのコードの書き方を4種類挙げる

JavaScriptの使い方として、4つのコードの書き方を挙げておきます。

  • スクリプトとして記述
  • 関数として記述
  • オブジェクトとして記述
  • クロージャを利用

日常的に使うのはスクリプトとして記述、関数として記述の二つで十分でしょう。ただし、グローバル変数を使わずに、状態を保持したいという目的でオブジェクトを使うこともあると思います。

なお、コードには分かりやすいようにステートメントの末尾にセミコロンを入れています。多くの処理系ではこのセミコロンはいらないと思います。

スクリプトとして記述

これはどこでも紹介されているのでいまさらな感じがしますが、ただステートメントを並べて結果を得るもので、シェル的な使い方、とでも言えばいいでしょうか。

var sum = 0;
var data = [1,2,3,4,5,6,7,8,9,10];
for (var i in data) {
    sum += data[i];
}
alert(sum);

関数として記述

スクリプトとして記述したものをいつでも呼び出せるようにして使いたい。そういうときの使い方です。

function sum(data) {
    var result = 0;
    for (var i in data) {
        result += data[i];
    }
    return result;
}
alert(sum([1,2,3,4,5,6,7,8,9,10]));

オブジェクトとして記述(1)

prototype.js や jQuery を利用している人は、ライブラリの中でそのようなものが使われていると考えると良いでしょう。オブジェクト指向言語に出てくるクラスと同様の使い方です。

function Calc() {
  // Calc のインスタンスのsumプロパティに該当するコードを割り当てる
  this.sum = function(data) {
    var result = 0;
    for (var i in data) {
        result += data[i];
    }
    return result;
  };
}
var calculator = new Calc();
alert(calculator.sum([1,2,3,4,5,6,7,8,9,10]));

ここで注目すべきは、以下の2点です。

  • インスタンスのプロパティに関数を割り当てることができる
  • function Calc() の中のトップレベルはコンストラクタのように、new したときに実行される

ただし、これは以下のように使うのが普通、というか推奨されると思います。先ほどのものはオブジェクトが new されるたびに function が sum プロパティに割り当てられるのですが、こちらはあらかじめ割り当てられているものが使われます。

function Calc() {};
// Calc クラスの prototype の sum プロパティに該当するコードを割り当てる
Calc.prototype.sum = function(data) {
    var result = 0;
    for (var i in data) {
        result += data[i];
    }
    return result;
};
var calculator = new Calc();
alert(calculator.sum([1,2,3,4,5,6,7,8,9,10]));


new Calc()が実行されるとインスタンスが作られ、prototype プロパティの下位プロパティがインスタンスのプロパティとして複写されます。インスタンスの sum を書き換えても prototype.sum には影響がありません。

function Calc() {};
// Calc クラスの prototype の sum プロパティに該当するコードを割り当てる
Calc.prototype.sum = function(data) {
    var result = 0;
    for (var i in data) {
        result += data[i];
    }
    return result;
};
var data = [1,2,3,4,5,6,7,8,9,10];
var calculator = new Calc();
alert(calculator.sum(data));
calculator.sum = function(data) {return data;}; // 書き換える
alert(calculator.sum(data));                    // 書き換えた sum が動く
alert((new Calc()).sum(data));                  // 元の sum が動く


ところが、生成した後で prototype.sum に変更を加えると、前に生成したインスタンスであってもその影響を受けます。

function Calc() {};
// Calc クラスの prototype の sum プロパティに該当するコードを割り当てる
Calc.prototype.sum = function(data) {
    var result = 0;
    for (var i in data) {
        result += data[i];
    }
    return result;
};
var data = [1,2,3,4,5,6,7,8,9,10];
var calculator = new Calc();
alert(calculator.sum(data));
Calc.prototype.sum = function(data) {return data;}; // 書き換える
alert(calculator.sum(data));                  // 書き換えた sum が動く
alert((new Calc()).sum(data));                // 書き換えた sum が動く


ついでに、関数を退避させておくと、また違う動作が見られます。

function Calc() {};
// Calc クラスの prototype の sum プロパティに該当するコードを割り当てる
Calc.prototype.sum = function(data) {
    var result = 0;
    for (var i in data) {
        result += data[i];
    }
    this.pre = result;
    return result;
};
var data = [1,2,3,4,5,6,7,8,9,10];
var calculator = new Calc();
alert('A:'+calculator.sum(data));
alert('B:'+calculator.pre);
var backup = calculator.sum;
// 書き換える
calculator.sum = function(data) {
    this.pre = data;
    return data;
};
alert('C:'+calculator.sum(data));          // 書き換えた sum が動く
alert('D:'+calculator.pre);                // 結果が反映されている
alert('E:'+backup(data));                  // 退避したものを使う
alert('F:'+calculator.pre);                // 結果が反映されていない!

backup に割り当てられたものは calculator.sum であり、その内部で this.pre = result; としているわけでこれで calculator.pre に 55 が入りそうなものですが、[1,2,3,4,5,6,7,8,9,10] が返されました。

calculator.sum に function を新たに割り当てる時点で、backup の中の this は calculator を参照しなくなります。これって一体どうなってるんでしょうね?

function Calc() {};
// Calc クラスの prototype の sum プロパティに該当するコードを割り当てる
Calc.prototype.sum = function(data) {
    var result = 0;
    for (var i in data) {
        result += data[i];
    }
    this.pre = result;
    return result;
};
var data = [1,2,3,4,5,6,7,8,9,10];
var calculator = new Calc();
alert('A:'+calculator.sum(data));
alert('B:'+calculator.pre);
var backup = calculator.sum;
alert('C:'+calculator.sum(data));          // 書き換えた sum が動く
alert('D:'+calculator.pre);                // 結果が反映されている
alert('E:'+backup([1,2,3]));               // 退避したものを使う
alert('F:'+calculator.pre);                // 結果が反映されていない!
alert('G:'+calculator.sum([1,2,3]));
alert('H:'+calculator.pre);                // 結果が反映されている

prototype.sum を書き換えなくても同様の結果になりました。つまり、backup = calculator.sum とした時点で backup と calculator.sum は厳密には同じものではないということです。考えてみれば、backup における this というのは calculator ではなく、実行コンテキストそのものともいえるわけで、これには気をつける必要がありますね。「どーせ同じとこ指しているんだから、短い変数名で代用して」…とか考えているとよくわからない動作に悩まされることがあるわけです。Cの関数ポインタとは意味が違うところです。

クロージャを利用

ここではクロージャの意味合いを把握できる例を挙げることにします。

function sum(producer) {
    var data = producer();
    var result = 0;
    for (var i in data) {
        result += data[i];
    }
    return result;
}
// ひとまず、その場で関数を定義して sum に渡すと結果が返される。
alert(sum(function(){ return [1,2,3,4,5,6,7,8,9,10]; }));

// クロージャを定義する。この段階では x は宣言されていない。
// function は定義されただけで中身は実行されていない。
// x は、lambda が実行されるときに初めて参照される。
var lambda = function(){ return x; };

// 現在のスコープに変数 x を定義する。
var x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];

// 実行する。sum の中で lambda が実行され、ここでの(sum の外での)
// x が評価されて取り込まれる。
// あとは sum の中の加算処理が動いて合計を返す。
alert(sum(lambda));

// さらに関数をかぶせてみる。sum 内部で producer() が実行される
// までは以下の関数は評価されない。
// 以下のコードでは [1..20]の配列に21を足してできた結果を使って
// 加算を行う
alert(sum(function() { var r = lambda(); r.push(21); return r; }));

// これも気をつけること。lambda() は x のコピーではなくて
// x そのものを返していたため、x 自体が操作されて内容が変わっている。
alert(x);

まあ JavaScript はブロックスコープの変数を持たない実装があるとか(letがあればいいんですが)、純粋な関数型言語ではないということがあり、破壊的な操作をしない、無理にクロージャとしての動作をさせるようなことはしない、というのに気をつけておいた方がいいかもしれませんね。趣味でやる分には十分ですが。

略語に対する違和感から考えるマネジメント・ハザード

ひとつは「ワイヤー」、そして「ジャバスク」。もうひとつ「マシン」。

これらの言葉が(私がかかわった状況で)何を指していたかというと、

  • ワイヤー=基本設計
  • ジャバスク=JavaScript
  • マシン=コンピュータのハードウェア

ということになるわけですが、いつもいつも何か違和感を感じていました。違和感についてちょっと詳しく書いてみます。

「ワイヤー」は「ワイヤフレーム(透視図)」を略したものだと思います。ソフトウェア自体の設計に透視図もなにもあったものではないというのはさておき、枠組みだけでも表現したもの、というような意味合いでこの言葉を使ったのであろうとは推測できます。ただ、「ワイヤー」では何のことやらさっぱり分からない。ジャーゴンでもスラングでもなく、ただの符牒といったところでしょうかね。

「ジャバスク」は個人的には何かとても嫌な感じがします。ナントカスクリプトをナントカスクと略すやり方を他に知らないというのもありますし、対象を代表する名前をさらに省略されては意味が欠けてしまうような印象があるためです。たぶんこの略記は「スクリプト」を発音しにくいと感じた人が言い出したことなんではないかと思ってみたりしますが。ジャバスクリプトという言葉には破裂音が二つもありますし。これはやっぱりジャバスクリプト、と発声してほしいところです。

「マシン」については、まさにリアルな「動く機械(からくり)」がそれであると私が感じていたため、違和感を抱いたものです。また「麻疹」という比較的身近な同音異義語もあります。コンピュータの場合はマシンというにふさわしい稼動部分はファンであったりモータであるわけですが、そこはそもそもコンピュータの要ではないということが、違和感の正体だと思います。コンピュータを「マシン」と呼ぶには概念の拡張が必要で、情報処理を行う部分も機械とみなし、マシンと呼ぶ、としないと違和感が消えませんでした。

そういえば「DOS」はどう呼ぶのかというのを思い出しました。見たときからドスと呼んでいたわけですが、「ドスは短刀を想像するから変だ。ディーオーエスだろう?」と友人から突込みが入ったのは20年以上前。でも普通にドスで通じますよね?いまだとディスクオペレーティングシステムとサービス拒否攻撃の二つの意味がかぶりますがそこは文脈依存で聞き取るとして。あえてドスを避けるなら「ディスクオーエス」かな。

このようなことを書いているのは、どれが正しい呼び方か、ということを示したいわけではありません。しかしどれが正しい呼び方かなんてのはどうでもいいというわけでもありません(そういう場合は言外に「話をしたくない」と言っていることが多い。すべてがそうではないけれど)。

何を示したいのかというと、「今現在の自分が保持している意味解釈を行う仕組みが作り出す感覚」が形成する場の中でのみの判断として「わかった」とか「わからない」とか「心地よい」とか「心地よくない」などと言える、ということです。つまり意見を言うということは自分の意味の場を全てではないにせよ、さらけ出しているのと同じということ。もうひとつ、「意味解釈システム」に変化を加えれば出てくる感覚そのものが変化する可能性があるということ。

それが何を意味するかというとまさしく「人の言葉はあてにならない」。かといって話半分で聞いていていい、というわけでもないのですが、「言質を取った」といってその先の行動を縛るという日常的によく使われる行動が大変奇妙なものだということです。他人の意味解釈システムが固定であることを要求するというのは養老孟司さんの言葉を借りれば「情報として扱おうとしている」ということです。そのようにしたら「人が何を考えているか分からない」といって悩むようなことになるのは当然のことで、一方「人が何を考えているか分からない」のはとりあえず当たり前だと認識してそれを前提とし、それを元にさてこちらはどう行動するかね、と決めるのは似たようであってもまったく違うものになってきます。

さて前置きが長くなりましたが、ここでホームグラウンドのソフトウェア開発の話に戻ります。

オブジェクト指向で物を作るとき、オブジェクトの中身はどうあれインタフェースが一定の応答を保証するならそれでよしとする考え方があります。これは言い換えると、「そのオブジェクトを実際に使わずともその他の状況から判断してそのオブジェクトの振る舞いを確実に予言できる」ことを意味します。もっと言えば、そのようにコードを書けば、書けるように努力すれば、それだけでソフトウェアの品質は上がります。(ここで言う「品質」は「予想もしない動作をしない」という意味です。)しかし、多くのプロジェクトではこれを人の振る舞いを「統制」することで制御しようとしています。決まったパターン、慣れたパターンのなかで思考することで補助的に振る舞いを予言できるようにしているように見えますが、これでは個人が持つ意味の場がそのパターンの中だけで成長することになり、融通がきかない育ち方をすることになりかねません。

まあ人を情報として扱おうとして取替え可能としようとしているところからすると勝手に成長されては困るということもあるかもしれませんが、人はどうあれ勝手に意味解釈のシステムを更新していくものです。それを無視してこのようなやり方を行うというのは、いずれどこかでそのやり方そのものが役に立たなくなりかねない。これは「マネジメント・ハザード」とでも呼んでも良いんじゃないでしょうか。

プロジェクトで作るのは製品ではなくて人、製品はその副産物、そういう考え方をしたほうが良いと思います。この考え方をしてしまうと、あるプロセスを適用すれば物ができる、というわけじゃないことになってしまうため困る人は困るのですが。