Home > その他

その他 Archive

新年あけましておめでとうございます

新年あけましておめでとうございます。いまこの記事はAmtrak Cascadesという大陸縦断鉄道の中で執筆しています。さきほどカナダからの出国を済ませました。時刻は6:18amで、日の出は8:07amの予定なので、初日の出までまだまだ時間があります。

amtrak cascades

車内にはかなり安定したwifiがあり、ssh接続すら切れません。当然各席に電源があり、3時間程度しかバッテリのもたない初代MacBook Airでも平然と作業をすすめることができます。

amtrak cascades

お腹が減ったら食堂車で朝食Vimをキメることも可能です。

amtrak cascades

スピリチュアルな話

をして感極まる元旦を迎えるというのも良いですが、あんまりそういう話は得意でないので、Vimの話をします。

プログラミング関する何かを新たに学ぶとき、まずVimでそれをどのようにラクに行なうかを考える、ということは日常的に行なわれていることだと思います。たとえば普段はVim scriptとRubyしか書かない人が急にPythonをはじめるときは、neocomplcacheの自動補完やref.vimを駆使してVim内でPythonの学習を行なうでしょう。

これにちなんで、ちょっと個人的な話と、プラギンの紹介を行ないます。

時代は低レイヤ

僕はRubyでらくらくスクリプト書きなぐりやHaskellで宣言的に書きなぐりといった高レイヤ部分ばかりに慣れ親しみすぎたため、アセンブラやそれ以前にCやJavaといった低水準言語すらもうまく使えない軟弱プログラマなのでした。最も低水準な言語はJavaScriptやVim scriptだったと思います。このままではまずいと思い、青木峰郎先生のふつうのコンパイラなどを片手にアセンブラやその周辺技術、とくにコンパイラの最適化あたりの勉強に集中することに決めました。

大抵のC言語コンパイラは末尾再帰最適化を行なっているということは、低レイヤにあまり慣れ親しんでいない人でも、耳にしたことがあるのではないでしょうか。さて、最適化が行なわれたか否かを実際に確かめるには、実際にアセンブラを確認するとよいでしょう。

int f(int acc, int n)
{
  return n < 0 ? acc : f(acc + n, n - 1);
}

0からnまでの和を得る末尾再帰の関数です。f(0, 10) == 55です。

$ gcc a.c -S -m32 -O2 -o -

_f:
  pushl %ebp
  movl  %esp, %ebp
  movl  8(%ebp), %eax
  movl  12(%ebp), %edx
  testl %edx, %edx
  jns   L7
  jmp   L3
  .align 4,0x90
L9:
  movl  %ecx, %edx
L7:
  leal  -1(%edx), %ecx
  addl  %edx, %eax
  cmpl  $-1, %ecx
  jne   L9
L3:
  leave
  ret

詳しい解説は省略しますが、ようするに再帰的に関数呼び出しを行なうかわりに、単なるループに変換されています。

これはGCCでx86アセンブリ言語に変換するよりもClangでLLVM IRに変換する方が直感的で読みやすいでしょう。

$ clang -O3 a.c -S -emit-llvm -o -
define i32 @f(i32 %acc, i32 %n) nounwind uwtable readnone ssp {
entry:
  %cmp1 = icmp slt i32 %n, 0
  br i1 %cmp1, label %cond.end, label %cond.false.lr.ph

cond.false.lr.ph:                                 ; preds = %entry
  %0 = mul i32 %n, %n
  %1 = zext i32 %n to i33
  %2 = add i32 %n, -1
  %3 = zext i32 %2 to i33
  %4 = mul i33 %1, %3
  %5 = lshr i33 %4, 1
  %6 = trunc i33 %5 to i32
  %7 = add i32 %0, %acc
  %8 = sub i32 %7, %6
  br label %cond.end

cond.end:                                         ; preds = %cond.false.lr.ph, %entry
  %acc.tr.lcssa = phi i32 [ %8, %cond.false.lr.ph ], [ %acc, %entry ]
  ret i32 %acc.tr.lcssa
}

要するにループに変換されています。

つづいて、末尾再帰ではない形でも確認してみましょう。

int f(int n)
{
  return n < 0 ? 0 : n + f(n - 1);
}

実はこれでも先ほどとほとんど同じ結果が得られます。わざわざ人間が末尾再帰の形に変形する必要はないのでした。

