Quantcast
Channel: Big Sky
Viewing all 121 articles
Browse latest View live

gcc は副作用のある関数呼び出しを含む式評価順序を最適化する。

$
0
0
2013/04/30 タイトル修正

昨日、とある場所でこんな話で盛り上がった。

逆ポーランド計算機を作ろうと思ったんだけど、どうも結果が期待通りにならない。
ソースコードを見せて貰うと以下の様なコードだった。
#include<stdio.h>
#include<stdlib.h>

#define MAX_SIZE 100

int stack[MAX_SIZE];
int stack_pointer = 0;

void push(int data){
  stack[stack_pointer++] = data;
}

int pop(){
  return stack[--stack_pointer];
}

int pop1(int n){
  printf("pop %d\n", n);
  return stack[--stack_pointer];
}


int main(void){
  char s[MAX_SIZE];
  int a, b;

  while( scanf("%s", s) != EOF ){
    switch (s[0]) {
    case '+':
      push(pop() + pop());
      break;
    case '-':
      /*
       * "3 4 -"などを与えると
       * -1ではなく1となってしまう
       * push(-pop() + pop());
       *
       */
      a = pop();
      b = pop();
      push(b - a);

      break;
    case '*':
      push(pop() * pop());
      break;
    default:
      push(atoi(s));
      break;
    }
  }
  printf("%d\n", pop());

  return 0;
}
このコードはちゃんと動作する。足し算と掛け算は間違いなく動作するだろう。でもコメント部に書いてあるコードを有効にすると結果が変わる。
push(-pop() + pop());
もちろん皆さん知ってはいるだろうが、これはスタックから取り出す順に依存する。このコードを書いた人は「3 4 -」という入力を「3 - 4」として処理したいが為に、先に4を取り出して符号を逆転し、3を取り出して加算する事で-1を得るという動作を期待した。
引数の評価順が左から右である事を期待したんですね。
push(-4 + 3);
しかしこれをgccでコンパイルして実行すると結果は -1 ではなく 1 が得られるんです。
なんと gcc は上記のコードを
push(pop() - pop());
に戻してコンパイルしてるんです。試しに
pop1.c
#include <stdio.h>

int a[] = {34}, p = sizeof(a) / sizeof(a[0]);

int
pop() {
  return a[--p];
}

int
main(int argc, char* argv[]) {
  printf("%d\n", -pop() + pop());
  return 0;
}
pop2.c
#include <stdio.h>

int a[] = {34}, p = sizeof(a) / sizeof(a[0]);

int
pop() {
  return a[--p];
}

int
main(int argc, char* argv[]) {
  printf("%d\n", pop() - pop());
  return 0;
}
上記 pop1.c と pop2.c を「gcc -S」でアセンブリ出力して比較して見たら全く同じ出力内容になりました。
    .file   "pop1.c"
.globl a
    .data
    .align 4
    .type   a, @object
    .size   a8
a:
    .long   3
    .long   4
.globl p
    .align 4
    .type   p, @object
    .size   p4
p:
    .long   2
    .text
.globl pop
    .type   pop, @function
pop:
    pushl   %ebp
    movl    %esp, %ebp
    movl    p, %eax
    subl    $1, %eax
    movl    %eaxp
    movl    p, %eax
    movl    a(,%eax,4), %eax
    popl    %ebp
    ret
    .size   pop, .-pop
    .section    .rodata
.LC0:
    .string "%d\n"
    .text
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    pushl   %ebx
    subl    $28, %esp
    call    pop
    movl    %eax, %ebx
    call    pop
    movl    %ebx, %edx
    subl    %eax, %edx
    movl    $.LC0, %eax
    movl    %edx4(%esp)
    movl    %eax, (%esp)
    call    printf
    movl    $0, %eax
    addl    $28, %esp
    popl    %ebx
    movl    %ebp, %esp
    popl    %ebp
    ret
    .size   main, .-main
    .ident  "GCC: (GNU4.4.7 20120313 (Red Hat 4.4.7-3)"
    .section    .note.GNU-stack,"",@progbits
最適化オプションは付けていないので、デフォルトの動作としてこうなります。gcc のバージョンは 4.7.2 です。
この最適化をやめるオプションを軽く探してみましたが見つけられませんでした。
これ、実は clang や MSVC だとこの最適化は行われなくて左から評価が行われる為、どちらも結果は -1 となります。
これは僕の推測なのですが、gccはたぶん、-pop+popについて
  1. 左pop
  2. 符号逆転
  3. 右pop
  4. 加算
という処理ををコンパイル時に最適化して pop-pop にする事で
  1. 本来右pop
  2. 本来左pop
  3. 加算
という風に手数を減らしてるんじゃないかと思ってます。
int
main(int argc, char* argv[]) {
  int c = 1;
  printf("%d\n", -pop()*c + pop());
  return 0;
}
こうすれば評価順をちょっとだけ強制できるけど、これも最適化次第では消されてしまうかもしれないしなによりダサい。 もちろんC言語において関数引数の評価順序は規定されていないし、本来こういう呼び出し方はしてはいけないのは皆さん知ってはいると思います。多くの人はこの問題には直面しないでしょう。
C/C++関数引数の評価順序 - yohhoyの日記

プログラミング言語C/C++では、関数実引数の評価順序は未規定(unspecified)となっている。

http://d.hatena.ne.jp/yohhoy/20120304/p1
しかしながら論理演算は左から右に評価される事を期待している為、この様な副作用のあるコードを書くと処理系によっては足をすくわれる事になるという、なんとも面白い事例でした。

気になったので他の言語ではどうなのか調べてみましたが
perl も
my @a = (34);
print ((-pop @a) + (pop @a));
ruby も
a = [34]
puts (-a.pop + a.pop)
python も
a = [34]
print (-a.pop() + a.pop())
javascript も
log = typeof console != 'undefined' ? console.log : print;
var a = [12];
log(-a.pop() + a.pop());
java も
import java.util.Stack;

public class poptest {
    public static void main(String[] args) {
        Stack<Integer> st = new Stack<Integer>();
        st.push(1);
        st.push(2);
        System.out.println(-st.pop() + st.pop());
    }
}
vim script も(誰も聞いてない)
let s:a = [34]
functions:pop()
  let r = s:a[-1]
  let s:a = s:a[:-2]
  return r
