なんとなく思い立って、Windows用コマンドラインランチャー「iceberg」をリリースしました。設定はLuaな設定ファイルのみという硬派なソフトなのでエンジニア向けです。

ダウンロードとドキュメントは以下から。とりあえずWindows64bit版しか今はないです。

image

これはなに?

シンプルなコマンドライン型ランチャーです。かなり昔から自分用につくって細々使い続けてきたものなので、ソースは割と悲惨です。何回か頑張って書き直しましたが気力が持ちません。使い続けているだけあってそこそこ安定しているはずです。一方あんまり使っていない機能はしらないうちに動かなくなっているかも知れません。というか動かなくなっていたので公開にあたってちょこちょこ直しました。

開発に至った思想は以下のような感じです。

  • 旧craftlaunchを使っていたのだが以下の不満があった。

    • アイコンくらいほしい。
    • 引数を渡すために ; と書かなくてはならないのが嫌だった。
    • 拡張言語がなにかしらほしかった。

      • そうこうしているうちにPythonで拡張できるcraftlaunchが出てきたが なんとなく自分の思ってたのと違った
    • 設定をバージョン管理システムで管理したかった

      • そうこうしているうちに(ry なcraftlaunchがでてきたんですが…
    • 見た目がもう少し派手でもいいんじゃないかと…

    • 曖昧マッチ(firefoxにffでマッチするような)がほしかった

特徴としては以下かなと。

  • Luaで拡張できる
  • 「普通に」引数が渡せる。ダブルクオーテーションで囲えば空白もOK
  • 前方一致、部分一致、あいまい検索ができる
  • migemo対応
  • 簡易ファイラとしても動作
  • 外部プログラムとの連携機能あり

    • 外部プログラムからicebergの入力欄に文字を入れるとか
  • 空白にもコマンドを割り当てられる

技術的な話

実装言語はC++ & Luaです。なぜLuaかというとやはり組み込みやすさが段違いで、拡張言語としての扱いやすさはPythonより高いと思っているからです。そろそろC#に移行しないといけませんかねえ…。

工夫した点というかハマったのは使用しているGUIライブラリであるfltkの挙動ですね。マルチバイトフォントのレンダリングがとてつもなく遅いのです。なのでリスト表示部分は自前でwin32のAPIを呼んでいます。コマンドラインランチャはサクサク感がウリですから、もたるのはちょっと。ただ入力欄はfltkのままなのでここに長い日本語を一気にぶち込むと少し固まります。とはいえ、こうしてハマってもすぐソースがよめるのがfltkの良いところだと思います。

あと、内部的にutf8で処理してます。はっきりいって無駄が多いんですけど、utf8はいろんなライブラリ混ぜるなら楽ですね。fltkもutf8なら日本語通るし、Luaもutf8なら日本語OK。PCの性能もあがっているので文字コード変換が増えてもつくりが楽だしこれでいいかなあ、と。

結構前から64bit Windowsの環境しかもってないのですが、もちろん昔は32bitで動かしていたのでたぶん、MinGWで git clone , ./tool/install_requires.sh , make dist , make package とやればコンパイルできる…はず。

一応OS依存部分は分岐させてあるので、その気になればMacとかLinuxにも移植できる…はず。


趣味プログラマです、こんにちわ。一応生きてます。

さて、Pythonista各位におかれましてはmoratoriumをエンジョイされていますでしょうか。そろそろライブラリも著名なものはPython3に対応してきましたし、そろそろ本格的にPython3、となっているころ合いですね。というか、Python3に対応してないとちょっと恥ずかしくなってきましたね。

とはいえ、Python2は根強く残るでしょう。というわけでPython2とPython3両方で動くコードを書きましょう。テストも書くのは当たり前ですし、せっかくなのでCIもしましょう。

と私も最近思ったので快適に開発を進めるための環境についてメモです。

  • github : 言わずもがなですね。
  • travis : githubと連携してCIできるサービス。P言語にも対応していて、最近アツいです。
  • tox : 複数のPython処理系でvirtualenvを作りテストを流せる。
  • distribute : 改良版setuptools。
  • pytest : toxと親和性の高いテストツール。noseでもよいですが私はpytestを押します。

Python2とPython3のインストール

あたりまえですが、Python2系とPython3系両方入れておきましょう。

toxのインストール

さて、早速toxを入れていくわけですがこれにはPython2系を使います。

$ wget http://python-distribute.org/distribute_setup.py 
$ python27 distribute_setup.py
$ easy_install virtualenv tox

簡単ですね。

プロジェクトを始める - ファイル・ディレクトリ構成

次に、プロジェクトの基本形を作りましょう。私は以下のような構成にしています。

/
  + - .tox                        : toxにより生成されるディレクトリ。
  + - docs                        : ドキュメント系はココ。
           + - source             : ドキュメントのソース。
           + - build
                     + - html(link to gh-pages)
                                  : sphinxで生成されたHTMLドキュメント。
                                    gh-pagesブランチへのリンク。
  + - src                         : ソースファイル類。
           + - プロジェクト名
           + - tests
  - tox.ini                       : tox設定ファイル。
  - .travis.yml                   : travis設定ファイル。
  - setup.py                      : セットアップスクリプト。
  - MANIFEST.in                   : 配布パッケージ生成用定義ファイル。
  - README.rst                    : 私を読んで。
  - CHANGES.rst                   : 変更履歴。

普通です。ポイントは docs/build/html をgh-pagesへのリンクにすること。これでドキュメントの管理が非常に楽になります。

tox.iniの作成

tox.iniに依存関係と、どの処理系でテストするか、テストはなにを使ってやるかを書きましょう。だいたいの場合、それだけで十分です。

[tox]
envlist = py27,py32

[testenv]
changedir=src/tests
deps=pytest
     pytest-cov
     その他依存パッケージ
commands=
  py.test \
    -rxs \
    --cov-report term-missing \
    --cov テスト対象パッケージ名\
    --basetemp={envtmpdir}  \ 
    []                        

見ての通りの内容です。Pytnon27とPython32でテストします、と。この時処理系のパス(python.exeやpython27などのパス)はOSごとに デフォルト の場所が使われます。 --configure でprefixを指定していたり、Windowsで別フォルダにインストールしている場合は以下のようにします。

[tox]
envlist = py27,py32

[testenv:py27]
basepython=処理系へのパス

[testenv:py32]
basepython=処理系へのパス

[testenv]
changedir=src/tests
deps=pytest
     pytest-cov
     その他依存パッケージ
commands=
  py.test \
    -rxs \
    --cov-report term-missing \
    --cov テスト対象パッケージ名\
    --basetemp={envtmpdir}  \ 
    []                        

とりあえず開発

とりあえず何かコードを書いて、テストも書きましょう。

toxでテスト

ではtoxでテストしましょう。tox.iniがあるディレクトリに移動してから

$ tox

これで

  • それぞれの処理系のvirtualenvの作成
  • 依存ライブラリのインストール
  • テストの実行

が行われ、結果が表示されます。py27だけ流したい場合は tox -e py27 とするか環境変数 TOXENV=py27 を設定したうえで tox とすればOK.

拙作のwebフレームワーク rays ですと以下のような感じです(長いのでpy32の結果のみ)。pytest-covを入れているのでC0カバレッジも表示されます。

_________________________________________________________________________________________ [tox sdist] __________________________________________________________________________________________
[TOX] ***creating sdist package
[TOX] /home/yuin/github/rays$ /opt/python2.7.2/bin/python2.7 setup.py sdist --formats=zip --dist-dir .tox/dist >.tox/log/0.log
[TOX] ***copying new sdistfile to '/home/yuin/.tox/distshare/rays-0.4.0.zip'
______________________________________________________________________________________ [tox testenv:py32] ______________________________________________________________________________________
[TOX] ***creating virtualenv py32
[TOX] /home/yuin/github/rays/.tox$ /opt/python3.2.2/bin/python3.2 /opt/python2.7.2/lib/python2.7/site-packages/virtualenv-1.7.1.2-py2.7.egg/virtualenv.py --no-site-packages py32 >py32/log/0.log
[TOX] ***installing dependencies: pytest, pytest-cov, webtest, sphinx
[TOX] /home/yuin/github/rays/.tox/py32/log$ ../bin/pip install --download-cache=/home/yuin/github/rays/.tox/_download pytest pytest-cov webtest sphinx >1.log
[TOX] ***installing sdist
[TOX] /home/yuin/github/rays/.tox/py32/log$ ../bin/pip install --download-cache=/home/yuin/github/rays/.tox/_download /home/yuin/github/rays/.tox/dist/rays-0.4.0.zip >2.log
[TOX] /home/yuin/github/rays/src/tests$ ../../.tox/py32/bin/py.test -rxs --cov-report term-missing --cov rays --basetemp=/home/yuin/github/rays/.tox/py32/tmp
===================================================================================== test session starts ======================================================================================
platform linux2 -- Python 3.2.2 -- pytest-2.2.3
collected 120 items

test_application.py ........................
test_async_extension.py sss
test_database.py .......
test_defaultattrdict.py .....
test_extension.py .
test_functions.py .........
test_hookable.py .....
test_request.py .....................
test_response.py ...............
test_session.py ............
test_staticfile_extension.py .....
test_templating.py .............
----------------------------------------------------------------------- coverage: platform linux2, python 3.2.2-final-0 ------------------------------------------------------------------------
Name                                           Stmts   Miss  Cover   Missing
----------------------------------------------------------------------------
/home/yuin/github/rays/src/rays/__init__    1648    190    88%   82, 259, 807-808, 824-839, 842-888, 892-901, 909-917, 925-933, 946-954, 1408, 1509, 1595-1597, 1934-1943, 1973, 2033-2063, 2088-2089, 2547-2560, 2563-2571, 2574-2575, 2579-2585, 2589, 2593-2596, 2600, 2604-2615
/home/yuin/github/rays/src/rays/compat        98     42    57%   11-13, 17, 19-20, 67-118
----------------------------------------------------------------------------
TOTAL                                           1746    232    87%
=================================================================================== short test summary info ====================================================================================
SKIP [3] /home/yuin/github/rays/.tox/py32/lib/python3.2/site-packages/_pytest/skipping.py:118: condition: SkipIf._no_gevent

============================================================================ 117 passed, 3 skipped in 14.81 seconds ============================================================================
________________________________________________________________________________________ [tox summary] _________________________________________________________________________________________

.travis.ymlの作成

無事、複数のインタプリタでテストできました。次はCIです。 Travis CI を使うと、githubにpushしたタイミングでビルド&テストが自動でできます。

まずは、Travis CIにgithubのアカウントでログインして、対象のレポジトリでCIを有効にします。

次に、Travis CIでのビルド&テスト設定を.travis.ymlに書きます。ここではtoxを使っていますから、以下のようになります。

language: python
env:
  - TOXENV=py27
  - TOXENV=py32
install: 
  - pip install --use-mirrors tox
script: tox

TOXENV でテストする環境を指定します。今回の例ではpy27とpy32です。

これでgithubにpushするとTravis CIが動くようになりました。

Travis CI + tox快適です

ガンガンPython3対応をしましょう!


Googleが Goという新しいプログラミング言語 を出したようで。早速、インストールして軽くドキュメントを流し読みしてみました。

英語なんて読みたくないよ、という人もいるかもしれないし、誰かの役に立つかもしれないので自分用メモおいときます。完全に自分用なんである程度他の言語の知識がある人向けな上、ざっくり流し読みなんで間違ってるかも。

どんな言語?

  • ネイティブコードを吐く、コンパイル型。
  • 速度はCレベル。
  • GC搭載。ポインタはあるけど、ポインタ演算はできません。
  • 各種アーキに最適化された、それぞれのコンパイラセットを持ちます。例:

    • 6g, 6l : amd64
    • 8g, 8l : i386
  • linux, mac, naclに対応。

  • 動的型言語と静的型言語のおいしいとこどり。

  • concurrent処理が組み込まれてます。

個人的雑感

  • こんな言語設計思想かなあと感じたり

    • とにかく、シンプルな言語に。

      • C++の複雑な部分などはできるだけはずしているような。

        • いわゆるクラスベースのオブジェクト指向はない。

          • 継承はない。
          • あるのはフラットなインタフェース空間のみ。
        • 例外もない。

    • 低レイヤからは離れすぎたくない。

    • concurrentを言語的にサポートしたい。

  • 以下の言語からの影響を感じたり

    • 言語コアはC。
    • C++はパッケージの書き方、記法に影響がみられる。
    • 命名規則や記法にPythonの影響がみられる。
    • concurrentな部分はErlangから影響をうけている。

言語仕様

自分なりに簡単にまとめて見ます。

変数への代入

下へ行くほど省略形。 var もあるから const もあるでよ。

var s string = "";
var s = "";
s := "";

オブジェクト構造

値扱いの型と参照型があります。

  • 値扱い : 代入、関数への私はコピーとなる。配列もまるごとコピーされる。

    • 各種数値 : int , float といったプラットフォームごとにサイズがきまる型と int32 のようにサイズ固定の型。
    • string : 不変、UTF8、ただのバイト配列
    • 配列
    • struct : ユーザ定義型
    • などなど
  • 参照 : 3つのみ

    • map : 辞書
    • slice : 明示的なサイズを持たない、配列のようなもの
    • channel : concurrentで使う

値扱い型のアロケート方法 : new -> T* を返す

var t *T = new(T);

参照型のアロケート方法 : make -> T を返す

m := make(map[string]int);
// これはダメ
var m map[string]int;

配列とスライス

  • 配列は「値」。でユーザがメモリ構造などを読める。スライスはコンパイラがよしなに確保する、参照型。

例えば int 型配列のポインタを受け取る場合。

標準配列(&がいる)

s := sum(&[3]int{1,2,3});
s := sum(&[...]int{1,2,3});

スライス

s := sum([]int{1,2,3});

辞書型

timeZone := map[string]int{
  "UTC": 0*60*60,
  "EST": -5*60*60,
  "CST": -6*60*60,
  "MST": -7*60*60,
  "PST": -8*60*60,
}

seconds, ok = timeZone[tz]
//値がなければokはfalse
if seconds, ok := timeZone[tz]; ok {
    return seconds
}
//消す場合
timeZone["UTC"] = 0, false;

パッケージ、制御構造、型定義、関数とメソッド、インタフェース

命名規則に特徴があります。 大文字始まり以外は外部から見えない が原則。単なる命名規則ではなく、そういう仕様。

パッケージ

パッケージ文でファイルの先頭に書きます。これが基本的な単位となります。

package file

制御構造

特徴は

  • ()がいらない。
  • ifswitch などの条件部に複数の文がかける
  • ループは for のみ( whiledo はない)
  • switch が強力に(内容的にif-else if-elseチェイン)
  • concurrent用に select がある

という点。かと。

ifの例

if i % prime != 0 {
    fmt.Printf("%d", i);
}

switchの例

switch nr, er := f.Read(&buf); true {
           case nr < 0:
               fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", f.String(), er.String());
               os.Exit(1);
           case nr == 0: // EOF
               return;
           case nr > 0:
               if nw, ew := file.Stdout.Write(buf[0:nr]); nw != nr {
                   fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", f.String(), ew.String());
               }
}

caseに条件式がかける。breakは自動でされる。

型定義

type , struct キーワードを組み合わせる。 メンバにメソッドは含みません。

type File struct {
    fd int; // file descriptor number
    name string; // file name at Open time
}

メンバ名が小文字なので、パッケージ外からは見えません。

関数とメソッド

  • 両方とも func キーワードで定義。
  • 違いはレシーバを指定するか否か。 レシーバを明示的に書くところはPythonっぽい。
  • 多値が返せる。

この多値がかえせる、というのがGoでは非常に重要な意味をもっています。

関数定義: 多値を返しています。大文字始まりなので、外部に公開されます。

func Open(name string, mode int, perm int) (file *File, err os.Error) {
     r, e := syscall.Open(name, mode, perm);
     if e != 0 {
         err = os.Errno(e);
     }
     return newFile(r, name), err
}

返す型に (file *File, err os.Error) と変数名がついてますね。これをつけておけば return; ってかくだけでその名前の変数を返してくれます。

func Open(name string, mode int, perm int) (file *File, err os.Error) {
     r, e := syscall.Open(name, mode, perm);
     if e != 0 {
         err = os.Errno(e);
     }
     file = newFile(r, name)
     return;
}

こんな風にもかけるってことです。おそらく、エラー処理が絡む場合とかこっちのほうが書きやすかったりするんじゃないですかね。

クロージャにもなります。いわゆる関数ポインタをとるようなところで、関数がそのままかけて外部変数も見えます。

startServer(func(a, b int) int { return a + b });

メソッド定義。 (file *File) がレシーバの指定。

func (file *File) Close() os.Error {
    if file == nil {
        return os.EINVAL
    }
    e := syscall.Close(file.fd);
    file.fd = -1; // so it can't be closed again
    if e != 0 {
        return os.Errno(e);
    }
    return nil
}

メソッド定義が struct での型定義時ではないことに注目。つまり 組み込み型などに対してもあとからメソッドを作成できるのです 。こんな感じ。

type IntArray []int
func (p IntArray) Len() int { return len(p); }

インタフェース

これ重要。Goはいわゆるクラスがないので、継承などもなく インタフェースによるダックタイピングでそれらを片付けますinterface キーワードで定義。

type reader interface {
     Read(b []byte) (ret int, err os.Error);
     String() string;
}

このように、 ReadString が定義されていればそれは reader なんだ、と考えます( ダックタイピング )。

実行時、動的にインタフェースが実装されているかも検査できます。

s, ok := v.(Stringer);

vStringer インタフェースを満たしていれば

  • sStringer オブジェクトとしてのv
  • oktrue

がかえってきます。

Concurrency

concurrentプログラミングはGoの大きな特徴。 CSP(Communicating Sequential Processes) に基づいてます。並行して動く「goroutines」という軽量プロセスが「channel」を介してやりとり。ガードと多重化のためにselect文があります。

channelは単体ではなくchan 受け渡しする型という感じで書きます。以下はチュートリアルのコードまんまです。

func generate(ch chan int) {
    for i := 2; ; i++ {
        ch <- i // Send 'i' to channel 'ch'.
    }
}

int を扱う channel を受け取ってそれにiを送っていきます。

func filter(in, out chan int, prime int) {
    for {
        i := <-in; // Receive value of new variable 'i' from 'in'.
        if i % prime != 0 {
            out <- i // Send 'i' to channel 'out'.
        }
    }
}

送られた i が一定の条件を満たしていたら、 int を扱う out というchannelに送ります。

func main() {
    ch := make(chan int); // Create a new channel.
    go generate(ch); // Start generate() as a goroutine.
    for {
        prime := <-ch;
        fmt.Println(prime);
        ch1 := make(chan int);
        go filter(ch, ch1, prime);
        ch = ch1
    }
}

channelは参照型なので make で作ります。 go で実行します。これは丁寧に書いた感じ。クロージャを使えばもっとシンプル。

func generate() chan int {
    ch := make(chan int);
    go func(){
        for i := 2; ; i++ {
            ch <- i
        }
    }();
    return ch;
}

多重化とガード

複数のチャンネルをとりあつかって、それらをガードにより振り分けられます。ErlangやScalaでおなじみの書き方です。

func server(op binOp, service chan *request, quit chan bool) {
    for {
        select {
        case req := <-service:
            go run(op, req); // don't wait for it
        case <-quit:
            return;
        }
    }
}

request 型のポインタを扱うchannelと bool 型を扱うchannelを使って、多重化しています。 quit チャンネルに値が送られてくるまでは、送られてきたものから良しなに処理してくれる、という感じですね。

というわけで

なぐり書きしたメモでした。変なことかいてたらすみません。まぁこんな感じな言語かなあ、という程度で。

繰り替えしになりますが、C言語を元にシンプルに保ちながらconcurrentプログラミングしやすくしてます、って感じですね。完全にダックタイピングベースで多値を多様するスタイルはおもしろいですね。なんとなく見た目がキモく感じるのは私の気のせいでしょう。

というか数ヶ月もブログ放置してたのかー。コード書いてないわけじゃないんですけど、たいしたもんかいてないんですよね。割合的には8割がたCかな。月一くらいはブログ書いていきたいなあ・・・