これらのような事実が実際に目で確認できることがわかりました。しかし毎回gccやclangコマンドをvimshellから入力するのは大変です。それどころか、さきほど記述したCのコードにファイル名を与えて保存することは大変で、そんなことをしていると学習以前に一年が過ぎてしまい、次の元旦を迎えてしまいます。このような事態に陥ったとき、訓練されたVim使いは「そうだquickrun、使おう」となります。

:QuickRun -type c/gcc  -exec '%c %o %s -S -o -' -cmdopt '-m32 -O2'

これで動作することが分かるので、よし、~/.vimrcを開いてg:quickrun_configに・・・おっと、それだとclangの例を試せません。

:QuickRun -type c/clang  -exec '%c %o %s -S -emit-llvm -o -' -cmdopt '-O3'

複数の候補から選択したい。Hack #200: 候補を選択し、実行するにあるように、これはunite.vimの出番です。

欲深いもので、Cという1つの言語だけをサポートするのではなく、RubyにはYARV instructionを、CoffeeScriptにはJavaScriptを、といった具合で、人間の煩悩が108あるように、複数の要求をすべて満たしてみたくなるものです。

というわけでプラギンにしてみました。すばやく学習を支援するという意味で、quicklearn.vimという名前にしました。quickrunとquicklearnでrとlの発音をそれぞれ学習でき、一石二鳥です。

紹介動画


Video streaming by Ustream

以下の言語/処理系/中間言語をサポートしています。

  • C
    • Assembly language (gcc)
    • LLVM IR (clang)
  • Haskell
    • Core (ghc)
  • CoffeeScript
    • JavaScript
  • Ruby
    • YARV Instructions (CRuby)

quicklearnは

:Unite quicklearn -immediately

のようにして実行できます。筆者は

nnoremap <space>R :<C-u>Unite quicklearn -immediately<Cr>

~/.vimrcで設定し、<space>Rで実行できるようにしています。なお、quickrunは<space>rにしています。

-immediatelyオプションがとても便利です。

なお、quickrunはアセンブリ言語やLLVM IRをサポートしています。つまり、quicklearnによって生成させたアセンブリ言語やLLVM IRのバッファでさらにquickrunを行なうことで、それを実行することができ、とても便利です。

実装

たったの98行です。

autoload/unite/sources/quicklearn.vim

let s:save_cpo = &cpo
set cpo&vim

" fmap([a, b, c], f) => [f(a), f(b), f(c)]
" fmap(a, f) => [f(a)]
function! s:fmap(xs, f)
  if type(a:xs) == type([])
    return map(a:xs, a:f)
  else
    return map([a:xs], a:f)
  endif
endfunction

let g:quicklearn_gcc_remote_url = get(g:, 'quicklearn_gcc_remote_url', 'localhost')

let s:quicklearn = {}
let s:source = {
      \ 'name': 'quicklearn',
      \ }
let s:quicklearn['c/clang/intermediate'] = {
      \ 'meta': {
      \   'parent': 'c/clang'},
      \ 'exec': '%c %o %s -S -emit-llvm -o -'}
let s:quicklearn['c/clang-O3/intermediate'] = {
      \ 'meta': {
      \   'parent': 'c/clang'},
      \ 'cmdopt': '-O3',
      \ 'exec': '%c %o %s -S -emit-llvm -o -'}
let s:quicklearn['c/gcc/intermediate'] = {
      \ 'meta': {
      \   'parent': 'c/gcc'},
      \ 'exec': '%c %o %s -S -o -'}
let s:quicklearn['c/gcc-32/intermediate'] = {
      \ 'meta': {
      \   'parent': 'c/gcc'},
      \ 'cmdopt': '-m32',
      \ 'exec': '%c %o %s -S -o -'}
let s:quicklearn['c/gcc-remote/intermediate'] = {
      \ 'meta': {
      \   'parent': 'c/gcc'},
      \ 'exec': 'ssh ' . g:quicklearn_gcc_remote_url . ' %c %o %s -S -o -'}
let s:quicklearn['haskell/ghc/intermediate'] = {
      \ 'meta': {
      \   'parent': 'haskell/ghc'},
      \ 'exec': [
      \   '%c %o -ddump-simpl -dsuppress-coercions %s',
      \   'rm %s:p:r %s:p:r.o %s:p:r.hi'],
      \ 'cmdopt': '-v0 --make'}