endfunction
echo (-s:pop() + s:pop())
-1 でした。
lisp だと
(setq x '(4 3))
(+ (- (pop x)) (pop x))
こう書けば -1 ですかね。

副作用のある処理を式として列挙記述するのはやめましょう。

jvgrep を iconv 非依存にした。

$
0
0
日本語向け grep コマンド、jvgrep は今まで go-iconv という cgo を使った iconv モジュールに依存していたのだけど、mahonia というキャラクタセットライブラリを使って非依存にした。
mattn/jvgrep - GitHub
https://github.com/mattn/jvgrep
mahonia - Mahonia character-set conversion library for Go - Google Project Hosting

Mahonia is a character-set conversion library implemented in Go. All data is compiled into the execu...

https://code.google.com/p/mahonia/
jvgrep 自体はちょっとファイルサイズが大きくなったけど、ランタイムだと iconv が乗らなくなる分だけメモリ使用量は減ったし、なによりポータブルになった。
ただし mahonia には cp932 などのエイリアスが無いので、JVGREP_ENCODINGS 環境変数を指定していた方は、cp932 を sjis に直す必要があります。

今のところ、dev ブランチですが数日以内には master へブランチにマージします。

gomon と goalert で快適Go言語開発

$
0
0
ディレクトリを監視して、変更があれば指定のコマンドを実行してくれるツール gomon を c9s さんが作ってくれました。
c9s/gomon - GitHub
https://github.com/c9s/gomon
この gomon を活用すべく goalert というツールを作りました。
mattn/goalert - GitHub
https://github.com/mattn/goalert
これらを組み合わせて
$ gomon . -- goalert go build -x
とやっておくと、ソースの修正に伴い goalert が起動し、goalert が go build を実行します。結果としてエラーになった場合は、goalert が Growl サーバに結果を投げてくれます。
goalert
めっちゃ便利になりました。
基礎からわかる Go言語基礎からわかる Go言語
古川 昇
シーアンドアール研究所 / ¥ 2,310 (2012-11-21)
 
発送可能時間:在庫あり。

Go言語で動く mobirc、gomirc で携帯からIRCしよう。

$
0
0
以前から携帯から IRC する手段として mobircを使ってきたのだけど、使ってるサーバのリソースが少なすぎてちょくちょく困ってました。
「これ、Go言語で書いたらパフォーマンスも出るし、使用リソースも減るし万々歳じゃね?」というアホの一つ覚えみたいな動機で作り始めました。
mattn/gomirc - GitHub
https://github.com/mattn/gomirc
mobirc とほぼ同等の機能が動きます。
  • 複数ネットワーク
  • クリッカブルリンク
  • 画像インライン表示
  • 新着件数表示
  • ログイン機能
iPhoneビューも付いてます。
gomirc
あと、バックログ機能が付いてましてサーバを再起動したい場合は CTRL-C すると backlog.json というファイルに最新100件のバックログが保存されます。
config.json は以下の様に記述します。
{
    "irc": [
        {
            "name""freende",
            "host""irc.freenode.net:6668",
            "user""my-username",
            "password""my-password",
            "channels": ["golang-nuts"]
        },
        {
            "name""perl",
            "host""irc.perl.org:6668",
            "user""my-username",
            "password""my-password"
        }
    ],
    "web": {
        "addr"":5004",
        "password""dankogai"
        "root""/mobirc/",
        "backlog""./backlog.json",
        "keywords": ["dan""kogai"]
    }
}
root が指定出来るので、サブディレクトリにインストールしたい人も安心設計。
ちゃんと計測していませんが、perl 版を使っていた頃に比べて top の RSS が 2/3 程度に減りました。
あと先日書いた lingr-ircd を使えばこの gomirc からも扱えますし
mattn/go-lingr - GitHub
https://github.com/mattn/go-lingr
zncを使えば複数のネットワークを束ねる事も出来ます。
znc
これで何時でも何処でも IRC 出来る様になりますね。よろしければどうぞ。
pull リクエストもお待ちしております。

cpp-netlib と picojson で lingr bot 書いた。

$
0
0
おしょうさんのを盛大にパクった。罪悪感はない。
netlib で HTTP Server を書いてみた - C++でゲームプログラミング

netlib で HTTP Server を書いてみた Boost . Asio だけだとしんどそうだったので netlib でちょっと書いてみました。 [環境] netlib 0...

http://d.hatena.ne.jp/osyo-manga/20130511/1368242653
mattn/cpp-lingrbot - GitHub
https://github.com/mattn/cpp-lingrbot
#include <boost/network/include/http/server.hpp>
#include <picojson.h>

struct cpp_bot {
  typedef boost::network::http::server<cpp_bot> server;

  void
  operator ()(server::request const &request, server::response &response) {
    namespace http = boost::network::http;
    typedef server::string_type string;
    string body = request.body;
    std::ostringstream data;

    std::cout << body << std::endl;
    picojson::value v;
    string err;
    picojson::parse(v, body.begin(), body.end(), &err);
    if (!err.empty()) {
      response = server::response::stock_reply(
        server::response::bad_request, "invalid request"
      );
      return;
    }

    if (!v.is<picojson::object>()) {
      response = server::response::stock_reply(
        server::response::bad_request, "invalid request"
      );
      return;
    }
    picojson::array arr;
    picojson::object obj;
    
    obj = v.get<picojson::object>();
    v = obj["events"];
    if (!v.is<picojson::array>()) {
      response = server::response::stock_reply(
        server::response::bad_request, "invalid request"
      );
      return;
    }

    arr = v.get<picojson::array>();
    BOOST_FOREACH(auto x, arr) {
      if (!x.is<picojson::object>()) {
        continue;
      }
      picojson::value message = x.get<picojson::object>()["message"];
      if (!message.is<picojson::object>()) {
        continue;
      }
      picojson::value text = message.get<picojson::object>()["text"];
      if (!text.is<string>()) {
        continue;
      }
      string str = text.get<string>();
      if (str.find("cpp"0) != string::npos) {
        data << "くぴぴ\n";
      }
    }

    std::string ret = data.str();
    size_t e = ret.find_last_not_of(\t\r\n");
    if (e != string::npos) {
      ret = string(ret, 0, e);
    }
    response = server::response::stock_reply(
      server::response::ok, ret
    );
  }

  void
  log(...){
    
  }
};


int
main(){
  typedef cpp_bot::server server;
  
  auto address = "localhost";
  auto port = "11614";
  try{
    cpp_bot handler;
    server server_(address, port, handler);
    server_.run();
  }
  catch(std::exception& e){
    std::cout << e.what() << std::endl;
    return 1;
  }

  return 0;
}
cpp という単語が含まれた発言に反応して「くぴぴ」と80年代アニメ風に応答します。

Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
スコット・メイヤーズ
ピアソン・エデュケーション / ¥ 3,990 (2006-04-29)
 
発送可能時間:在庫あり。

Vimを使ったGo言語開発手法

$
0
0
先日、Go言語バージョン1.1がリリースされました。安定しているのは勿論、幾つか新機能が追加されましたが、何よりもパフォーマンスチューニングが施された一番嬉しいですね。
Go 1.1 performance improvements | Dave Cheney

This is the first in a series of articles analysing the performance improvements in the Go 1.1 relea...

http://dave.cheney.net/2013/05/21/go-11-performance-improvements
さて今日はVimを使ってGo言語を開発する方法を紹介したいと思います。
VimでGo言語を開発するには、Go言語のリポジトリに含まれる misc/vimにランタイムパスを追加します。以下を vimrc に追加します。
setrtp+=$GOROOT/misc/vim

入力補完を行う

入力補完として gocode を入れます。正直、これは必須と言っていいです。
$ go get github.com/nsf/gocode
を実行します。$GOPATH/binにインストールされるのでパスを通しておきましょう。
$GOROOTに入ってしまった人は一度削除して、$GOPATHを設定後にもう一度、上記のコマンドを実行します。
$GOPATHにインストールされているはずなので以下の様にvimrcに追加します。
exe "set rtp+=".globpath($GOPATH"src/github.com/nsf/gocode/vim")
gocodeを入れると、パッケージ名の途中であったり変数の後の "."をタイプした後<c-x><c-o>をタイプすると、あり得る関数名や、メソッド名がずらずらーっと補完出来ます。
gocode
その際、completeoptというオプションに previewを足しておくと、補完内容が詳細に表示されて分かりやすくなります。
set completeopt=menu,preview

パッケージをインポートする

Go言語でも、欲しい機能があればパッケージをインポートします。その際、いちいちファイルの先頭に移動して、importを追加したりしてませんか?上記のオフィシャルが提供しているVimプラグインを使っているのであれば、以下の様に実行しましょう。
:Import fmt
before
package main

func main() {
    fmt.Println("hello world")
}
after
package main

import (
    "fmt"
)

func main() {
    fmt.Println("hello world")
}
カーソルの移動もありませんので、引き続きコーディングが再開出来ます。また、コマンドの引数でパッケージ名の補完が効きます。net/htまでタイプして <tab>をタイプすれば net/httpを補完してくれますし、ありえる候補も補完してくれます。

ドキュメントを見る

Go言語には godocというツールが付属しています。コマンドラインから使っても便利なのですが、Vimから使うともっと便利になります。
:Godoc net/http
godoc

:Importと同様にパッケージ名が補完出来ます。

整形する

Go言語には go fmtというコマンドが付属しており、相応しいソースに整形する事が出来ます。
インデントが崩れたりした場合でも
:Fmt
を実行するだけで、綺麗なソースコードに整形されます。

テンプレートを使う

手前味噌ですが、僕が作ってる sonictemplate-vimを使うと、ソースの書き始めが一気に楽になります。
mattn/sonictemplate-vim - GitHub
https://github.com/mattn/sonictemplate-vim
これを bundle ディレクトリ等に入れておき、goのファイルを新規で開きます。そこで
:Template web-app
を実行すれば以下のコードが展開されます。
package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/"func (w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "")
    })
    http.ListenAndServe(":8080"nil)
}
この後、http.ListenAndServe(":8080", nil)の1行上で空行を開け、:Template <tab>をタイプすると、web-app に特化した候補が優先的に表示されます。
sonictemplate
sonictemplate-vim はGo言語だけでなく、他の言語でも同様に便利なテンプレートが用意されています。よろしければ使ってみて下さい。また pull-req によるテンプレートの追加もお待ちしています。

