Vim scriptのexists→get変換公式

毎日Vim scriptを書いているあなたは、exists関数をよく用いると思います。特に、ユーザーが設定する変数があるかないかで挙動を制御するのに便利ですよね。

existsは、コードを書くときには分かりやすいです。「何某が存在するときはこうで…」と言いながらコードを書けるからです。しかし多くの場合、変数名を繰り返してしまうという問題があります。そういう場合は、get関数を使いましょう。(同じ趣旨の記事は、 vim-jp » Hack #239: グローバル変数を安全に参照する があります)

existsを使っているコードをgetで書き直そう

例えば次のようなケースです。
syntaxm4

    let b:m4_quote = exists('g:m4_default_quote') ? g:m4_default_quote : "`,'"

これは

    let b:m4_quote = get(g:, 'm4_default_quote', "`,'")

と書くことができます。変数名の繰り返しがなくなって、綴りを間違えたりすることもなくなりますね。「同じ事を繰り返すな」そういうふうに幼稚園で習ったはずです。

次のケースは多いのか少ないのか分かりませんが、検索すると幾つか出てきます。existsで調べた変数と、andかorを取るケースです。例えば
vinarise

  if exists('g:vinarise_no_default_keymappings') &&
        \ g:vinarise_no_default_keymappings

vim-surround

if !exists("g:surround_no_mappings") || ! g:surround_no_mappings

syntastic

    if !exists('g:syntastic_enable_perl_checker') || !g:syntastic_enable_perl_checker

こういうケースですね。これらは、次のようにget関数を用いることができます。
vinarise

  if get(g:, 'vinarise_no_default_keymappings')

vim-surround

if !get(g:, 'surround_no_mappings')

syntastic

    if !get(g:, 'syntastic_enable_perl_checker')

変数が存在した時に、もう少し違う条件を課す場合があります。例えば
unite-outline

  if exists('g:unite_source_outline_ctags_program') && !empty(g:unite_source_outline_ctags_program)

これをgetで書くと
unite-outline

  if !empty(get(g:, 'unite_source_outline_ctags_program'))

となります。変数が存在しないときは、 !empty(0) == 0 だからです。

また、次のようなケースもありました。
autodate

  if exists('b:autodate_keyword_pre') && b:autodate_keyword_pre != ''

これは

  if get(b:, 'autodate_keyword_pre', '') != ''

同じくautodate

  if exists('b:autodate_lines') && b:autodate_lines > 0

  if get(b:, 'autodate_lines') > 0

と書けます。

変換公式

さて、以上、幾つかのケースを見てきますと、公式を作りたくなってきます。まず、一番最初の三項演算子のパターン
syntaxm4

    let b:m4_quote = exists('g:m4_default_quote') ? g:m4_default_quote : "`,'"
    ==
    let b:m4_quote = get(g:, 'm4_default_quote', "`,'")

のパターンは

exists('g:foo') ? g:foo : bar == get(g:, 'foo', bar)

と書けます。g:はb:でもs:でも可能です。このパターンは、街頭の女子高生46人に投票していただいた結果、getで書き直して欲しいコードのナンバーワンという結果が出ています。

次に、
vinarise

  if exists('g:vinarise_no_default_keymappings') &&
        \ g:vinarise_no_default_keymappings
  ==
  if get(g:, 'vinarise_no_default_keymappings')

のパターンは

exists('g:foo') && g:foo == get(g:, 'foo')

となります。get()の3つ目の引数を省略すると0になるからです。

更に
vim-surround

if !exists("g:surround_no_mappings") || ! g:surround_no_mappings
==
if !get(g:, 'surround_no_mappings')

!exists('g:foo') || !g:foo == !get(g:, 'foo')

ですね。一つ前の公式の否定形です。

そして

  if exists('b:autodate_keyword_pre') && b:autodate_keyword_pre != ''
  ==
  if get(b:, 'autodate_keyword_pre', '') != ''

のようなケース。これは少し難しいですね。ある関数fがあって、

exists('g:foo') && f(g:foo) == f(get(g:, 'foo', bar))
  (ここでbarは f(bar) == 0 となる値)

となります。先程のもう一つのautodateの例は

  if exists('b:autodate_lines') && b:autodate_lines > 0
  ==
  if get(b:, 'autodate_lines', bar) > 0
     (ここでbarは bar > 0 == 0 となる値 → つまり 0-1 など)
  ==
  if get(b:, 'autodate_lines', 0) > 0
  ==
  if get(b:, 'autodate_lines') > 0

と変形できます。なぜなら、0 > 0 は 0 だからです。

公式集

以上、4つのパターンを見てきました。

(a)

exists('g:foo') ? g:foo : bar == get(g:, 'foo', bar)

(b)

exists('g:foo') && g:foo == get(g:, 'foo')

(c)

!exists('g:foo') || !g:foo == !get(g:, 'foo')

(d)

exists('g:foo') && f(g:foo) == f(get(g:, 'foo', bar))
  (ここでbarは f(bar) == 0 となる値)

他に、少し珍しくはなりますが、次のパターンも考えられます。

(e)

exists('g:foo') && !g:foo == !get(g:, 'foo', 1)

(f)

!exists('g:foo') || g:foo == get(g:, 'foo', 1)

このパターンを使ったものは、使用例は少ないですが、次のようなコードが見つかりました。
vim-surround

  if exists("b:surround_indent") ? b:surround_indent : (!exists("g:surround_indent") || g:surround_indent)

これを、以上で作った公式を用いて変形してみます。
vim-surround

  if exists("b:surround_indent") ? b:surround_indent : (!exists("g:surround_indent") || g:surround_indent)
  ==
  if get(b:, 'surround_indent', !exists("g:surround_indent") || g:surround_indent)
  ==
  if get(b:, 'surround_indent', !get(g:, 'surround_indent', 1))

変形出来ました。

公式がいっぱいあるように見えますが、(c)は(b)の否定ですし、(f)は(e)の否定です。そして(b)も(e)も、(d)の特殊ケースとなっていますので、実質的には(a)と(d)だけですね。本質が見えてしまえば式変形も簡単です。

まとめ

変数をexistsで調べているコードは、getで書けることが多いです。また、andやorで変数名が繰り返しになっている場合、getで書くとすっきりします。ただ、人によってはgetを使ったコードは見難い、すぐに理解できないと思います。他の人やあなた自身が困ることになりますので、技巧的なgetの使用は控えましょう