Island Life

2010/02/21

gitのワークフロー

仕事でgitを使い始めた。分散SCMを使ったのは初めてだったので最初は モデルを頭の中に作るのに苦労したが、自分なりのワークフローが 何となく出来てきたのでメモっとく。 gitの機能のまだほんの一部しか使ってないと思うけど。

まず案件の前提。

  • 開発者は10数人。
  • 開発はチケット毎にパッチを作ってレビューを受ける。
  • 本家のリポジトリのブランチは開発バージョンに沿って切られており、 複数バージョンの開発が同時進行してる。

以下のワークフローはこの前提のもとでの作業。 前提が違えばフローも違ってくると思われる。

  1. リモートリポジトリをミラーしているブランチ (e.g. origin/version1.1 に対する version.1.1 とか) は原則として完全に同期させとく。以下のコマンドは適宜実行する。
    $ git checkout version1.1
    $ git fetch
    $ git fetch --tags
    $ git merge origin/vesion1.1
    
  2. チケットが来たら、メインのブランチからトピックブランチを切って移動。 トピックブランチはwork-* と名づけている。
    $ git branch work-FOO
    $ git checkout work-FOO
    
  3. 作業してこまめにコミット。この時のコミットログは自分しか見ないのでメモ程度で良い。
    $ git commit -a -m 'simple log'
    
  4. 作業が終了したら(あるいは適宜)、メインから上流の変更を取り込み。
    $ git merge version1.1
    
  5. conflictしたら編集して、以下の手順で解決。
    $ git add source/with/conflict.cl
    $ git commit -m 'conflict resolution'
    
  6. mergeが済んでテストも通ったら、パッチ作成用ブランチを作る。 パッチ作成用ブランチはwrap-*と名づけている。
    $ git checkout version1.1
    $ git branch wrap-FOO
    $ git checkout wrap-FOO
    
  7. 作業ブランチから変更を取り込み。この時--squashして、コミットをひとつにまとめる。 また、ちゃんとしたフォーマットに沿ったcommit logを用意してコミットする。
    $ git merge --squash work-FOO
    $ git commit -F commit.log
    
  8. これがうまくいったら、メインブランチに変更をマージ。というのは、 パッチをレビューに回すスクリプトが別にあって、 こいつがorigin/${version}と${version}間のパッチを作るようになってるもんで。 パッチを送ったらメインブランチはリモートと同期させておく。 (wrap-FOOをマージした状態のままリモートからmergeするとパッチの順番が 前後して色々面倒になるので)
    $ git checkout version1.1
    $ git merge wrap-FOO
    $ パッチ生成、レビューに回す
    $ git reset origin/version1.1
    
  9. パッチが受理されて本家にpushされたら、wrap-FOOは消していい。 メインブランチもreset --hardでリモートに合わせとく。 work-FOOの方は、後で問題が出てパッチがrevertされた時に使うかもしれないので とりあえず取っておく。
    $ git branch -D wrap-FOO
    $ git reset --hard origin/version1.1
    

wrap-* が必要なのかどうかはまだよくわからない。 こうして書いてみるとversion1.1に直接merge --squashしちゃってもいい感じだけれど、 wrap upからパッチ作成の段階で試行錯誤してる時期があって (rebase -iとか使ってみたり)、 その間に別のトピックブランチを切る必要が出てきた時に 手つかずのversion1.1がある方が便利だったので、こういうフローにした。 全部merge --squash一発で済むならwrap-*を省いてもいいかも。

もっと使い込んだら変わってくるかもしれないけど、今のところはこんな感じ。

Tag: Programming

2010/02/19

マップリテラル

Lispのハッシュテーブルには伝統的にリテラル表記(外部表現)が無い。 他の主要なデータ構造ではread/write invariance (WiLiKi:Scheme:ReadWriteInvariance) が 保証されているので、残念なことである。 ハッシュテーブルは手頃な大きさのマップ(キーから値への写像を表現するデータ構造) に 便利だが、組み込みのマップ型にリテラル表記を備えている言語も増えていて、 Lispに移った時に不便に感じることもある。 (但し、マップを必ずしもハッシュテーブルで実装する必要はない。 Lispの場合、とりあえず読み書きしたかったらalistでいいじゃん、 という要因はあったかもしれない。)