これだけあれば、この夏のビーチでモテモテ間違いなしですね!

基礎からわかる Go言語基礎からわかる Go言語
古川 昇
シーアンドアール研究所 / ¥ 2,310 (2012-11-21)
 
発送可能時間:通常1~4週間以内に発送

Vim で幅跳び

$
0
0
先日、某所で何人かに遊んで貰ったので、ここにも書いて皆に遊んでもらおうと思います。
mattn/habatobi-vim - GitHub
https://github.com/mattn/habatobi-vim
:Habatobi
を実行すると起動します。j と k を交互にタイプすると走り出すので jkjkjkjkjkjkjkjk... とタイプして下さい。
赤い線の前でスペースキーをタイプするとジャンプします。
記録が出て、どうこうなる訳ではないですが息抜きのつもりでどうぞ。

Google Chrome を更新したら「最近閉じたタブ」が表示されなくなる問題の解決方法

$
0
0
Google Chrome を使っていて「新しいタブ」を開き、右下にある「最近閉じたタブ」で閉じてしまったタブを復帰させる事が良くあります。
recent closed tab
Google Chrome Dev Channel を使っているのですが、どうやら Google Chrome をアップデートするとこのツールバー自身が消えてなくなる様なのです。この機能のヘビーユーザだったのでかなり困りました。
いろいろ調べたところ、どうやら以下の設定で直す事が出来る事が分かりました。
  1. chrome://flags/#enable-instant-extended-apiを開く
  2. 設定を「規定」から「無効」に変更する。
  3. ブラウザを再起動する。
この3手順で、以前と同じツールバーで「最近閉じたタブ」が表示される様になりました。

Go言語で日付処理

