Vim 8.0 からの Vim script が以前とは比べ物にならないほど便利なのでプラグインを書くのがはかどります。書く上でいい感じの書き方などを模索していて、いまクラスベースオブジェクト指向言語っぽくできないかといろいろ遊んでいます。具体的に言うとまあ、Python なんですけど、正直 Python も Vim script の100分の1も書いてないのであやふやかも。
まず、クラスはスクリプトローカルな辞書変数を作ってメソッドを生やすことで代替します。コンストラクタは同名のスクリプトローカル関数を定義することにしましょう。 Vim script では変数と関数は別のテーブルにのるらしいのでたぶん大丈夫。
" Foo クラス
let s:Foo = {
\ 'key1': 'value1',
\ }
" Foo クラスのメソッド
function! s:Foo.echo() abort
echomsg 'foo'
endfunction
" Foo クラスのコンストラクタ
function! s:Foo() abort
return deepcopy(s:Foo)
endfunction
次にこの Foo
クラスを継承する Bar
クラスを作ります。
" Bar クラス
let s:Bar = {
\ 'key2': 'value2',
\ }
" Bar クラスのメソッド
function! s:Bar.echo() abort
echomsg 'bar'
endfunction
継承には専用の関数を用意します。
" 継承用の関数
function! s:inherit(subname, supername, args) abort
let super = call('s:' . a:supername, a:args)
let sub = deepcopy(s:[a:subname])
call extend(sub, super, 'keep')
let sub.__SUPER__ = {}
for [key, l:Val] in items(super)
if type(l:Val) == v:t_func || key ==# '__SUPER__'
let sub.__SUPER__[key] = l:Val
endif
endfor
return sub
endfunction
ちょっと読みにくい感じですが、親クラスのコンストラクタを呼び、子クラスのもととなる辞書のコピーに extend()
しているだけです。親クラスのメソッドの関数参照は適当なところに保存しておきます。さて、この s:inherit()
関数を子クラスのコンストラクタで呼ぶことを継承と称することにしましょう。
" Bar クラスのコンストラクタ
function! s:Bar() abort
return s:inherit('Bar', 'Foo', [])
endfunction
動かしてみます
let s:foo = s:Foo()
let s:bar = s:Bar()
call s:foo.echo() " foo
call s:bar.echo() " bar
いいかんじですね。さらに Bar
クラスをつかってその親クラスのメソッドを呼びたい場合もあるかもしれません。これにも挑戦してみましょう。
" 親クラスのメソッドを呼ぶための関数
function! s:super(sub, ...) abort
if !has_key(a:sub, '__SUPER__')
return {}
endif
let level = max([get(a:000, 0, 1), 1])
let supermethods = a:sub
for i in range(level)
let supermethods = supermethods.__SUPER__
endfor
let super = {}
for [key, l:Val] in items(supermethods)
if type(l:Val) == v:t_func
let super[key] = function('s:supercall', [a:sub, l:Val])
endif
endfor
return super
endfunction
function! s:supercall(sub, Funcref, ...) abort
return call(a:Funcref, a:000, a:sub)
endfunction
この s:super()
関数を使うことで親クラスのメソッドを呼び出せます。
call s:super(s:bar).echo() " foo
たぶん親クラスの親クラスとかも行けるはず。
- 書いたものの全体
" Foo クラス
let s:Foo = {
\ 'key1': 'value1',
\ }
" Foo クラスのメソッド
function! s:Foo.echo() abort
echomsg 'foo'
endfunction
" Foo クラスのコンストラクタ
function! s:Foo() abort
return deepcopy(s:Foo)
endfunction
" Bar クラス
let s:Bar = {
\ 'key2': 'value2',
\ }
" Bar クラスのメソッド
function! s:Bar.echo() abort
echomsg 'bar'
endfunction
" Bar クラスのコンストラクタ
function! s:Bar() abort
return s:inherit('Bar', 'Foo', [])
endfunction
" 継承用の関数
function! s:inherit(subname, supername, args) abort
let super = call('s:' . a:supername, a:args)
let sub = deepcopy(s:[a:subname])
call extend(sub, super, 'keep')
let sub.__SUPER__ = {}
for [key, l:Val] in items(super)
if type(l:Val) == v:t_func || key ==# '__SUPER__'
let sub.__SUPER__[key] = l:Val
endif
endfor
return sub
endfunction
" 親クラスのメソッドを呼ぶための関数
function! s:super(sub, ...) abort
if !has_key(a:sub, '__SUPER__')
return {}
endif
let level = max([get(a:000, 0, 1), 1])
let supermethods = a:sub
for i in range(level)
let supermethods = supermethods.__SUPER__
endfor
let super = {}
for [key, l:Val] in items(supermethods)
if type(l:Val) == v:t_func
let super[key] = function('s:supercall', [a:sub, l:Val])
endif
endfor
return super
endfunction
function! s:supercall(sub, Funcref, ...) abort
return call(a:Funcref, a:000, a:sub)
endfunction
let s:foo = s:Foo()
let s:bar = s:Bar()
call s:foo.echo() " foo
call s:bar.echo() " bar
call s:super(s:bar).echo() " foo
ファイルに保存して :source
コマンドで読み込むと動きます。普段 matlab とか Fortran とかみたいな手続き型っぽい言語しか書かないのでなんか勘違いしてるかも。コメント欄でやんややんやしてください。