なので、新しいLisp拡張を考える時にハッシュテーブルのリテラル表記を、 と思うのは自然なのだけれど、Common LispやSchemeでハッシュテーブルリテラルが 無いのには理由がある。最大の理由は、 「ハッシュテーブルを再現するには、各要素だけでなくハッシュ関数および比較関数の情報が必要」 という点だろう。

一般的なクロージャのポータブルなリテラル表記は無いので、 これらの関数が自由に指定できる場合、ハッシュテーブルの リテラル表記がいつでも可能とは限らない。 それに関数を書き出せたとしても、 それが人間にとって読み書きしやすいものにはならないかも。 (特に、プログラム中にリテラルとして書きたい場合とか。)

アプリケーションが決まればハッシュ関数と比較関数は固定される ことが多いだろうから、そしたらリードマクロを使って リテラル表現を自由に決めてください、ってのがたぶんCommon Lispの立場。 例えば比較関数をeql決め打ちで

  { :foo => 1
    :bar => 2 }

なんてのをハッシュテーブルとして読み込む、とかいうコードはすぐ書ける。

つまり、ハッシュ関数と比較関数が言語で決め打ちになっていれば (たとえカスタマイズできるとしても、大部分のケースをカバーできる関数がデフォルトに なっていれば)、言語としてリテラルを決めてしまってもいい。 マップリテラルがある他の言語は、そういうことだ。 ハッシュ/比較関数の自由度と、リテラルの便利さがトレードオフになっているわけだ。

Lispには比較関数がいくつもあって、これはプログラマの 選択肢をできるだけ用意しとくって精神なんだろうけど、敢えてデフォルトに 絞り込もうとすると「eqかeql」とequalp (Schemeなら「eq?かeqv?」とequal?) が考えられる。 この二つは甲乙つけがたい。基本的に、前者はオブジェクトとしての同一性、 後者は値としての同一性を見ていると考えられるけど、 どちらも同じくらい必要なことが多いからだ。

ところでClojureはマップリテラルを持っている。 その理由は、Clojureではオブジェクトの同一性よりも値の同一性の方が はるかに重要なので、比較関数を = に決め打ちしてしまってもほぼ問題にならないから。 で、なぜ値の同一性の方がそんなに重要になるかというと、 Clojureのデータ構造が原則immutableだから。 immutableなデータ構造では、値の同一性のみが問題になる (メモリ上の同一番地にあろうが別の場所にあろうが、それらのオブジェクトは 操作に対してまったく同様に振る舞うので、区別できない)。

だもんでClojureに関して言えば、データ構造のmutabilityを捨てたら マップリテラルが自然についてきた、という感じだ。

マップリテラルがあるかどうかには、 「等しい」とは何か、という問いが隠されている、という話。 俺言語を設計するときにでも参考にされたし。

Tags: Programming, Lisp

2010/02/16

「Xで使うべき」だからといって「X以外では使うべきじゃない」ってわけじゃない。けれど…

From バカが征く:

http://blog.practical-scheme.net/shiro/20100215-when-you-should-use-lisp-2

でも、そうだとすると、自分には関係のない世界に なっちゃいますね (笑)。『極めて複雑な処理が必要で』 っていうのは、自分の能力的にないでしょうから。

いや、確かに対比のために「他の言語でも困らないような分野で わざわざLispを使うのはハンディがあるよ」と書いたけれど、 強調したかったのは「これこれこういう分野ならLispの力を活かせるよ」って方。 それ以外の分野であっても周囲の条件次第ではハンディにならない 場合はあるだろう。偶然チームにLisperばかり集まってたとか :-)

それはそれとして、逆に、1つの言語ですべてを済ます ことをあきらめたら、選択肢がかなり広がるともいえるん じゃないでしょうか。Gaucheにしても、Rubyにしても、 C言語との親和性を重視してると思いますし。Clojureに しても、Javaをスッ飛ばしてJNIで書くこともできるん じゃないでしょうか。

もちろん全ては可能であり、そこにはトレードオフがあるだけなんだけど。

