Quantcast
Channel: Vimタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 5608

Vim script でクラスベースオブジェクト指向言語の継承っぽいことをする

$
0
0

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 とかみたいな手続き型っぽい言語しか書かないのでなんか勘違いしてるかも。コメント欄でやんややんやしてください。


Viewing all articles
Browse latest Browse all 5608

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>