本当にキモい Vim script - 型チェック編

Vim Advent Calendar 2013 の 64 日目の記事です。

Vim script の変数には以下の型があります。

  • Number (数値)
  • String (文字列)
  • Float (浮動小数点数)
  • List (リスト)
  • Dictionary (辞書)
  • Funcref (関数参照)

このうち、String <=> Number 間と Number <=> Float 間では暗黙の型変換が行われます。
ある値が入っている変数に対して、暗黙の型変換が行われない別の型の値を代入しようとするとエラーになります。

" :help sticky-type-checking から抜粋
:let l = "string"
:let l = 44             " 型が文字列から数値に変わる
:let l = [1, 2, 3]      " エラー! l はまだ数値である
:let l = 4.4            " 型が数値から浮動小数点数に変わる
:let l = "string"       " エラー!

まず、String <=> Float が変換されないので、入れる順番によってエラーになったりならなかったりします。キモい。

let a = "foo"
let a = 10
let a = 3.14  " OK

let b = "foo"
let b = 3.14  " エラー!

このエラーを避けるためには、一旦 :unlet する必要があります。特に for で複数の型が入っているリストを回す場合は注意が必要です。

for var in [1, [], "string"]
  echo var
  unlet var  " unlet がないと型が違うためループ時にエラーになる
endfor

更にキモいのは、辞書のメンバー変数に対して代入する時には適用されない点です。

let dict = {}
let dict.var = "foo"
let dict.var = 3.14  " OK!
let dict.var = []    " OK!!!

これと、g: や l: が辞書変数であることを利用すると、以下のような hack が可能です。

let g:foo = "hoge\nhuga"           " これを split した配列に置き換えたい
" let g:foo = split(g:foo, "\n")     " これは当然エラー

" 普通にやるとこう
let g:foo_temp = split(g:foo, "\n")
unlet g:foo
let g:foo = g:foo_temp
unlet g:foo_temp

" hack
" g: はグローバル変数が全て入った辞書であり、その辞書の .foo 要素に対して値を代入している
let g:.foo = split(g:foo, "\n")

便利。…だけど…キモい。