$
0
0
元ネタ: 誰もが一度は陥る日付処理。各種プログラミング言語におけるDateTime型/TimeStamp型の変換方法のまとめ
Go言語が無かったので書いてみた。

現在時刻の取得

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Now())
}
2013-06-19 21:46:14.186298 +0900 +0900

Time => Unix時刻変換

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Now().Unix())
}
1371646123

Unix時刻 => Time変換

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Unix(13716461230))
}
ここからが面白くて、通常日付のフォーマットやパースは殆どの言語では %Y%m%d といった表記を使います。 しかしGo言語では、ある固定の数値を用いて表現する事で、いかにも日付らしく表現出来る手法を取っています。
package main

import (
    "fmt"
    "log"
    "time"
)

func main() {
    t, err := time.Parse(
        "2006-01-02 15:04:05 -0700",    // スキャンフォーマット
        "2013-06-19 21:54:23 +0900")    // パースしたい文字列
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(t)
}
Formatも同じく
package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Now().Format("2006/01/02 15:04:05 MST"))
}
2013/06/19 22:01:01 +0900
この数値とタイムゾーン文字列で構成された文字列を、定数として提供しています。
const (
    ANSIC       = "Mon Jan _2 15:04:05 2006"
    UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
    RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
    RFC822      = "02 Jan 06 15:04 MST"
    RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
    RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
    RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
    RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
    RFC3339     = "2006-01-02T15:04:05Z07:00"
    RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
    Kitchen     = "3:04PM"
    // Handy time stamps.
    Stamp      = "Jan _2 15:04:05"
    StampMilli = "Jan _2 15:04:05.000"
    StampMicro = "Jan _2 15:04:05.000000"
    StampNano  = "Jan _2 15:04:05.000000000"
)
この手法を使う事で、ちょっとした日付フォーマットの差異も自分のプログラムで吸収する事が出来る様になっています。
初めてこの実装を見た時は戸惑いましたが、慣れると非常に心地よくなります。

おまけでもう少し。

Go言語では、timeパッケージを使って経過時間(Duration)も表す事が出来ます。
time.Sleep(1 * time.Second) // 1秒
time.Sleep(2 * time.Minute) // 2分
time.Sleep(3 * time.Hour)   // 3時間
1時間と61秒は、61分
(1 * time.Hour + 61 * time.Second).Minutes()
日付の加算も出来る。閏年も完璧。
package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Date(20122281213140, time.UTC).AddDate(001))
    // 2012-02-29 12:13:14 +0000 UTC
    fmt.Println(time.Date(20122291213140, time.UTC).AddDate(001))
    // 2012-03-01 12:13:14 +0000 UTC
}
このDurationを使って、数秒後に発動するイベント(channel)も作れます。例えば、3秒以内に CTRL-C を押すかのイベントループ
package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    s := make(chan os.Signal)
    signal.Notify(s, syscall.SIGINT)

    select {
    case <- time.After(3 * time.Second):
        fmt.Println("Timeout")
        break
    case <- s:
        fmt.Println("Pressed CTRL-C")
        break
    }
}
3秒後にコールバック
package main

import (
    "fmt"
    "os"
    "time"
)

func main() {
    time.AfterFunc(3 * time.Second, func() {
        fmt.Println("Timeout")
        os.Exit(0)
    })

    select {}
}
とまぁ、とても便利になっています。Go言語触った事ないとか、かなりヤバいです。

「えーマジGo言語童貞!?」
「Go言語童貞が許されるのは小学生までだよね」
「キモーイ」
「キャハハハハハハ」

組み込み版 NoSQL、「UnQLite」

$
0
0
unqliteこれまで組み込みDBだと SQLite が一般的でしたが、ローカルであっても NoSQL したい、組み込みで使いたい、というニーズはあるかと思います。 そんな場合、UnQLite を使うと便利そうです。
UnQLite - An Embeddable NoSQL Database Engine

UnQLite is a self-contained C library without dependency. It requires very minimal support from exte...

http://unqlite.org/
unqlite/unqlite - GitHub

README.md UnQLite UnQLite is a in-process software library which implements a self-contained, server...

https://github.com/unqlite/unqlite
特徴としては以下の通り。
  • サーバの要らない NoSQL データベースエンジン
  • トランザクショナル(ACID)データベース
  • ゼロコンフィグレーション
  • 単一のデータベースファイルでテンポラリファイルを使用しない
  • クロスプラットフォームなファイルフォーマット
  • UnQLite は依存の無いCライブラリを内蔵している
  • 標準でキーバリューストアを提供している
  • Jx9によるドキュメントストア(JSON)データベース
  • カーソルをサポートしており、リニアレコードをトラバース出来る
  • プラッガブルランタイムによりストレージエンジンを交換出来る
  • ディスクだけでなくインメモリデータベースもサポート
  • O(1) での検索をサポートした強力なストレージエンジンを構築
  • スレッドセーフで再入可能
  • 単純で綺麗で簡単にAPIが使える
  • テラバイトサイズのデータベースをサポート
  • BSDライセンス
  • 融合: UnQLiteとJx9が単一のソースコードで結合されたC言語ソースファイル
  • 可用性の高いオンラインサポート
詳しい詳細は以下を参照
UnQLite - Distinctive Features

The following page enumerates distinctive features of the UnQLite Database Engine and the Jx9 Embedd...

http://unqlite.org/features.html
試しに幾らかプログラムを書いてみた。
まずは単純にストアする物。
#include "unqlite.h"

int
main(int argc, char* argv[]) {
  int rc;
  unqlite *db;
  char buf[256];
  unqlite_int64 buflen = sizeof(buf);

  rc = unqlite_open(&db, "test.db", UNQLITE_OPEN_CREATE);
  if (rc != UNQLITE_OK) return;

  rc = unqlite_kv_store_fmt(db, "mattn", -1"俺の%s""塩");
  if (rc != UNQLITE_OK) goto leave;

  rc = unqlite_kv_fetch(db , "mattn", -1, buf, &buflen);
  if (rc != UNQLITE_OK) goto leave;

  buf[buflen] = 0;
  puts(buf);

leave:
  if (rc != UNQLITE_OK) {
    const char *pbuf;
    int len;
    unqlite_config(db, UNQLITE_CONFIG_ERR_LOG, &pbuf, &len);
    if (len > 0) {
      puts(pbuf);
    }
    if (rc != UNQLITE_BUSY && rc != UNQLITE_NOTIMPLEMENTED) {
      unqlite_rollback(db);
    }
  }
  unqlite_close(db);
}
使い方はそんなに難しくないですが、エラーを取るのに何手か掛かるのは少し面倒臭いです。 次に結合
#include "unqlite.h"