複数言語のギャップを越えるオーバヘッドがどれくらいかは ケースバイケースで、それこそ別コンポーネントと言えるくらい 疎結合にできる場合なら問題無いだろうけど、 意味的に単一コンポーネントとして切り離せないんだけど言語を 混ぜて使わなくちゃならない、って場合は、オーバヘッドは結構大きいと思う (例えばバイナリの構造体を共有しなくちゃならないとしたら? 定義を二ヶ所に 書いておきたくはない。ひとつの定義から各言語のソースを生成するのは 定番だけど、それによって確かにビルド工程やデバッグは複雑化する。)

言語を混ぜて使うことをサポートするツールはたくさんあるし、 そういう環境に慣れてればどうってことないと思うかもしれない。 言語に限らず、OSやハードウェアだって、今あるものの上で済ませられる 話はたくさんある。たぶん仕事として喰って行くのに充分なくらい。

でも、時には手を止めて、今見えている地平線の向こうに想いを馳せるのも悪くない。 地平線というのは、今、手にしている道具や手法、そして自分の能力で、 コスト的に限界に達するところ。けれども道具や手法を工夫したら、 能力は変わらなくても、その向こうが見えるかもしれない。 (過去50年のコンピュータエンジニアリングの進化を思えば、 道具や手法が能力よりもはるかに生産性に寄与しているのは確かだろう。 今仕事で組んでるそのコード、全部アセンブラで書けって言われて書ける?)

新しい言語の話やハードの噂がついつい気になっちゃうのって、 今は手がとどかない、地平線の向こうを見てみたいっていう気持ちなんじゃないかな。

Tags: Programming, Lisp

2010/02/15

仕事でLispを使うこと(2)

仕事でLispを使うことの続き。

Lispが力を発揮する一例として、高レベルから低レベルまでの アクセスが必要な場合というのをあげたけれど、もう少し考えを進めてみる。

さくっと作れることが利点である言語、まあいわゆるLLと呼ばれてる 言語同士を比べるとき、「さくっと作れること」という点では それほど差は出ない。その場合、むしろライブラリの豊富さや デベロッパーの多さなど、外部的な要因が重要になる。 なので、既存のライブラリやサービスをうまく利用して新しいサービスを 素早くローンチする、といった分野では、Lispは相対的には不利であろう。

Lisp(やMLやHaskell)が「仕事として向いている」、つまり競争力を持っているのは、 むしろ複雑度が極めて高く、かつ性能も頑健性も要求されるような 分野ではないか。DBMSとかコンパイラとか VMとかOSとか。というのは複雑度が上がるにつれ 検証の手間というのがどんどん増えてくるので、 目的に合わせた漏れの少ない高度な抽象化と、 メタ情報にアクセスして検証を自動化する手法は 不可欠になってゆくだろうと思うからだ。

(余談だが、この観点では、現在のLispはメタ情報の扱いが弱いと思う。 マクロは強力だが、ローカルなソースコードの表面にしかアクセスできない という欠点がある。つまりプログラム全域を見て演繹できる情報を利用できない。)

もちろん、そういうインフラ系に目を向けるからといって、 Lispの持つ動的な性質を活かさないということではない。 繰り返しになるが「インフラも書けるLL」であるところがポイントなのだ。 高性能で頑健なコンパイラ。でも改良したくなったらちょろっとスクリプトを 忍び込ませることができる。そういうノリである。

Paul Grahamが普通のやつらの上を行けとかもうひとつの未来への道で 「サーバ側で走るプログラムは言語の縛りが無いんだから一番強力な言語を使うべき」 と書いた頃は、一般向けサーバサイドアプリの黎明期であったこともあり、 サーバ側の階層をあまり意識せずひとくくりに論じていた。 10年経って、現実にアプリケーションはサーバ側に移動し、 サーバ側で色々なコンポーネントをどう構成するのが良いかという点に 関心は移っている。 一方で、クライアントマシン側にも計算の一部を振り分けることも一般化してきた。

現代のアプリケーションは、クライアント側とサーバ側に分散した 多数のコンポーネントが協調して動作する、分散プログラムだ。 Paul Grahamは、クライアントサイドのアプリはOSに制約されるために OSのAPIによる言語選択のバイアスを受けるとしたが、例えばクライアントサイドの JavaScriptエンジン、とかゲームスクリプティング、なんてものは OSとはあまり関係ないコンポーネントだったりする。

というわけで現在の環境では、クライアントかサーバか、という分け方よりも、 コンポーネント単位で考えるべきだろう。 独立性が比較的高く、そのコンポーネント内では極めて複雑な処理が必要で、 かつ柔軟に進化してゆくことが望ましいもの。そういうところが狙い目だ。

