Vim 7.4.2071 で type() の定数が追加された
先日知ったんですが、Vim の patch 7.4.2071 で type()
の戻り値の定数が追加されました。
これは v:t_xxx
という形で定義されています。
let value = 42 let list = [] let dict = {} let Func = function("map") " 以前のコード echo type(value) == type(0) echo type(list) == type([]) echo type(dict) == type({}) echo type(Func) == type(function("tr")) " 定数を使ったコード echo type(value) == v:t_number echo type(list) == v:t_list echo type(dict) == v:t_dict echo type(Func) == v:t_func
これはかなりべんり。
Boost.Hana を使って名前付き引数
Boost.Hana を使って名前付き引数(っぽいの)をやってみた。
[コード]
#include <iostream> #include <boost/hana.hpp> template<typename T> void print(T t){ std::cout << t[BOOST_HANA_STRING("name")] << ":" << t[BOOST_HANA_STRING("age")] << std::endl; } int main(){ using namespace boost; using namespace boost::hana::literals; print(hana::make_map( hana::make_pair(BOOST_HANA_STRING("name"), "homu"), hana::make_pair(BOOST_HANA_STRING("age"), 13) )); print(hana::make_map( hana::make_pair(BOOST_HANA_STRING("age"), 14), hana::make_pair(BOOST_HANA_STRING("name"), "mami") )); return 0; }
[出力結果]
homu:13 mami:14
一応、それっぽく動作してるんですが、make_pair
や BOOST_HANA_STRING
のあたりがやや冗長ですね。
BOOST_HANA_STRING
将来的には C++17 の user defined literals を利用して "name"_s
みたいに定義できるようになるらしい。
vimfiler.vim でカーソルが移動する度にカーソル下のファイル名を出力する
function! s:set_vimfiler() augroup my-filetype-vimfiler autocmd! * <buffer> autocmd CursorMoved <buffer> execute "normal \<Plug>(vimfiler_print_filename)" augroup END endfunction augroup my-vimfiler autocmd! autocmd FileType vimfiler call s:set_vimfiler() augroup END
なんとなくやってみたら地味に便利。
Vim script の closure が利用できるケース
前回『Vim script の closure
は使うべきではない』と言ってたんですが、次のようなコードだと結構有効的だと思います。
function! s:init(value) let value = a:value " 初期値に基づいて関数を定義する function! s:hoge() closure return value + value endfunction function! s:bar(value) closure return value + a:value endfunction endfunction " 任意の値で初期化して関数を定義する call s:init(42) echo s:hoge() " => 84 echo s:bar(-20) " => 22 " 任意の値で関数を書き換える call s:init(-4) echo s:hoge() " => -8 echo s:bar(-20) " => -24
関数内で関数を定義したいときに closure
を利用して関数外の変数を参照しています。
要は『意図して関数を上書きしてる』ようなコードですね。
あまりこのような『関数内で関数を定義する』コードは見かけないんですが、こういう風に closure
を利用して関数を定義すると『スクリプトローカル変数』を隠蔽することが可能になります。
例えば、closure
を使わない場合は、次のように『スクリプトローカル変数』を使わなければなりません。
function! s:init(value) " 関数から参照するためにスクリプトローカル変数を定義する let s:value = a:value function! s:hoge() return s:value + s:value endfunction function! s:bar(value) return s:value + a:value endfunction endfunction
『スクリプトローカル変数を回避する』のであれば確かに closure
はかなり強力な機能であると思う。
Vim script の closure を使うべきではない使い方
Vim の patch 7.4.2120 において :function
の引数に closure
というキーワードが追加された。
これは関数内から関数外の変数を参照するための機能である。
:func-closure
には次のような使い方の例が載っている。
function! Foo() let x = 0 function! Bar() closure let x += 1 return x endfunction return function('Bar') endfunction let F = Foo() echo F() " => 1 echo F() " => 2 echo F() " => 3
なるほど、確かに便利そうだ。
[問題点]
では、これの何が問題なのか。
まず、Vim script において『ローカル関数』というものは存在しない。
なので上記のコードのように『関数内で定義した関数』は『グローバル関数』(もしくはスクリプトローカル関数)として定義される。
つまりどういうことかというと『Foo()
が呼び出される度に Bar()
関数が上書きされる』ということである。
なので、Foo()
を呼び出す度に F()
の結果がリセットされてしまう。
function! Foo() let x = 0 function! Bar() closure let x += 1 return x endfunction return function('Bar') endfunction let F = Foo() echo F() " => 1 echo F() " => 2 echo F() " => 3 " Bar() 関数が書き換えられる let F2 = Foo() " F と F2 は同じ関数を参照してるため、意図した結果が帰ってこない echo F() " => 1 echo F2() " => 2 echo F() " => 3
上記の場合は、2回目に Foo()
を呼び出したら F()
の結果が初期化されてしまう。
また、F
と F2
には同じ関数を参照してしまうので、意図した結果が帰ってこない。
もし、こういう風に関数を再定義して closure
を利用したいのであればローカル関数を定義したり、関数をコピーする必要がある。
そもそも Vim script で『関数内で関数を定義する』というのは辞書関数を除いて基本的にやるべきではない。
[回避方法]
要は『関数内でグローバル関数(or スクリプトローカル関数)』を定義しなければよい。
こういう時こそ lambda
が利用できる。
function! Foo() let x = 0 return { -> [execute("let x += 1"), x][-1] } endfunction let F = Foo() echo F() " => 1 echo F() " => 2 echo F() " => 3 " 毎回別の関数が生成されるので F と F2 は別の関数を参照する let F2 = Foo() echo F() " => 4 echo F2() " => 1 echo F() " => 5
また、辞書関数を利用しても回避することができる。
function! Foo() let x = 0 let func = {} function! func.call() closure let x += 1 return x endfunction return func.call endfunction let F = Foo() echo F() " => 1 echo F() " => 2 echo F() " => 3 " 毎回別の関数が生成されるので F と F2 は別の関数を参照する let F2 = Foo() echo F() " => 4 echo F2() " => 1 echo F() " => 5
[まとめ]
closure
自体はとても便利な機能であるが、closure
を利用する場合は、『その関数を定義する場所や定義した関数を誰が参照するのか』を注意して使う必要がある。
Vim script の関数に closure 機能が実装された
Vim の patch 7.4.2120 で Vim script の関数に closure 機能が実装されたました。
これは :function
の引数に closure
キーワードを追加することで、関数内から関数外の変数を参照できるような機能です。
function! s:main() let value = 0 let list = [] " closure を付けて関数を定義 function! s:closure() closure " 関数外のローカル変数が参照できる let value += 1 call add(list, value) endfunction call s:closure() call s:closure() call s:closure() echo value " => 3 echo list " => [1, 2, 3] endfunction call s:main()
これはすごい。