int
main(int argc, char* argv[]) {
  int rc;
  unqlite *db;
  char buf[256];
  unqlite_int64 buflen = sizeof(buf);

  rc = unqlite_open(&db, "test.db", UNQLITE_OPEN_CREATE);
  if (rc != UNQLITE_OK) return;

  rc = unqlite_kv_store(db, "mattn", -1"じゅげむ"12);
  if (rc != UNQLITE_OK) goto leave;

  rc = unqlite_kv_append(db, "mattn", -1"じゅげむ2"12);
  if (rc != UNQLITE_OK) goto leave;

  rc = unqlite_kv_append(db, "mattn", -1"ごこうの"12);
  if (rc != UNQLITE_OK) goto leave;

  rc = unqlite_kv_append(db, "mattn", -1"すりきれ"12);
  if (rc != UNQLITE_OK) goto leave;

  rc = unqlite_kv_fetch(db , "mattn", -1, buf, &buflen);
  if (rc != UNQLITE_OK) goto leave;

  buf[buflen] = 0;
  puts(buf);

leave:
  if (rc != UNQLITE_OK) {
    const char *pbuf;
    int len;
    unqlite_config(db, UNQLITE_CONFIG_ERR_LOG, &pbuf, &len);
    if (len > 0) {
      puts(pbuf);
    }
    if (rc != UNQLITE_BUSY && rc != UNQLITE_NOTIMPLEMENTED) {
      unqlite_rollback(db);
    }
  }
  unqlite_close(db);
}
伸長も楽ちん。
実は UnQLite には Jx9 という組み込み言語が内蔵されており、データベースをスクリプトで操作出来る様になっています。
#include "unqlite.h"
#include <stdio.h>

static int
printer(const void *out,unsigned int len,void *data) {
  return fwrite(out, len, 1stdout) > 0 ? UNQLITE_OK : UNQLITE_ABORT;
}

int
main(int argc, char* argv[]) {
  int rc;
  unqlite *db;
  unqlite_vm* vm;

  rc = unqlite_open(&db, "test.db", UNQLITE_OPEN_CREATE);
  if (rc != UNQLITE_OK) return;

  rc = unqlite_compile_file(db, "mattn.jx9", &vm);
  if (rc != UNQLITE_OK) goto leave;

  rc = unqlite_vm_config(vm, UNQLITE_VM_CONFIG_OUTPUT, printer, 0);
  if (rc != UNQLITE_OK) goto leave;

  rc = unqlite_vm_exec(vm);
  if (rc != UNQLITE_OK) goto leave;

leave:
  if (rc != UNQLITE_OK) {
    const char *pbuf;
    int len;
    unqlite_config(db, UNQLITE_CONFIG_ERR_LOG, &pbuf, &len);
    if (len > 0) {
      puts(pbuf);
    }
    if (rc != UNQLITE_BUSY && rc != UNQLITE_NOTIMPLEMENTED) {
      unqlite_rollback(db);
    }
  }
  if (vm) unqlite_vm_release(vm);
  unqlite_close(db);
}
この様に Jx9 ファイルをコンパイル実行するコードを書いて
if (!db_exists('users')) {
  $rc = db_create('users');
  if (!$rc) {
    print db_errlog();
    return;
  }
}
$users = [
{
   name: 'james',
   age : 27,
   mail: 'dude@example.com'
},
{
   name: 'robert',
   age : 35,
   mail: 'rob@example.com'
},
{
   name: 'monji',
   age : 47,
   mail: 'monji@example.com'
},
{
   name: 'barzini',
   age : 52,
   mail: 'barz@mobster.com'
}
];
$rc = db_store('users', $users);
if (!$rc) {
  print db_errlog();
  return;
}
$rc = db_store('users', {name: 'mattn', age: 18, mail: 'mattn.jp@gmail.com'});
if (!$rc) {
  print db_errlog();
  return;
}
print "Total number of stored records: ", db_total_records('users'), JX9_EOL;
スクリプトを書きます。実行すると users データベースに JSON の様な構造でデータが格納されます。

なかなか面白いですね。
もう少し拡張すれば、ひとりぼっちTreasure Dataも作れそうな気がしました。
残念ながら、他の NoSQL と速度比較した資料を見つけ出せなかったので、時間が出来たらベンチマークも走らせてみたいと思いました。
これからこの UnQLite の各言語バインディングも出てくるんじゃないかと思います。

C言語から使えるJSONパーサ、jansson がとても直感的で良い

$
0
0
おなじみC/C++から使えるJSONライブラリを紹介するコーナー。まずは過去のまとめ。
最近は結構 matsuu さんのブクマから見つけて記事を書いてたけど今日はそうじゃない所からご紹介。
Jansson — C library for working with JSON data

Jansson Jansson is a C library for encoding, decoding and manipulating JSON data. It features: Simpl...

http://www.digip.org/jansson/
特徴としては
  • 簡単で直感的な API とデータモデル
  • 包括的なドキュメント
  • 他のライブラリへの依存がない
  • ユニコードのフルサポート (UTF-8)
  • 徹底したテストスーツ
こんな感じ。いつもの様にサンプル。Twitter API がバージョン 1.1 になって認証無しでは殆ど機能しなくなったので github API を使う。
#include <assert.h>
#include <string.h>
#include <memory.h>
#include <curl/curl.h>
#include <jansson.h>

typedef struct {
  char* data;   // response data from server
  size_t size;  // response size of data
} MEMFILE;

MEMFILE*
memfopen() {
  MEMFILE* mf = (MEMFILE*) malloc(sizeof(MEMFILE));
  if (mf) {
    mf->data = NULL;
    mf->size = 0;
  }
  return mf;
}

void
memfclose(MEMFILE* mf) {
  if (mf->data) free(mf->data);
  free(mf);
}

