2016/01/29


13年前、僕は vim-dev (Vim の開発グループ)に Vim からソケット通信が出来る関数群のパッチを書いて送りました。

Socket functions for vim. - Yahoo! Groups
https://groups.yahoo.com/neo/groups/vimdev/conversations/topics/32576

その時は Bram (vimboss) に「Python や Perl インタフェースを使えるやないか、もしくは外部コマンドとか」と返されてしまいました。確かに言語拡張を使えば出来ますし、それに処理がブロッキングだったので実はそれほど有益では無かったかも知れません。

その後、このソケット通信のパッチは vimproc の一部に取り込まれ、シーケンシャルなソケット通信は出来る様になりました。しかし Vim には非同期インタフェースがありません。自分でイベントを投げ続けてソケットをハンドリングするしかありませんでした。しかし本日リリースされたパッチ 7.4.1191 で channel という機能により、非同期ソケット通信が出来る様になりました。

Patch 7.4.1191 - Google Groups
https://groups.google.com/forum/#!topic/vim_dev/t6RbXywJZRo

あのパッチを送った頃を知っている KoRoN さんや ynkdir さんは恐らく感慨深い思いではないかと思います。

channel は以下の様に使います。

function! Callback(handle, msg)
  echo a:msg
endfunction
let handle = ch_open("127.0.0.1:5000""raw")
call ch_sendraw(handle, "GET / HTTP/1.0\r\n\r\n""Callback")

ローカルにウェブサーバを立てておき、このコードを実行すると以下の様に表示されます。

channel

raw だけでなく json も扱えます。先日入った jsonencode() を使う事も出来ます。接続関数 ch_open の第二引数に raw を指定すると文字列がそのまま送受信されます。json を指定すると json エンコードされた文字列が送信されます。送信関数は2つあり、ch_sendraw() は生の文字列を、ch_sendexpr() はハンドルと文字列をペアにした配列を送信します。サーバ側はこのハンドルに対して応答を返すと、複数のハンドルを持った vim のどちらに応答するか、といった事が可能になります。

今日はこの ch_sendexpr を使って時刻通知機能を作ってみました。まずサーバを書きます。

package main

import (
    "encoding/json"
    "log"
    "net"
    "time"
)

func main() {
    l, err := net.Listen("tcp"":8888")
    if err != nil {
        log.Fatal(err)
    }
    for {
        conn, err := l.Accept()
        if err != nil {
            continue
        }
        go func(conn net.Conn) {
            defer conn.Close()
            var b [500]byte
            // リクエストを受け取る
            // フォーマットは JSON の配列
            // [handle, msg]
            n, err := conn.Read(b[:])
            if err != nil {
                log.Println(err)
                return
            }
            var v [2]interface{}
            err = json.Unmarshal(b[:n], &v)
            if err != nil {
                log.Println(err)
                return
            }
            // v の1個目には handle が入っているのでそのまま使う
            for {
                // v の2個目を更新して送信する
                v[1] = time.Now().String()
                err = json.NewEncoder(conn).Encode(v)
                if err != nil {
                    break
                }
                time.Sleep(1 * time.Second)
            }
        }(conn)
    }
}

サーバは Accept すると1行リクエストを受け取ってハンドル番号を得ます。その後、そのハンドル番号を指定して現在時刻を送り続けます。Vim script 側は以下の様に書きます。

function! Callback(handle, msg)
  echo "ただ今:" . a:msg
endfunction
let handle = ch_open("127.0.0.1:8888""json""Callback")
echo ch_sendexpr(handle"GET""Callback")

これを実行すると

time server

おぉぉぉぉぉぉ!if_python だとスレッドを使ってバックグラウンドでソケット受信する必要がありましたが、これだと何も意識しなくて良いし、マルチスレッドが起因して落ちる事もない!これで勝つる!!!

今日の出来事は、個人的には数年ぶりの感動でした。これからどんどん非同期通信を使った Vim plugin が現れてくると思うのでとても楽しみです。


2016/01/26


Golang の開発者 Russ Cox 氏が2010年に「変数名の長さ」について書いた文章です。

research!rsc: Names

David Andersen (February 4, 2010 2:37 PM) A few comments: Rob: You wrote, "I could see if j is just ...

http://research.swtch.com/names

全てのプログラマは変数の名前付けに関する哲学を持っている。これは私の哲学である:

名前の長さはその情報の中身を超えるべきではない。 ローカル変数の場合、名前 i は index や idx といった情報を限りなく即決に伝える。 同様に i と j は、i1 や i2 (index1 や index2 はさらに良くない) よりもインデックスを名付ける為の良いペアである。なぜならばそれらはプログラムを斜め読みする時に個別に伝えやすいからである。 グローバルな名前は相対的により多くの情報を伝えなくてはならない。なぜならばそれらは色んなコンテキストに現れるからである。 短かったとしても、この正確な名前は acquire や take_ownership の様な長ったらしい物よりもより多くを伝える事が出来る。 全てを名前で伝える。

何年か前に私はこのメトリックに移行したが、あまりにも多くJavaコード を見てきてしまったせいで最近この言い回しを実体験する事になったのかもしれない。


2015/12/15


linux - .bashrcでexportしたPATHが/procにあるプロセスファイルの環境変数(PATH)と一致しないように見える - スタック・オーバーフロー

Ubunt 15.04 を使っています。 どういう際に利用するかは措いておくとして、 /proc にある各プロセスIDの名前がついたディレクトリにある、 environ というファイルを、プログラミン...

http://ja.stackoverflow.com/q/19984/440

回答の中で答えられている通り、/proc/$$/environ (/proc/self/environ)は、プログラム起動時の環境変数だけが表示される。プロセスが起動中に設定した環境変数は表示されない。

pid1

なんとかして取れないかなーと思って以下のスクリプトを書いた。

#!/bin/bash

if [ "x$2" == "x" ]; then
  GREP=cat
else
  GREP="grep ^$2="
fi

gdb -q --silent -p $1 <<EOF | grep XXXXXXX | cut -c9- | $GREP
set \$i=0
while (1)
  set \$r=*((char**)environ+\$i)
  if (\$r == 0)
    loop_break
  end
  printf "\\nXXXXXXX %s\\n", \$r
  set \$i++
end
EOF

これにプロセスIDを指定して起動すると

pid2

動的に設定された環境変数の値が取れる(第二引数に環境変数名を指定してもok)。仕組みはコードを見ると分かるが、environ のポインタをずらしながら値を表示する gdb スクリプトを実行してる。

ただしこの方法は、あくまで setenv(3) を使って environ を更新するプログラムに限られる。例えば bash のあるバージョンでは environ を更新しないらしく、このスクリプトを持っても環境変数が取れなかった。言語処理系の中には環境変数は自プロセスには反映せず子プロセスの起動時のみ使用する物もあるので、そういった場合には使用出来ない。