この記事は Vim 8.0 Advent Calendarの 1 日目の記事です。
Vim 8.0 では Vim script の関数機能が強化されました。この記事では Partials とラムダを紹介します。
Partials
これまでの Vim script では function()
関数で関数参照(Funcref
)を作成できました。これにより、関数を変数に入れ、直接呼び出すことができます。
let Foo =function('strftime')
echo Foo('%Y-%m-%d')" => 2016-12-01
echo Foo('%Y-%m-%d',1482634800)" => 2016-12-25
これに加え、function()
関数に事前に引数を渡すことで、引数部分をバインドした関数参照を作れるようになりました。これを Partial と呼びます。
let Foo =function('strftime', ['%Y-%m-%d'])
echo Foo()" => 2016-12-01
echo Foo(1482634800)" => 2016-12-25
function()
の第2引数以降に辞書を渡すことで、self をバインドすることも可能です。
function! Value()dictreturn self.value
endfunctionletdict= {'value': 10}
let Foo =function('Value',dict)
echo Foo()" => 10
辞書から辞書関数の値を参照すると、自動的に辞書をバインドした関数参照が得られます。
letdict= {'value': 20}
function!dict.get_value()dictreturn self.value
endfunctionlet Foo =dict.get_value
echo Foo()" => 20
Partial から、バインドしている引数や辞書を得るには get()
を使います。
letdict= {'value': 20}
function! AddN(n)dictreturna:n+ self.value
endfunctionlet Add30 =function('AddN', [30],dict)
echo Add30()" => 50
echo get(Add30,'name')" => AddN
echo string(get(Add30,'func'))" => function('AddN')
echo get(Add30,'dict')" => {'value': 20}
echo get(Add30,'args')" => [30]
ラムダ
これまでは関数を作るためには :function
Ex コマンドを使って、複数行に渡ってコードを書く必要がありました。
そのため、sort()
のような一部の関数を受け取る関数の実装が面倒でした。
function! MyCompare(i1, i2)returna:i1 -a:i2
endfunction
echo sort([3,5,4,1,2],'MyCompare')" => [1, 2, 3, 4, 5]
そこで新しくラムダ構文が追加されました。{args -> expr}
という形式で、本文には式のみが書けます。
echo sort([3,5,4,1,2], { i1, i2 -> i1 - i2 })" => [1, 2, 3, 4, 5]
また、map()
と filter()
は今まで文字列で式を渡していましたが、関数参照を渡せるようになりました。
echo map(range(10), { val -> val * 2 })" => [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
クロージャ
関数内で更に関数を定義した際に、クロージャを作れるようになりました。これはどういうことかと言うと、ネストした関数の内側から、外側の関数のローカルスコープを参照できるということです。
クロージャを使うには :function
Ex コマンドに closure
フラグを渡すか、もしくはラムダを使います。
function! Counter()letc=0function!s:count() closure
letc+=1returncendfunctionreturn funcref('s:count')endfunctionlet C1 = Counter()let C2 = Counter()
echo C1()" => 1
echo C2()" => 1
echo C1()" => 2
echo C2()" => 2
関数のスコープに注意してください。関数内であろうとも、関数自体のスコープは関数ローカルにはなりません。グローバル関数を定義すれば、それはグローバル関数になります。恐らくこれは歴史的な理由によるものだと思われます。
バインドされる変数は、クロージャ内部のコード内で表れる変数のみになります。よって、例えば :execute
や eval()
などで動的に参照される変数はバインドされません。
2種類の関数参照
関数参照は function()
関数で生成できますが、これによって生成される関数参照は名前参照になります。これはつまり、関数が同名で再定義された場合、参照先の関数も新しい関数になってしまうということです。
一方で先ほどのクロージャは何度も再定義されることが多く、問題になる場合があります。そこで、関数が上書きされても元の値を参照し続ける新しい参照を作るために、funcref()
関数が追加されました。funcref()
関数の引数は function()
関数と全く同じで、Partial も作成可能です。違いは、funcref()
呼び出し時点での関数自体への参照が得られる点です。これにより参照先の関数が後で上書きされても、元の関数を参照し続けることができます。
注意点として、組み込み関数に対しては使えません。組み込み関数の関数参照を作る場合には function()
を使う必要があります。