size_t
memfwrite(char* ptr, size_t size, size_t nmemb, void* stream) {
  MEMFILE* mf = (MEMFILE*) stream;
  int block = size * nmemb;
  if (!mf) return block; // through
  if (!mf->data)
    mf->data = (char*) malloc(block);
  else
    mf->data = (char*) realloc(mf->data, mf->size + block);
  if (mf->data) {
    memcpy(mf->data + mf->size, ptr, block);
    mf->size += block;
  }
  return block;
}

char*
memfstrdup(MEMFILE* mf) {
  char* buf;
  if (mf->size == 0return NULL;
  buf = (char*) malloc(mf->size + 1);
  memcpy(buf, mf->data, mf->size);
  buf[mf->size] = 0;
  return buf;
}

int
main() {
  CURL* curl;
  MEMFILE* mf = NULL;
  char* js = NULL;
  int i;

  mf = memfopen();

  curl = curl_easy_init();
  curl_easy_setopt(curl, CURLOPT_URL, "https://api.github.com/legacy/repos/search/unko");
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, mf);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memfwrite);
  curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  js = memfstrdup(mf);
  memfclose(mf);

  json_error_t error;
  json_t *result = json_loads(js, 0, &error);
  if (result == NULL) {
    fputs(error.text, stderr);
    goto leave;
  }
  json_t *repositories = json_object_get(result, "repositories");
  json_t *repository;
  json_array_foreach(repositories, i, repository) {
    printf("%s/%s%s\n",
      json_string_value(json_object_get(repository, "username")),
      json_string_value(json_object_get(repository, "name")),
      json_string_value(json_object_get(repository, "description")));
  }

  json_decref(result);
leave:
  free(js);
}
実行結果
mizzy/unko: テスト用
satorunet/unko: 
siyo/unko: unko
kotatsumikan/unko: ファイルの内容を「うんこ」に変換するコマンドです。
karr3304/unko: ブログシステム
t11086/unko: 勉強中
keamano/unko: うんこアプリ
nmbakfm/unko: install unko command on your PC
nanananamememe/unko: 
fivestar/unko: test
smellman/unko: まるでくそのようだ
susuhushi/unko: github
kjwtnb/unko: いろいろと書き捨て
kamiyama/xunko: unko
FromAtom/Test: Unko
tam33363/unko_wave: 
konyavic/unko-curry: 
konishika/unko_ng: 
oilfield/unko_hakken: oilfield
tanaton/unko2ch: 2ちゃんねる過去ログ転送サービス
kenmaz/cocos2d_UnkoYoke: simple unko game
mattn/Plack-Middleware-ReplaceToUnko: plack middleware for replacing images to shit image that referer from external sites.
legokichi/superUnkoChanLv200: roombaProgramming
Plavender/Unkown: 
omomuron/unkonow: 
FanPF/unkown: I don't  kown
ThievingSix/UnkosAMI: 
nakajijapan/unkore_iphone: iPhone Game - Bureau of Nakajima Cleansing
比較する物としては parson が一番近いです。parson は各JSON 型がそれぞれ別のC言語型として扱われている為、value と object/array/number/boolean/string といったデータ型の交換が必要で、parson では json_value_get_array という関数の様に value → array という型変換はもちろん、array 内要素の value から目的の型への変換が必要になったりします。
しかし jansson の場合は全て json_t 型のポインタで交換されていて、特に型交換する必要もなく、型チェックが必要な場合は json_is_array という関数でチェックが可能なのでとても直感的に、かつシームレスに処理が書けます。
そして parson の一番の欠点としてシリアライザが無い事が挙げられますが、jansson には存在します(json_dumps等)。
この点から見て、僕が把握しているC言語から使えるJSONパーサだと、現状 jansson が一番扱いやすいと思います。
ライセンスはMIT、チュートリアルが少ないですがAPIドキュメントはしっかり書いてあります。

ぜひ仕事でも使って行きたいと思います。

サーバを再起動したら勝手にscreenが起動してその中でirssiが動いていて欲しい場合のベストプラクティス

$
0
0
個人的にお借りしているサーバがあってそこで何個かbotを動かしているのだけど、そのサーバがセキュリティアップデート等で再起動した後、ログインしてscreen起動して、その中で画面割ってbot起動して、また別の画面でirssiを起動する、みたいな事を毎回やってた訳ですがいい加減めんどう臭くなってきたので自動化した。 まずscreenを自動起動する仕組みを考えた。rcスクリプトでもいいけど、そもそも共用サーバなのでroot権限が無い。そこでcronを使う。crontab -eして
@reboot (. ~/.profile; /usr/bin/screen -d -m)
@rebootという識別を使います。再起動して1回だけ実行されるコマンドが書けます。最近の linux であれば使えるかと思います。
ここで .profileを読み込んでるのは、これをしないと screen が新しく起動するプロセスに対してインタラクティブシェルから起動していたはずの環境変数等を引き継いでくれないから。例えば最近の Ubuntu であれば ~/.bashrc
# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac
こんな風にインタラクティブシェルじゃない場合には読み込まない様になっている。編集してもいいんだけど、僕はどの環境でも使えるベストプラクティスを選んだ。
また screen に渡している -d -m、これが無いとコンソールと通信するはずの screen が端末にアクセス出来なくて死ぬ。起動してすぐデタッチしてます。

あとは ~/.screenrcで好きなように画面を割って各プロセスを起動すれば良い。
startup_message off
vbell off
defshell /bin/bash
defscrollback 1000
hardstatus string "%?%H %?[screen %n%?: %t%?] %h"
screen 0 irssi
screen 1 /home/mattn/bin/startup-servers
僕の場合は、goreman というツールでサーバやbotを起動していて、それと irssi で画面分割している。
ちなみに defshell を設定しないと cron から起動した場合は $SHELL が設定されていないので、/bin/shになってしまうのを回避する為に必要です。

Go言語でDLLの読み込み

$
0
0
Twitterで「Go言語で、いわゆるプラグインの動的ロードってできる?」という発言を見かけたので。 CGOが書けるのでなんでも出来ます。Linux であれば goffi を使えばほぼ何でも出来ます。
cookieo9/goffi - GitHub