Tags: , Programming, Lisp

2010/02/14

仕事でLispを使うこと

http://www.google.com/buzz/rui314/St7Z3Wm9ZjJ/

Lispはなんとなくすごそうというイメージがあるけど、実際にはそれほどでもない。90年代くらいまではGCがあるというだけで、生産性X倍といえたのかもしれないが、いまは良い他の言語がたくさんあって、言語の日常的な使用例で差が特にあるとは思えない。

[...(中略)...]

Lispで仕事をしたいというのはいいけど、それが極端になってLispでしか仕事をしたくないとかJavaは書きたくないというと、なんかもったいないなと思う。

まったくそのとおり。プログラミング言語なんて道具にすぎない。 エンジニアは仕事に合わせて最適な道具を選ぶのであって、 道具に合わせて仕事を選ぶのではない。

けれども道具に愛着を持つのもまたエンジニアの性であって、 良さげな道具を手に入れたらそれを使ってみたくなるというのも人情だ。

この二つは必ずしも相反するものではない。 自分の得意分野、つまり相対的に自分が他に比べてより多くの価値を生み出せる分野が、 たまたま自分の好きな道具がもっとも適している分野と重なっていれば良い。 もちろん言語の選択には色々な事情が絡んでくるので、 その場合であっても常にLispで仕事ができるというわけではない。 けれども、(1)他ではなかなかできないことをあなたのチームはやれる、 (2)あなたのチームがそれをする時に、Lispを選択すると生産性が最大となる、 というのであれば、Lispを選択しないというのは悪手であろう。

Lispが好きで仕事に使いたいと思っているなら、(2)をクリアするのは簡単だ。 問題は(1)である。他のチームが他の言語を使ってできるのと 同じことを同じように出来るだけでは、意味が無い。 他の条件が同じなら、マイナー言語はメジャー言語より不利なのは間違いない。 エンジニアを探すのも、ライブラリを探すのも、後々のメンテナンスも、メジャー言語を 選んでおいた方が楽になるに決まってる。 よそではできないことができます、そしてその秘密兵器が実はLispなのです、 という順番でなければならない(MLでもHaskellでもいいけれど)。

現在の環境で、そういった意味でのLisp (decentなCommon Lisp処理系)のエッジは、 抽象度の非常に高いところ(DSLとか)から非常に低いところ (ビットを直接触るとかコンパイル結果のネイティブコードを見ながらチューニングするとか) までを単一の言語でカバーしているところだと思う。 今もCommon Lispのコードで共有メモリ上に置かれたバイナリのデータ構造を いじってるのだけれど(事実上、特殊なmalloc/freeを実装している感じ)、 性能を確保しつつメンテナンス可能な範囲に複雑性を収めるには 多分Common LispかC++じゃないと無理だろうなと思う。 とりあえず、それなりの質のネイティブコードを吐けること、 必要ならばさらに高速なネイティブコードを出せるように言語から制御できること、 メモリ上のデータ配置を制御できること、 GCを起こさないコードを書けること、あたりは必須。 (このへんは言語というより処理系の話も含まれるんだけれど、 言語の選択は現在availableな処理系で考えるしかないので、 「そういう処理系が使える」ということ自体はポイントになる)。 それでいて性能クリティカルでないところでは高階関数もGCも使いまくりたいので、 そうなると選択肢はCommon Lispということになる。

JVM上で走るLisp/Scheme処理系というのはその意味で不利な競争を強いられる。 大きな強みである低レベル層へのアクセスが制限されてしまうからだ。 もちろん、JVMはJVMで大きな土俵を用意してるのでその上で競争することにも 意義はあるのだけれど、「他の条件が同じならマイナー言語はマイナーであるだけで 不利になる」というハンディキャップがある。 Clojureのように、今までのメインストリームであまり目にしなかった パラダイムを付加価値として持ち込むという戦略がないと難しいだろう。

なお、Gaucheの狙いは、上で述べたような現行のCommon Lispがカバーするところではなく、 「使い捨てスクリプト」から「大規模なプログラム」へと プログラムが自然に発展してゆけるようにすることにある。

(追記:続きもあるよ)

Tags: Programming, Lisp, Scheme

More entries ...