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