Go FFI (and dlopen) packages to wrap C libraries. This code is not being actively d...

https://github.com/cookieo9/goffi
Windows であれば CGO を使わずとも、もともと Windows API には LoadLibrary、GetProcAddress という物が用意されており、Go言語にもその wrapper が提供されています。
package main

import (
    "log"
    "syscall"
    "unsafe"
)

func main() {
    dll, err := syscall.LoadDLL("user32.dll")
    if err != nil {
        log.Fatal(err)
    }
    defer dll.Release()

    proc, err := dll.FindProc("MessageBoxW")
    if err != nil {
        log.Fatal(err)
    }

    proc.Call(0,
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("メッセージ"))),
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("ボックス"))),
        0)
}
golang
便利ですね。

ZenCoding.vim を Emmet.vim にリネームしました。

PerlでWindowsと親和性の高いreadlineが欲しい → あった「Caroline」

$
0
0
readline って言うと、便利な反面 Windows だと問題が多く発生して、Windows でも動かすスクリプトでの使用は敬遠してたんですが、Caroline だと多い日も安心。
tokuhirom/Caroline - GitHub

Yet another line editing library

https://github.com/tokuhirom/Caroline
readline なんかと違って、内部は utf-8 なので unix 等のスクリプトと同じ動きをします。ヒストリも保存出来るし、入力補完も自作出来ます。
eg/suddenly_death.plを見るといいです。

$ perl -Ilib eg/suddenly_death.pl
hello> 
ここで mを入力してタブキーをタイプすると...
$ perl -Ilib eg/suddenly_death.pl
hello> 突然のmattn
そして ENTER キーをタイプすれば
_人人人人人人_
> 突然のmattn <
 ̄^Y^Y^Y^Y^Y^ ̄

vim-airline でハァハァ

$
0
0
airline の為だけに hahhah-vim を autoload 化しました。
mattn/hahhah-vim - GitHub
https://github.com/mattn/hahhah-vim
そして vim-airline-hahhah
mattn/vim-airline-hahhah - GitHub
https://github.com/mattn/vim-airline-hahhah
ハァハァ
powerline、airline 等を試して来ましたが、これが無いとやっぱり vim 使ってる気になれませんね。

分散環境情報サーバ etcd を使った設定共有の活用

$
0
0
etcd って何と聞かれた場合、一言で言ってしまえば zookeeper なんだけど
Documentation · CoreOS

etcd etcd is a highly-available key value store for shared configuration and service discovery.

http://coreos.com/docs/etcd/
coreos/etcd - GitHub
https://github.com/coreos/etcd
etcd の良い所は
  • curl で使える様な簡単な API
  • SSL Cert 認証もオプションとして使える
  • ベンチマークで 1000s of writes/s per instance を出せるくらい速い
  • Raft を使って正確に、確実に分散する
という感じらしい。作ってるのは CoreOS。
etcd はキーをパス形式で指定し、値を入出力出来るのだけど、例えば
  • /app/fooに test1
  • /app/barに test2
この様に設定して /appを参照すると foobarの両方が参照出来る。
curl からも簡単に実行出来るので、ちょっと値を更新といった場合にもいちいちプログラムを書かなくても良い。
etcd はみんな大好きGo言語で書かれていて Windows でもビルド出来るし、linux のノードと混ぜて運用する事も出来る。Go言語で書かれた go-etcd というクライアントを使えば、簡単に etcd を操作出来る。
coreos/go-etcd - GitHub

This etcd client library is under heavy development. Check back soon for more docs. In the meantime, check out etcd for details on the client protocol.

https://github.com/coreos/go-etcd
この etcd を使った小さなプログラムを書いてみた。
mattn/etcdenv - GitHub

etcd + env = awesome!

https://github.com/mattn/etcdenv
通常、unix で env と言えば
$ env DB=newdb CACHE=mycache foo
の様に環境変数を指定してプロセスを起動する物で、shebang で使われる事が多い。
例えば上記の様に書かれたプログラムが複数のサーバで起動するシステムであった場合、環境変数の内容を書き換えるとなれば大変だ。もちろんデプロイしてしまえばいいんだけど、そもそも環境変数値を動的に変えたいなんてニーズはある訳だ。
そこで
$ etcdenv -key=/app foo
この様にキーを指定して起動すると、etcd から環境変数値を取得して foo を起動する。利用シーンで言えば以下の様な感じ。
$ curl http://127.0.0.1:4001/v1/keys/app/db -d value="newdb"
$ curl http://127.0.0.1:4001/v1/keys/app/cache -d value="new cache"

$ curl http://localhost:4001/v1/keys/app
[{"action":"GET","key":"/app/db","value":"newdb","index":4},{"action":"GET","key":"/app/cache","value":"new cache","index":4}]

$ etcdenv -key=/app/
DB=newdb
CACHE=new cache

$ etcdenv -key=/app/ ruby web.rb
foreman や goreman の Procfile で以下の様に指定してもいい。
$ cat Procfile
web: etcdenv -key=/app ruby web.rb
後は複数あるシステムの環境変数を変えたければ curl で更新すればいい。

なお、etcd は設定情報に特化はしているけど、よく考えたらこれってファイルシステムだよね、とひらめいたので vim から etcd をファイルシステムとして扱える物を作ってみた。
mattn/vim-metarw-etcd - GitHub

vim-metarw-etcd etcd filesystem for vim

https://github.com/mattn/vim-metarw-etcd
:e etcd:/app/
とすれば上記の DB や CACHE が一覧され、ENTER で参照出来る。更新は :wで ok。
vim-metarw-etcd

TAB で補完も出来るのでファイルシステムそっくりに扱える。ちなみに僕はこの2日程、etcd 上で日記を書いてる。
etcd の使い道はもっと可能性があると思うので、もう少し遊んでみたい。

「実践Vim 〜思考のスピードで編集しよう〜」書評

$
0
0
アスキー・メディアワークス様より、献本して頂きました。
実践Vim 思考のスピードで編集しよう!実践Vim 思考のスピードで編集しよう!
Drew Neil
アスキー・メディアワークス / ¥ 2,940 (2013-08-29)
 
