Vim 7.4.2071 で type() の定数が追加された

先日知ったんですが、Vim の patch 7.4.2071 で type() の戻り値の定数が追加されました。

github.com

これは 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_pairBOOST_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() の結果が初期化されてしまう。 また、FF2 には同じ関数を参照してしまうので、意図した結果が帰ってこない。 もし、こういう風に関数を再定義して 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 機能が実装されたました。

github.com

これは :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()

これはすごい。