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

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

お腹が減ったら食堂車で朝食Vimをキメることも可能です。
スピリチュアルな話
をして感極まる元旦を迎えるというのも良いですが、あんまりそういう話は得意でないので、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の発音をそれぞれ学習でき、一石二鳥です。
紹介動画
以下の言語/処理系/中間言語をサポートしています。
- 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本目の記事でした。今年もよろしくお願いいたします。
- Comments: 0
- Trackbacks: 0
Hack #241: Haskellで使いたい関数を使ってからそのモジュールをimportする
- 2011-12-19 (月)
- Vim Hacks
Haskell Advent Calendar 2011への寄稿記事です。
問題
Haskellを書いていて、長いソースコードの末尾の方にて、急にとあるモジュールのとある関数を使いたい、そんなときはよくあります。ありがちなのがApplicativeの演算子いくつかと、Data.Function.onと、Data.Listのアレとコレと・・・。いくらでもあります。
Haskellでは一般的に、モジュールのimportはソースコードのかなりはじめの方にまとめて記述します。importしたい関数を一度しか使わず、しかもソースコードの末尾の方に位置していようと、おかまい無しです。このとき、ggなどでソースコード上部まで移動し、neco-ghcなどを駆使してmoduleとその関数を的確にimportし、そして<C-o>などでもといた場所に戻ることになると思います。:spなどで画面分割してから行ったり、あるいはmarkをつけるという方法もありますが、いずれにせよこれらの作業のため脳内の作業メモリがスタックオーバーフローするのは明らかでしょう。
解決
unite-haskellimportを用います。
https://github.com/ujihisa/unite-haskellimport
まずは上記プラギンをインストールします。依存プラギンはunite.vim、依存ツールはhoogleです。
$ cabal update && cabal install hoogle && hoogle data
たとえば急に==>という関数を使いたくなったとしましょう。
:Unite haskellimport
としてunite窓を開き、==>と打鍵しましょう。
Test.QuickCheck.Property (==>) :: Testable prop => Bool -> prop -> Property
Test.QuickCheck (==>) :: Testable prop => Bool -> prop -> Property
インストールしているcabalパッケージにもよりますが、たとえば上記のような項目が選択肢にでてくることでしょう。実際にimportしたい側を選択します。
候補を選択すると、unite-haskellimportはdefaultのactionとして:Haskellimportコマンドを発行します。これは、対象を、ソースコードのそれらしい場所に挿入するものです。既に他のimport文がある場合は、最後のimportのあとに挿入します。
Before
import qualified Data.Text as T
f x = ...
After
import qualified Data.Text as T
import Test.QuickCheck ((==>))
f x = ...
今回importしたのは記号からなる関数でした。が、もしも記号ではない関数、つまりVimの<cword>になるようなものならば、:Uniteのかわりに
:UniteWithCursorWord haskellimport
とするとよいでしょう。これを頻繁に行うようならば、適切なキーにマッピングしておくべきです。たとえば以下を ~/.vim/ftplugin/haskell.vimに記述すると、<space>Iと打鍵するだけでカーソル以下にある単語の関数をimportしてくれます。
nnoremap <buffer> <space>I :<C-u>UniteWithCursorWord haskellimport<Cr>
ujihisa
- Comments: 0
- Trackbacks: 0
Hack #240: <C-[> での誤爆を防止する
- 2011-12-12 (月)
- Vim Hacks
挿入モードから抜ける方法は多数ありますが、その中に <C-[> キーがあります。
しかしこの [ キーは、日本語キーボードではすぐ左に @ キーがあり、誤って <C-@> を押してしまうと直前に挿入したテキストがさらに挿入されてから挿入モードを抜けるため、誤爆すると大惨事となります。
誤爆を防止する
誤爆をすると言うことは、本来行いたい操作があるということです。 このような誤爆を簡単かつ確実に防止するには、対象の機能を本来押したいキーに置き換えてしまうのが効果的です。
考えてみましょう。この <C-@> の機能「直前に挿入されたテキストをもう一度挿入し、挿入を終了する。」は、あなたにとって必要でしょうか? もし必要ないと判断したら、本来押したいキー、この場合は <C-[> に置き換えましょう。以下のようにします。
imap <C-@> <C-[>
こうすることで誤爆を防ぐことができます。この @ は [ よりも近いため押し易く、しかもこの位置は英字キーボードと同じ位置であるため、英字キーボードに乗り換える練習にもなります。
もし万が一 <C-@> の機能が使いたい場合、しかし誤爆は避けたい場合は、別の位置に <C-@> をマッピングするとよいでしょう。
thinca- Comments: 0
- Trackbacks: 0
ujihisa.vim#2が本日開催されます
- 2011-11-19 (土)
- その他
http://vim-jp.org/ujihisa.vim-2/
楽しみですね!
- Comments: 0
- Trackbacks: 0
Hack #239: グローバル変数を安全に参照する
- 2011-10-30 (日)
- Vim Hacks
こんにちは、ujihisaです。以前からこのvim-users.jp上で告知していた、東京でのVimのカンファレンスであるujihisa.vim#2が、ついに一般参加者の募集をはじめたようです。
- ujihisa.vim#2 http://vim-jp.org/ujihisa.vim-2/
このページの末尾から参加申し込みページにアクセスできるようです。なお、HootSuiteをお使いの方は、以下のリンクから簡単にujihisa.vim#2の宣伝ができるようで、とても便利です。
さて、本題です。
問題
Vim scriptを読んでいると以下のようなコードを比較的頻繁に見かけるのではないでしょうか。
if !exists('g:aaa_bbb')
let g:aaa_bbb = 'something default'
endif
特定のグローバル変数が存在しないときのみデフォルトの値を入れるときのパターンです。これは一行で宣言的に書きたいですよね。かといって、これのためだけに別の関数を用意するのも、健康的でなさそうです。
解決
実はグローバル変数の一覧をg:で取得することができます。同様にバッファローカル変数一覧はb:、スクリプトローカル変数一覧はs:など。これは変数名がキーで変数の値が値の辞書となっています。
echo g:hello
は、実は以下のように書くことができます。
echo (g:).hello
echo (g:)['hello']
いずれもそのキーがなければ実行時エラーをだしてしまいます。しかし、get()関数を用いれば、デフォルト値を使いつつその値に参照することができます。
echo get(g:, 'hello', 'the default value')
これはg:helloが定義されていればその値を表示、定義されていなければthe default valueを表示します。
したがって、元々の問題は、
if !exists('g:aaa_bbb')
let g:aaa_bbb = 'something default'
endif
以下のように一行で記述することができます。
let g:aaa_bbb = get(g:, 'aaa_bbb', 'something default')
美しいですね。これがVim script使いの生きる道といえるのではないでしょうか。
ujihisa- Comments: 0
- Trackbacks: 0
Hack #238: neobundle.vim で plugin をモダンに管理する
- 2011-10-25 (火)
- Vim Hacks
こんにちは。Shougoです。Vimテクニックバイブルの執筆で長らくお休みを頂いていました。 執筆が終わってからも、プラグインの更新で忙しいのが難点です。 とはいえ、あまりに間隔が空いているのは執筆者としてまずいと感じたため、久しぶりに復帰します。
みなさん、Vimプラグイン管理には何をお使いでしょうか。残念ながら、Vimのプラグイン管理には良い方法がなく、 各々が「自分の考える最強のプラグイン管理プラグイン」を作ってきました。 つまり、長らくプラグイン管理プラグインは戦国時代の様相を呈していたわけです。 実装が存在しないVimJoltsもその一つです:-)。 しかし最近、Vimのプラグイン管理はVundleに一元化されることが多くなってきました。 「Hack #215: Vundleでプラグインをモダンに管理する」やVimテクニックバイブルでも紹介されています。 私も最初はpathogenを使っていましたが、アップデートが簡単なVundleに乗り換えた経緯があります。 ただしVundleにもいくつか不満があったため、自分でforkしたVundleをしばらく使用していました。 しかし、最終的にVundleの仕様変更に追随できなくなったため、forkしたVundleを元にneobundle.vimを開発することにしました。 変更点をVundleにマージするには、変更点が多すぎたということもあります。 今回はneobundle.vimについて紹介します。
neobundle.vimとVundleの違い
Vundleをベースにしたため、基本的な機能は同じです。 Vundleを知っているなら、すぐに乗り換えることも可能でしょう。 変更点は幾つかありますが、主なものは以下の通りです。
コマンド名が改善されている
VundleはBundleを意識して付けられたコマンド名になっているため、コマンド名とプラグイン名が対応していません。
例えば、プラグインをインストールするコマンド名は:VundleInstallではなく、:BundleInstallです。
この辺りは、議論がされているようですが、まだ結論は出ていません。
neobundle.vimはプラグイン名をプレフィクスとした、:NeoBundleInstallとなっています。
BundleをNeoBundleに改名するだけで、基本的に使用することができます。
vital化されている
ルーチンを他のプラグインと共通化するために、vital.vimを使用しています。 プラグインの開発が楽になります。
shellslashがオンでも動作する
shellescape()を使用していますが、shellslashオプションがオンの場合、クオートが”になってしまうという問題があります。
つまりWindows環境で、cmd.exeをshellとして使用している場合、shellslashを有効にしていると動きません。
neobundle.vimではshellescape()を使用していません。
vimprocに対応している
vimprocが利用できる環境では、アップデートにvimprocを使用します。 Windows環境では、DOS窓が出ないという利点につながります。
unite.vimインタフェースの実装
実は、これがneobundle.vimで一番やりたかった機能です。 neobundle.vimはunite.vimのインタフェースを実装しているため、unite.vimからVimプラグインをアップデートすることができます。 非同期でアップデートを行うため、使用にはvimprocが必要です。 neobundle.vimはunite.vimやvimprocがない環境でも動作しなくてはならないため、従来のインタフェースも残っています。 ただし、従来のインタフェースは緊急用のため重視していません。
Subversion, Mercurialへの対応
tsukkeeさんがVundleに追加していた機能をマージし、Subversion, Mercurialに試験的な対応をしました。 まだ実験段階ですが、試していただければと思います。
リビジョン指定
:NeoBundle {repository} {revision}という構文でリビジョン指定ができます。まだ実験段階の機能です。
不具合のあるプラグインのバージョンを固定する時に便利だと思います。
neobundle.vimの導入
neobundle.vimの開発は、github上で行われています。https://github.com/Shougo/neobundle.vim 導入する場合、~/.vimにリポジトリをcloneすると楽でしょう。
$ git clone https://github.com/Shougo/neobundle.vim ~/.vim/neobundle.vim.git
plugin の管理
plugin の管理は以下の様に .vimrc に NeoBundle 'plugin name' と記述することで行います。
極端な話、BundleをNeoBundleに変更するだけです。
set nocompatible
filetype off
if has('vim_starting')
set runtimepath+='path to neobundle directory'
call neobundle#rc(expand('~/.bundle'))
endif
NeoBundle 'git://github.com/Shougo/clang_complete.git'
NeoBundle 'git://github.com/Shougo/echodoc.git'
NeoBundle 'git://github.com/Shougo/neocomplcache.git'
NeoBundle 'git://github.com/Shougo/neobundle.vim.git'
NeoBundle 'git://github.com/Shougo/unite.vim.git'
NeoBundle 'git://github.com/Shougo/vim-vcs.git'
NeoBundle 'git://github.com/Shougo/vimfiler.git'
NeoBundle 'git://github.com/Shougo/vimshell.git'
NeoBundle 'git://github.com/Shougo/vinarise.git'
filetype plugin on
filetype indent on
Vundleと同様、先にfiletypeをoffにしてから、neobundle.vimの設定を行います。
最初、neobudle.vimの関数を呼べるよう、neobundle.vimのパスを&runtimepathに追加します。
そしてneobundle#rc(expand('~/.bundle'))で初期化します。
neobudle#rc()の引数はプラグインをインストールする基準となるパスです。
NeoBundleコマンドで設定をしたあと、最後にfiletype pluginをオンにします。
NeoBundle コマンドの使い方
Vundleと全く同じです。基本的にBundleコマンドがNeoBundleコマンドに変わっただけです。
Github 上のリポジトリから取得する
NeoBundle 'user_name/repository_name'
vim-scripts 上のリポジトリから取得する
NeoBundle 'script_name'
それ以外のリポジトリから取得する
NeoBundle 'git://repository_url'
NeoBundle 'http://svn.macports.org/repository/macports/contrib/mpvim/'
NeoBundle 'https://bitbucket.org/ns9tks/vim-fuzzyfinder'
Gitリポジトリのフルパスを指定します。 MercurialやSubversionにも試験的に対応しています。
pluginの管理
plugin のインストールには、:NeoBundleInstall コマンドを使用します。
.vimrcで設定したplugin が自動的に取得され、インストールまで行われます。
ただし、アップデートしたプラグインを使用するには、Vimを再起動したほうが良いです。
:NeoBundleInstall
既にインストールされている plugin をアップデートするには、 :NeoBundleInstall! コマンドを利用します。
通常のNeoBundleInstallでは、アップデートが行われないからです。
:NeoBundleInstall!
不要になったプラグインを削除するには、:NeoBundleの設定を.vimrcから削除した後、
:NeoBundleCleanコマンドを実行します。
neobundle.vimはplugin検索には対応していません。
neobudle.vimはneobundle/install sourceを使用することで、
unite.vimインタフェースによりアップデートを行うことができます。
アップデートは非同期に行われます。
neobundle/install sourceの引数に!を与えると、:NeoBundleInstall!相当になります。
プラグイン名を引数に与えることで、個別にアップデートをすることも可能です。
:Unite neobundle/install:!
:Unite neobundle/install:neocomplcache
:Unite neobundle/install:neocomplcache:unite.vim
ドキュメントの更新
プラグインのインストール時に、自動的に:helptagsコマンドが実行されるため、
ユーザーは何もする必要がありません。
参考
- :help neobundle
- Comments: 0
- Trackbacks: 0
Hack #237: 古い quickfix を参照する
- 2011-10-20 (木)
- Vim Hacks
quickfix は :grep や :make の結果として使われたり、時にはプラグインが何かの結果を表示するのに使ったりと、様々な状況で使用されます。
それだけ便利なものですが、時には、以前の内容をまだ見たいのにうっかり新しい内容を quickfix へ出力してしまうことがあります。
:colder :cnewer コマンド
実は、quickfix は過去のリストを10個まで保持しています。これは :colder :cnewer コマンドで切り替えられます。
:colder [count]
:cnewer [count]
:colder で [count] 個分古い quickfix へ移動します。:cnewer で [count] 個分新しい quickfix へ移動します。
もし誤って quickfix を上書きしてしまった場合でも、慌てず騒がず :colder すれば安心です。
- Comments: 0
- Trackbacks: 0
Hack #236: 短期間でVim script力を向上する
- 2011-10-15 (土)
- Vim Hacks
こんにちは、ujihisaです。この記事はGentoo上で執筆しています。ちなみに来日することが決まりました。こんなカンファレンスや、勉強会を企画してみました。いずれも来月です。
問題
Vim scriptというプログラミング言語の能力を高める効率的な方法はあるのでしょうか?
Vimテクニックバイブルも既に完読してしまい、次にやるべきことを見失っている読者も多いかもしれません。
解決
Project Eulerというオンラインの問題集があります。オンラインで答え合わせもできます。
ぜひこれにVim scriptで挑戦してみましょう。以下に問1の回答例を示します。
(もっと良い回答を思いついた、という方はぜひコメントやトラックバックで示してください。楽しみにしています!)
問1
http://projecteuler.net/problem=1
If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.
回答例1
range()で1から1000までの数値の配列を作り、それをもとにforで加算していきます。
function! s:p1()
let memo = 0
for i in range(1, 999)
if i % 3 == 0 || i % 5 == 0
let memo += i
endif
endfor
return memo
endfunction
echo s:p1()
これをfiletype=vimなバッファで作成し、最後にquickrunしましょう。そこには答えがでているはずです。
回答例1
filter()を使います。
function! s:p1()
let memo = 0
let list = filter(range(1, 999), 'v:val % 3 == 0 || v:val % 5 == 0')
for i in list
let memo += i
endfor
return memo
endfunction
echo s:p1()
さっきよりも見通しのよいコードになったでしょう。lambdaがなくて文字列になってしまうのがちょっと困りものですが…。
さて、この調子でforを消せると考えた人も多くいるかもしれません。そうです、つい5日前に読んだHack #235を思い出しましょう。vitalを使います。
function! s:p1()
let L = vital#of('vital').import('Data.List')
let list = filter(range(1, 999), 'v:val % 3 == 0 || v:val % 5 == 0')
return L.foldl1('v:memo + v:val', list)
endfunction
echo s:p1()
もうここまできたら一行にしたくなるのが人情ですよね。
echo vital#of('vital').import('Data.List').foldl1('v:memo + v:val', filter(range(1, 999), 'v:val % 3 == 0 || v:val % 5 == 0'))
ほかにも多くの解法があると思います。読みやすくかつ短いコードを高速で書くことができるよう、日々鍛錬あるのみです。
ujihisa- Comments: 0
- Trackbacks: 0
Hack #235: リストの要素の総和や総乗を簡単に求める
- 2011-10-10 (月)
- Vim Hacks
こんにちは、ujihisaです。無事引っ越しが完了しましたが、インターネット回線の確保にはもうしばらく時間がかかるらしく、しばらく家でオフラインな状態です。ちなみに来日することが決まりました。こんなカンファレンスや、勉強会を企画してみました。いずれも来月です。ふるってプレゼンに応募ください。
問題
リストlistの和は、他の言語ならば以下のようにして求めることが一般的です。
- Ruby
list.inject(:+)- これは
list.inject {|i, j| i + j }の意。Enumerable#injectの特殊用法 list.inject(0, :+)- 同じくこれは
list.inject(0) {|i, j| i + j }の意。
- Haskell
foldl1 (+) list- これは
foldl1 (\i j -> i + j) listの意。言語仕様として演算子は括弧でくくるだけで2引数関数のような振る舞いになる(*1)。 sum list
- Python
reduce(lambda a, b: a + b, list)sum(list)と、sumといった直接的な方法がサポートされているかは別にして、なんらかの「リストと関数を受け取り、(リストに限らない)なんらかの値を返す」関数が提供されるのが一般的であることがわかります。
Vim scriptには残念ながらそういった関数が標準では提供されていないようです。どうすればよいでしょうか。
解決
vital.vimという、プラギン埋め込み型の汎用ライブラリを使います。
前述の問題を解決するには、vitalのData.Listモジュールを用いるのがよいでしょう。Data.Listの上記要件を満たす関数は以下の4つで、意味はそれぞれHaskellのPreludeに定義されているものと同じです。
- foldl
- foldl1
- foldr
- foldr1
たとえばfoldl1を用いると、以下のようにしてリストlistの要素の値の総和を求めることができます。
let L = vital#of('プラギン名').import('Data.List')
echo L.foldl1('v:memo + v:val', list)
非常に書きやすく読みやすく、理想的です。なお、実験的にvitalを用いる場合は、vitalを&rtpに入れた上でvital#of('vital')とするとそのvitalを読み込むことができます。
- Comments: 0
- Trackbacks: 0
Hack #234: Vim外にいるときはVimを透けさせる
- 2011-10-05 (水)
- Vim Hacks
こんにちは、ujihisaです。無事引っ越しが完了しましたが、インターネット回線の確保にはもうしばらく時間がかかるらしく、しばらく家でオフラインな状態です。ちなみに来日することが決まりました。こんなカンファレンスや、勉強会を企画してみました。いずれも来月です。
問題
Vim使いの大半は、Vimをフルスクリーンで使っていることと思います。このとき、WindowのfocusがVimの窓にあるときは別に構わないのですが、focusがVimの外にあるときに、つねに背景がVimになってしまいます。せっかくファンシーなデスクトップの壁紙を設定しているのに、システム起動直後と終了直前にしか見れないのは、いささか寂しいものです。
解決
VimにはFocusGainedとFocusLostというイベントがあります。これを用いてVimの透明度を変更すると、とてもいい感じになります。
参考までに、著者のMacVimのための設定を以下に掲載します。~/.vimrcではなく~/.gvimrcに記述してください。
augroup hack234
autocmd!
if has('mac')
autocmd FocusGained * set transparency=10
autocmd FocusLost * set transparency=50
endif
augroup END
普段は透明度10 (ほとんど不透明)にしており、focusがないときは透明度50 (かなり透明) にしています。筆者のGentoo上のGVimは'transparency'をサポートしていないため、if has('mac')で条件分岐しています。


追記
mattnさんがWindows版GVimでも動作するためのプロジェクトを発動させたようです。
ujihisa- Comments: 0
- Trackbacks: 0
- Search
- Feeds
- Links