発送可能時間:一時的に在庫切れですが、商品が入荷次第配送します。配送予定日がわかり次第Eメールにてお知らせします。商品の代金は発送時に請求いたします。

まず先に総評を言ってしまいますが、Vimmer ならば買うべき本だと思います。
Big Sky :: Vimテクニックバイブル ~作業効率をカイゼンする150の技

もちろんこれらの本も僕を喜ばす事は出来たのですが、なにぶんページの殆どが知っている情報だったので僕にとって「ワクワクする本」では無かったんです。 ずっと最新情報取り入れたVim本出ないかなと思ってまし...

http://mattn.kaoriya.net/software/vim/20110810203558.htm
Vimテクニックバイブル ~作業効率をカイゼンする150の技Vimテクニックバイブル ~作業効率をカイゼンする150の技
Vimサポーターズ
技術評論社 / ¥ 3,129 (2011-09-23)
 
発送可能時間:在庫あり。

Vim テクニックバイブルを書いた時にも何冊か Vim の本を紹介しましたが、これまで「Vim を現場で使う事にスコープを当てた本」というのは存在しませんでした。
あくまでリファレンスでしかなく、なぜ Vim はこんな動作なのか、編集する方法には複数の方法があるのに何故この方法が良いとされているのか、そういった事が書かれている本というのは僕が知る限りありませんでした。
この本は「Practical Vim」と題された、Drew Neil 氏が書いた本の日本語訳で、私も英文の方は一部だけ読んだ事があったのですがこの本が日本語で読める様になった事に大きな喜びを感じました。訳者の方やアスキー・メディアワークス様に感謝したいと思います。
The Pragmatic Bookshelf | Practical Vim

Vim, a vast improvement over its classic ancestor vi, is a serious tool for programmers, web develop...

http://pragprog.com/book/dnvim/practical-vim
今日1日かけてじっくりと読ませて頂きました。僕も自他共に認める Vim 中毒者ですので、書かれている事の殆どは知っていたりもしましたが、改めて「この本良いな」と思いました。
これまでのリファレンス本は、体裁よく頭から必要だと思われた事を淡々と説明していましたが、本書は TIPS という形式で計 121 ものシーンを用いて実際に起き得る問題を紹介し、その解法を説明しています。かつインターネットからダウンロード可能なサンプルを用いて、言葉通り「実践」(Practical)を実現しています。
例えば
// example
var a = 1
var foo = ""
var fizz = new Date()
この様なテキストを
// example
var a = 1;
var foo = "";
var fizz = new Date();
こう編集するにはどうするのが良いのか、URL が並んだテキストの一部を共通的にを編集するにはどうすれば良いのか、といった問題に対して実際のサンプルで読者と一緒に読み進めます。
つまり僕が思うに、この本は「この前こんな場面で Vim なら簡単に出来そうだけど、やり方がわからなかった」といった人にとても有用な本であって、訳者の方やアスキー・メディアワークスの方にはとても失礼かもしれませんが、本の右端を折ったり付箋紙で貼ったりしながら自分が欲しい場面をどんどん見つけて欲しい本だと思いました。

例えば「bar」単語の「a」にカーソルがあり、その単語を削除する場合、dbx, bdw, dawの3つが考えられます(実際もっとあります。ただdbxは...間違い?)。この中で、Vimmer は消した後の事も考えます。ドットをタイプし、同じ様な削除行為を繰り返したいならば、「daw」を使います。そういった内容も書かれています。
ちなみになぜ「daw」が良いのかは、この本を買って見て頂くとして本書ではこの他にも「インターネットでこの方法が良いって言われてたけど、何故なの?」などといったことを事細かに説明してくれています。テキストオブジェクトについて説明した Vim の本というのも確かにこれまで無かったのでは無いでしょうか。
ちなみにテキストオブジェクトについては、結構ページ数を割いています。
中級者から上級者になりたい、そんな方にはぜひ読んで頂きたい本だと思いました。

最後に本書を読んで、僕の頭に残った言葉を紹介したいと思います。
一歩下がって、三歩進む
Vim らしい言葉だと思います。Vim はモードのあるエディタです。カーソルキーを使わないならば、移動するだけでも1キー押さなければなりません。しかしこの一歩下がる行為がある事で Vim は三歩も四歩も、いや数十歩先を進む事が出来るのです。

GVim のツールバーをカッコよくしてくれるプラグイン「vim-toolbar-icons-silk」

$
0
0
Vim のツールバーの画像は随分昔に作られた物で、見る限り古くそれだけで Vim を使うのを辞めてしまう人もいるんじゃないか、という程にクラッシックな画像でした。
vim toolbasr
実は Vim にはツールバーをスクリプトから変更出来る機能があり、画像さえ用意すればカッコいいツールバーにする事が出来る事はごく一部の Vimmer の中だけで知られていました。そして誰もこの腰の重い作業をやろうとする人はいませんでした。 そんな中、istepura という人が Silk というアイコン集を使って Vim のツールバー向けに画像を用意してくれました。
istepura/vim-toolbar-icons-silk - GitHub

README.md vim-toolbar-icons-silk GViM toolbar icons based in Silk icon theme. Theme site: http://www...

https://github.com/istepura/vim-toolbar-icons-silk
これを bundle するだけで簡単に自動的にアイコンがカッコよくなります。
vim toolbasr
オサレ...

Software Design 2013年10月号「生産性を向上させるVimのTips」を書かせて頂きました。

$
0
0
最近の Vim の使われ方は明らかに数年前とは違う物で、新しい使われ方がどんどんと増えてきています。知識を共有する事で自分なりの Vim を見つける事が出来ます。
生産性を向上させるVimのTips」と題して普段の生活でどの様に Vim を活用し、どの様に Vim の情報を収集していくかを説明してみました。
その他、コラムも含めて Vim を普段から知っている人であればニタニタ出来る記事になったと思っています。

ぜひお手に取ってご覧ください。

Software Design (ソフトウェア デザイン) 2013年 10月号 [雑誌]Software Design (ソフトウェア デザイン) 2013年 10月号 [雑誌]

技術評論社 / ¥ 1,280 (2013-09-18)
 
発送可能時間:近日発売 予約可

Viewing all 121 articles
Browse latest View live