let s:quicklearn['coffee/intermediate'] = {
      \ 'meta': {
      \   'parent': '_'},
      \ 'exec': ['%c %o -cbp %s %a']}
let s:quicklearn['ruby/intermediate'] = {
      \ 'meta': {
      \   'parent': 'ruby'},
      \ 'cmdopt': '--dump=insns'}

" inheritance
for k in keys(s:quicklearn)
  let v = s:quicklearn[k]
  for item in ['command', 'exec', 'cmdopt', 'tempfile', 'eval_template']
    let ofParent = get(g:quickrun#default_config[v.meta.parent], item)
    if type(ofParent) != type(0) || ofParent != 0
      let s:quicklearn[k][item] = get(v, item, ofParent)
    endif
    unlet ofParent
  endfor
endfor

" build quickrun command
for k in keys(s:quicklearn)
  let v = s:quicklearn[k]
  let s:quicklearn[k].quickrun_command = printf(
        \ 'QuickRun %s %s %s -cmdopt %s',
        \ v.meta.parent == '_' ? '' : '-type ' . v.meta.parent,
        \ get(v, 'command') ? '-command ' . string(v.command) : '',
        \ join(s:fmap(get(v, 'exec', []), '"-exec " . string(v:val)'), ' '),
        \ string(get(v, 'cmdopt', '')))
endfor
lockvar s:quicklearn

function! unite#sources#quicklearn#define()
  return s:source
endfunction

function! s:source.gather_candidates(args, context)
  let configs = filter(copy(s:quicklearn), 'v:key =~ "^" . &filetype . "/"')

  return values(map(configs, '{
        \ "word": substitute(v:key, "/intermediate$", "", ""),
        \ "source": s:source.name,
        \ "kind": ["command"],
        \ "action__command": v:val.quickrun_command,
        \ }'))
        "\ "action__type": ": ",
endfunction

let &cpo = s:save_cpo

適当な辞書を事前に作っておき、uniteから実際に実行させたいコマンド文字列を事前に生成しておきます。それを対応するfiletypeごとにs:source.gather_candidatesで適切な辞書の配列を返すことによって行なっています。uniteのkindはcommandです。

冒頭のローカル関数fmapはfunctorっぽいものの実現です。対象が複数でも単数でも気にせず使えます。JavaScriptに詳しい方には、jQueryのアレ、といえば通じるかもしれません。

以上

Vim Advent Calendar 2011の32本目の記事でした。今年もよろしくお願いいたします。

ujihisa.vim#2が本日開催されます

http://vim-jp.org/ujihisa.vim-2/

楽しみですね!

Vimテクニックバイブルが発売されました

longline

数多くの人が購入し、すでに完読した方も多くいらっしゃることと思います。

感想をブログに書いた方は、その情報を共有するため、ぜひともこのエントリにトラックバックを送るなり、コメントでURLを貼付けるなどとしたいただけると、大変便利です。

非Vim使いが生産性を3倍高めるたった一つの方法

Vimを使う。

quickrunでquine

一行目に

#!cat

と書きます。二行目以降は何を書いてもかまいません。

これで、quickrunを実行しますと、編集中ファイル自体が出力されます。かなり便利です。

文責: ujihisa

Hack #167: Vimスクリプトで無名関数やクロージャを使う方法

無名関数

function! s:foo()
    let foo = {}
    function foo.funcall() dict
        echo 'lambda'
    endfunction
    return foo
endfunction

call s:foo().funcall()

クロージャ

function! s:foo(num)
    let foo = {'i': a:num}
    function foo.funcall() dict
        let self.i += 1
        return self.i
    endfunction
    return foo
endfunction

echo s:foo(1).funcall()
let s:clos = s:foo(5)
" 6
echo s:clos.funcall()
" 7
echo s:clos.funcall()
" 8
echo s:clos.funcall()

説明

Vimスクリプトでは辞書型に関数をキーとして代入することができます。
その場合関数はほぼ無名のような存在になります。
また辞書型に格納した「dict」つきの関数からはその辞書を「self」という変数で参照できるので
辞書に関数内で使う変数などを辞書に保存しておけば関数内からその変数を参照できる、いわば疑似的なクロージャを実現することができます。

  1. 自分で変数を渡す必要がある
  2. obj.funcall() を自分で呼ぶ必要がある

などなど他言語と比べかなりプリミティブですが、これを駆使すればVimスクリプトでも現実的に無名関数やクロージャが扱えることがわかるでしょう。

またVimスクリプトでは辞書型を使ったプロトタイプ指向によりクラスなども扱うことができます。
そこまでしなくても辞書型を活用することでモジュールを分けられるのでいろんな活用方法があります。

tyru

Hack #163: VimをVimスクリプトインタプリタとして使う

viの前身であるedは、シェルスクリプトなどで文字列置き換えのために使うことができます。 (fileというファイルの中身を全行逆転させる例です)

ed - file <<EOS
g/^/m0
write
qall!
EOS

g/^/m0^にマッチする行に対して:m0という操作を適用するコマンドです。 :m0は分かりやすく書くと:move 0で、引数の行の下に現在の行を持っていくという動作をします。 ^はどの行にもマッチするので、:m0で全部の行に対して上からマッチした順に1行目に持っていきます。 操作が終わると全行が逆になっているというわけです。

また:write:qall!は全行逆になったバッファをfileに書き込むために必要です。

VimについてくるexというコマンドはedのVim版とも言えるものですのでもちろん上のようなことができます。 しかしデフォルトでは.vimrcやプラグインなども読み込んでしまうため、 「素のex」として使いたい場合は「-u NORC –noplugin」を指定する必要があります。 さらにVimmerとしてはVimの機能が使えず戸惑わないように「-N」も指定する必要があります。 よってexをVimスクリプトインタプリタとして使うには以下のように起動すればできそうです。

ex -N -u NORC --noplugin

冒頭の例のex版はこのようになります。

ex -N -u NORC --noplugin file <<EOS
g/^/m0
write
qall!
EOS

デフォルトでもこのようにVimスクリプトインタプリタのように使えなくはないのですが、以下のような難点があります。

  1. ファイルに対して実行するには末尾に必ず:write:qall!をつけなければならず面倒
  2. ファイルに対して実行する際に元のデータを壊してしまう
  3. いくつかの定型的なオプションを付けるのが面倒
  4. 標準入力からスクリプトを読み込むのでファイルを標準入力から読み込めない

解決

iexを使います。 これはVimスクリプトをPerl、Ruby、Pythonなどの多くのLLのインタプリタと同じ感覚でVimスクリプトを実行させることができる優れ物です。

$ echo 'g/^/m0' >reverse.vim
$ cat ~/.vimrc | iex reverse.vim -

# -eオプションで指定することも可能
$ cat ~/.vimrc | iex -e 'g/^/m0' -

$ iex    # /bin/exが開く

詳しくはiex -hを見てください。

いくつかのVimスクリプト

おまけとしていくつかのUNIXコマンドをVimスクリプトで実装します。

tac

$ cat tac.vim
g/^/m0
$ iex tac.vim file

sort

$ cat sort.vim
sort
$ iex sort.vim file

sort -u (sort | uniq)

$ cat uniq.vim
sort u
$ iex uniq.vim file

grep

$ cat grep.vim
edit `=ARGS[1]`
execute 'v/' . ARGS[2] . '/d'
$ iex -s grep.vim ~/.vimrc vim

("vim"のみを含んだ行が表示される)

また-eオプションを使ったやり方を示すと

$ iex -s -e 'edit `=ARGS[1]' -e 'execute "v/" . ARGS[2] . "/d"' ~/.vimrc vim

のようになります。
これは一般のLLインタプリタと同じようにワンライナーのようなものを書くのに適しています。

また-sを与えることで引数の扱いを変えています。
-sを与えると中身を読み込まずにg:ARGSというListに代入するだけにします。
その他細かい違いなどはiex -hを見てください。

tyru

Vim 7.3の新機能まとめ

とりあえず自分も今調べている最中なので:help version7.3をそのまま紹介する形にします。
他にもこれは紹介するべきというものや間違い等があったらどんどん追加/修正お願いします。 >Vim Hacksのメンバー

Persistent undo

Hack #162: Vimを終了しても undo 履歴を復元するですでに紹介されていますが、Vimを終了してもundo履歴が復元できる機能です。

ファイル暗号化方式の追加

ファイルを暗号化して保存する際、従来はPkZip方式での暗号化が行われていましたが、
新たにBlowfish方式での暗号化も選べるようになりました(:help ‘cryptmethod’)。

conceal

シンタックスハイライトで特定のハイライトグループを見た目「消す」ことができます。
すでに最新版のVimをインストールすると、syntax/help.vimがこの機能を使っていて単語を囲んでいる*|などの文字が消えています。

シンタックスハイライトで文字を隠すことができるようになったわけで、いろいろと応用できそうです。
プラグイン開発者にとって見逃せない新機能となっています。

例としてすでにsyntax/markdown.vimでこの機能を使い

[リンク](URL)

というmarkdownの記法を

リンク

に見た目を変えてしまうパッチがメーリングリストに投げられました

またオプションとしてconceallevelが加えられています。

Luaインターフェース

LuaでVimプラグインが書けるようになりました。
詳しくは:help luaを。

Python3インターフェース

Pythonインターフェースに加えPython3インターフェースが加えられたようです。
両者はコマンドが分かれていて、それぞれ:python, :python3となっています。

‘encoding’をmodelineで変更することの禁止

modelineでencodingを変更することができなくなりました。

‘relativenumber’オプション

これはオンにしてみれば分かりやすいと思いますが、相対的な行番号を表示します。
つまり現在行の横に「0」と表示されて、一行上と一行下は「1」、二行上と二行下は「2」と表示されるようになります。
このオプションがセットされたとき’number’の値はリセットされます。
この機能は、jkに数値を付けて移動する場合(13jなど)のために追加されました。

詳しくは:help 'relativenumber'を。

いくつかの関数追加

More floating point functions: |acos()|, |asin()|, |atan2()|, |cosh()|,
|exp()|, |fmod()|, |log()|, |sinh()|, |tan()|, |tanh()|.  (Bill McCarthy)

Added the |gettabvar()| and |settabvar()| functions. (Yegappan Lakshmanan)

Added the |strchars()|, |strwidth()| and |strdisplaywidth()| functions.

詳しくは:help added-7.3を。

新機能の解説

また以下のサイトで

  1. ‘cursorbind’
  2. conceal
  3. :ownsyntax

の3つの新機能の解説があります。 英語ですがスクリーンショットなどもありなかなか分かりやすいです。

http://sites.google.com/site/vincenegri/concealownsyntaxforvim

Hack #159: オプションの値を気にせずsplit, vsplitする

Vimの標準のコマンドである:split:vsplit、 またはそのマッピング版である<C-w>s<C-w>vは それぞれ&splitbelow&splitrightに依存しており、 自分の思った通りの方向にウインドウを開いてくれない場合があります。

そこで、このようなマッピングを定義します。

nmap spj <SID>(split-to-j)
nmap spk <SID>(split-to-k)
nmap sph <SID>(split-to-h)
nmap spl <SID>(split-to-l)

nnoremap <SID>(split-to-j) :<C-u>execute 'belowright' (v:count == 0 ? '' : v:count) 'split'<CR>
nnoremap <SID>(split-to-k) :<C-u>execute 'aboveleft'  (v:count == 0 ? '' : v:count) 'split'<CR>
nnoremap <SID>(split-to-h) :<C-u>execute 'topleft'    (v:count == 0 ? '' : v:count) 'vsplit'<CR>
nnoremap <SID>(split-to-l) :<C-u>execute 'botright'   (v:count == 0 ? '' : v:count) 'vsplit'<CR>

こうすることでいつでも自分の思った通りの方向にウインドウを開くことができます。

また

nmap spj <SID>(split-to-j)
nmap spk <SID>(split-to-k)
nmap sph <SID>(split-to-h)
nmap spl <SID>(split-to-l)

の部分は自分の好きなように変えてください。 筆者は上のようにそれぞれspj, spk, sph, splに割り当てています。

追記: kana氏の指摘によりコードを大幅修正しました。

追記2: [count]を取れるようにしました。30sphなどとすると30の幅を持つウインドウが左に開きます。 ちなみに現在筆者はマッピングを<Space>sj, <Space>sk, <Space>sh, <Space>slに変更しました。 押しやすいマッピングはVimmerの数だけあるので、各人押しやすいマッピングを常に追求しましょう。 ちょっとでも押しにくいと感じたら積極的に他のマッピングを検討すべきです。

tyru

Vim Quiz 1

以下のようにウインドウが分割されています。

1

これを以下のように配置しなおすにはどうすればよいでしょうか。

2

文責: ujihisa

ホーム > その他

Search
Feeds
Links
  • 公式
  • 勉強会
  • 情報
  • コミュニティ
  • Meta
    Etc
    Creative Commons License
    This blog is licensed under a Creative Commons License.

    Return to page top