vim9.txt For Vim バージョン 9.0. Last change: 2022 Sep 15
VIMリファレンスマニュアル by Bram Moolenaar
Vim9 script のコマンドと文法 Vim9 vim9
ほとんどの文法については eval.txt で解説されています。このファイルには Vim9
script の新しい文法と機能について書かれています。
1. Vim9 script とは Vim9-script
2. 変更点 vim9-differences
3. 新しいスタイルの関数 fast-functions
4. 型 vim9-types
5. 名前空間、Import と Export vim9script
6. 将来的な変更: クラス vim9-classes
9. 言語設計の背景 vim9-rationale
==============================================================================
1. Vim9 script とは Vim9-script
Vim script は、互換性の維持に気を配りながら成長してきました。そのため、古い悪
しき仕様を変更できないことが多いほか、Vi との互換性に制約を受けて、より良い解
決策を採用できなくなっています。処理は遅く、実行するたびに各行のパースが行われ
ています。
Vim9 script の主な目的は劇的な性能の向上です。これは、コマンドをより効率よく実
行できる命令にコンパイルすることで実現しています。これにより、10倍から100倍の
実行速度の向上が期待できます。
第2の目的は、Vim script 特有の文法を回避し、より一般的に使われる JavaScript や
TypeScript、Java のようなプログラミング言語に近づけることです。
パフォーマンスの向上は、100% の下位互換性を捨てることによってのみ達成しうるも
のです。例えば、関数の引数を辞書 "a:" から利用できるようにするためには、かなり
のオーバーヘッドが必要になります。そのため、Vim9 script では、この辞書が利用で
きなくなりました。その他の違いは、エラーの処理方法など、より微細なものです。
Vim9 script は以下の場所で使用することができます:
- コマンド :def で定義された関数の中
- コマンド vim9script で始まるスクリプトファイルの中
- 上記のコンテキストで定義された自動コマンド
- コマンド修飾子 vim9cmd が先頭に付いたコマンド
Vim9 script ファイルの中でコマンド :function で関数を定義すると、その中では
旧来の Vim script の記法が、最新の scriptversion とともに有効になります。し
かし、これは混乱を招く可能性があるため、推奨できません。
Vim9 script と旧来の Vim script は同時に利用できます。古いスクリプトを書き換え
なくとも、以前と同様に実行することが可能です。高速化が必要なコードには、:def
で定義する関数を使ったほうが良いかもしれません。
:vim9[cmd] {cmd} :vim9 :vim9cmd E1164
Vim9 script の文法と方式を使用して {cmd} を評価、実行します。
コマンドを入力する時と、旧来のスクリプトや関数内で使用する時に
便利です。
:leg[acy] {cmd} :leg :legacy E1189 E1234
旧来の Vim script の文法と方式を利用して {cmd} を評価、実行し
します。これは Vim9 script、あるいは :def で定義する関数での
み便利です。
Note {cmd} は旧来の Vim script の式として解釈されるため、{cmd}
ではローカル変数 {訳注: Vim9 script におけるローカル変数} を用
いることはできない。
==============================================================================
2. 旧来の Vim script からの変更点 vim9-differences
概要
E1146
Vim9 script と :def で定義する関数を使用する際に最もよく遭遇する変更点の概要
は以下のとおりです:
- コメントは " ではなく、# で始めます:
- 値の代入には :let E1126 を使用せず、変数の宣言には :var を使用します:
- 変数と関数のスコープは、明示しない限りスクリプトローカルです。
- 関数を引数の型、戻り値の型とともに宣言します:
:Print
:append
:change
:d 直接 'd' か 'p' かが続いているもの。
:insert
:k
:mode
:open
:s フラグのみ与えて使用されたとき
:t
:xit
- 一部のコマンド、特に制御構文として使われるコマンドは、省略することはできませ
ん。例えば、:throw を :th と書くことはできません。 vim9-no-shorten
- 波括弧変数は使用できません。
- コマンドの前に範囲指定を置くときは、コロン (:) を前置しなくてはなりません:
:exe を使ってください:
- 式による指定のマッピングを定義する際は、その式はそのマッピングが定義されたス
クリプトのコンテキストで評価されます。
# から始まるコメント
旧来の Vim script のコメントは、ダブルクォーテーションで始めます。Vim9 script
のコメントは # で始めます。
これは、ダブルクォーテーションは文字列の開始を表す文字でもあるからです。多くの
場所、特に改行を含む式の途中では、文字列とコメントの両方が現れるため、どちらを
意味しているのかわかりにくくなってしまいます。この混乱を避けるために、Vim9
script では、# のみをコメントとして認識します。このコメント形式はシェルスクリ
プトやPythonのコードと同じです。
Vi において # は行番号付きでテキストを表示します。Vim9 script では、代わりに
:number を使用します。
可読性を向上するために、コマンドと #、コメント文の間にはスペースをおいてくださ
い:
コメントを #{ で始めてはいけません。旧来の Vim script の辞書リテラルと似てお
り、どちらか判別がつきにくいところではエラーになるからです。折り畳みの開始に使
える #{{ や #{{{ はコメントの始まりになっても良いです。
スクリプトファイルの先頭では、Vim は vim9script コマンドが見つかるまでそのス
クリプトが Vim9 script かを知るすべがありません。なのでその行までは旧来のコ
メントを使う必要があります。:
これは不恰好なので、vim9script を一番最初の行に書くのが良いでしょう:
旧来の Vim script では # は代替ファイル名としても使われます。Vim9 script で
は、代わりに %% を使う必要があります。## の代わりに %%% を使います。(すべての
引数を意味します)
Vim9 関数
E1099
:def で定義された関数はコンパイルされます。処理の実行は多くの場合、通常の関
数に比べて10倍から100倍ほど速くなります。
多くのエラーは関数が実行される前に、コンパイルされる段階で検出されます。読みや
すく理解しやすいコードを強制するために、構文は厳密に定められています。
コンパイルは以下のいずれかのタイミングで実行されます:
- 関数が最初に呼び出されるとき
- 関数が定義された後ろの位置で、スクリプト中に :defcompile コマンドが見つ
かったとき
- 関数に対してコマンド :disassemble が実行されたとき
- コンパイルされた関数から呼び出されたり、関数リファレンスとして使用されたとき
(引数と戻り値の型をチェックできるようにするため)
E1091 E1191
もし関数のコンパイルに失敗した場合は、次その関数が呼ばれたときも再度コンパイル
を試みることはなく、代わりに "E1091: Function is not compiled: {name}" という
エラーを発生させます。{訳注: 日本語メッセージの場合: "E1091: 関数はコンパイル
されていません: {name}"}
コンパイルはまだ作成されていないユーザーコマンドと遭遇したときに失敗するでしょ
う。この場合は execute() を使うことでエラーを関数の実行時に発生するようにす
ることができます。
:def は :function が持っているようなオプションを持っていません:
"range"、"abort"、"dict" や "closure" のこと。:def で定義される関数は常にエ
ラーが発生し次第、実行を中断します (:silent! がコマンドに対して使われた場合
やエラーが :try ブロック内で捕捉された場合でない限り) 。また与えられた「範
囲」も受け取らず、"dict" 属性を持つ関数になることもできません。そして常にク
ロージャとなれます。
vim9-no-dict-function
いずれ、「辞書関数」機構を置き換えるクラスが追加されるでしょう。当面は明示的に
辞書を渡す必要があります。:
一方、旧来の辞書関数を呼ぶことはできます:
引数の型と戻り値の型を指定する必要があります。型には "any" を指定することがで
き、型のチェックは旧来の関数と同様に実行時に行われます。
E1106
引数を参照する際は、他のプログラミング言語と同様、"a:" をつけずに名前だけで指定
することができます。
引数辞書 "a:" と 引数リスト "a:000" はありません。
vim9-variable-arguments E1055 E1160 E1180
可変長引数を定義する場合は TypeScript のように、最後の引数として名前とlist型で
定義します。例えば、数値の可変長引数の例は以下のとおりです:
関数の引数が任意 (引数に既定値が指定されている場合) のときは、その引数に
v:none を渡すことでその既定値を使うことができます。これは既定値を使いたい引数
の後ろの引数に値を指定したいときに便利です。例:
vim9-ignored-argument E1181
引数 "_" (アンダースコア) は引数を無視するのに使えます。これは使わないが呼び出
す際に一致するように引数を与えないといけないようなコールバックにおいて一番便利
です。例えば、map() を使っていて、キーと値の 2 つの引数が与えられる時に引数の
キーを無視するには:
ません。
関数と変数はデフォルトでスクリプトローカル
vim9-scopes
Vim9 script でスクリプト直下に :function や :def を使って関数を定義する
と、関数はプリフィックス "s:" をつけた際のように、スクリプトローカルで定義され
ます。グローバルスコープの関数や変数を定義するにはプリフィックス "g:" をつける
必要があります。 スクリプト内の関数のうち他のスクリプトからインポートされるも
のとオートロードスクリプト内の関数について、他のスクリプトで利用できるようにす
るためには "export" をつける必要があります。
:def で定義される関数内で :function か :def でネストした関数を名前空間の
指定なしに作成したときは、ネストした関数はその関数が定義されたブロックにローカ
ルな関数になります。またその関数は function() に文字列を用いて渡すことはできず、
その関数自身の参照を渡さなければいけません:
詳細: これは "Inner" が実際には生成された名前 {訳注: <lambda>XXX のこと。} を
もつ関数への関数参照になるからです。
関数の中でスクリプトローカル関数を定義することはできません。代わりにローカル関
数を定義して、それをスクリプトローカルな Funcref (これはスクリプトレベルで定義
されていないといけません) に代入することができます。グローバル関数はプリフィッ
クス "g:" を使うことで定義できます。
関数をプリフィックス "s:" や "g:" をつけずに参照した場合、Vim は関数を次のよう
に探します:
- 同じ関数の中、ブロックスコープの中
- スクリプトスコープの中
インポートされた関数は、:import コマンドで決まる名前を前置して見つけられます。
スクリプトローカル関数の参照が "s:" をつけることなく使えるので、スクリプトロー
カル関数の名前はプリフィックス "s:" をつけて宣言した場合でも大文字から始まる必
要があります。旧来の Vim script では "s:funcref" とすることができました。なぜ
なら、"s:funcref" を "funcref" として参照することができなかったからです。しか
し Vim9 script ではそれが可能なので、組み込み関数との名前の干渉を避けるため
に "s:Funcref" のように名前が指定される必要があります。
vim9-s-namespace E1268
Vim9 script において、スクリプトレベルでのプリフィックス "s:" の使用はサポート
されていません。プリフィックスのない全ての関数と変数は全てスクリプトローカルに
なります。
:def で定義される関数内においては、"s:" の使用はスクリプト依存です: 旧来の
Vim script 内ではスクリプトローカルな関数と変数に対して "s:" を使いますが、
Vim9 script 内では使いません。これはこのドキュメントの以下の説明でも同様です。
旧来の関数内においては、スクリプトローカルなアイテムに対しての "s:" の指定は従
来通り必要です。これはスクリプトが Vim9 script であろうが旧来の Vim script で
あろうが関係ありません。
いずれの場合でも、関数は使用されるよりも前に定義されていなくてはなりません。使
用されるタイミングは、コマンド :defcompile によってコンパイルされるとき、ま
たは関数を呼び出す関数がコンパイルされているとき(戻り値の型を確認するため)で
す。
その結果として、名前空間を持たない関数や変数は通常、スクリプト内で定義されてい
るか、インポートされたものかのどちらかで見つけることができます。グローバルな関
数や変数はどこでも定義できます (どこで定義されているか、見つかるといいですね!
しばしば :verbose を使ってどこで最後に値がセットされたか調べることができま
す) 。
E1102
グローバル関数は引き続き、ほとんどいつでも定義し、削除することができます。Vim9
script でのスクリプトローカル関数は、スクリプトが読み込まれたときに一度定義さ
れたきり、そのスクリプト内で削除や置き換えはできません (スクリプトローカル関数の
削除や置き換えはスクリプトの再読み込みをすることでできます)。
関数のコンパイルや、関数の呼び出しが未定義の関数に遭遇したとき、自動コマンド
FuncUndefined は呼び出されません。必要であればオートロード関数を使用したり、
旧来の関数を呼び出すことで FuncUndefined イベントが発生します。
デフォルトでは Vim9 script の再読み込みにより関数と変数がクリアされる
vim9-reload E1149 E1150
旧来の Vim script を2回目に読み込んだときは、何も削除されることはなく、コマン
ドはすでにある変数や関数を置き換えて新しいものを作り、置き換えられなかったもの
はそのまま残しておきます。
Vim9 script を2回目に読み込んだときは、存在するすべてのスクリプトローカルの関
数や変数は削除され、クリーンな状態から開始します。これはプラグインを開発中に、
新しいバージョンを試す際には便利です。いずれかの名前を変えたとしても、古い名前
が残る心配はありません。
消さずに残すには、以下を使用します:
これを使用することで、再読み込みの際に任意の場所で finish コマンドにより脱出
することができます。例えば、バッファローカルオプションが関数に設定され、その関
数を2回以上定義する必要がないとき:
:var、:final や :const で宣言する変数
vim9-declaration :var E1079
E1017 E1020 E1054 E1087 E1108 E1124
ローカル変数は :var で定義する必要があります。ローカル定数は :final または
:const で定義する必要があります。このセクションでは、両者を "変数" と呼ぶこ
とにします。
変数はスクリプトローカルや、関数、コードブロックのスコープで定義できます:
変数は、定義されたコードブロックか、ネストされた配下のブロックで参照することが
できます。コードブロックが終わったあとの処理から参照することはできません:
参照したい場合には、ブロックよりも前で宣言しなくてはなりません:
こちらの方が単純な値については簡潔で早くはありますが。:
意図的に続く処理から変数を隠したいとき、ブロックを使うことができます:
これは特にユーザーコマンドで便利です:
また、自動コマンドでも便利です:
多分 :def で定義される関数を使う方が良く動くでしょうが。
E1022 E1103 E1130 E1131 E1133
E1134
変数を型を指定し、初期値なしで宣言した場合、変数は false (bool 型のとき)、空
(string、list、dict などの型のとき)、あるいはゼロ (number、any などの型のとき)
で初期化されます。これは "any" 型を使う時に特に重要で、初期値は数字のゼロとな
ります。例えば、リストを宣言したとき、要素を追加することができます:
変数を null、例えば null_list、で初期化することは変数を初期化しないこととは
異なります。これはエラーになります:
E1016 E1052 E1066
Vim9 script では :let は使用できません。すでに存在する変数に対してはコマンド
を使用せずに代入します。実際に宣言されることがないので、グローバル変数、ウィン
ドウ変数、タブ変数、バッファ変数、そして Vim の定義済変数についてもコマンドを
使用せずに代入します。またそれらの変数は :unlet によって削除することもできま
す。
E1065
変数を :va で宣言することはできず、それは必ず完全な名前の :var として書か
れなければなりません。これはコードを読みやすくするためのものです。
E1178
:lockvar はローカル変数に対しては動作しません。代わりに :const か :final
を使ってください。
exists() 関数と exists_compiled() 関数はローカル変数あるいは引数に対しては
動作しません。
E1006 E1041 E1167 E1168 E1213
変数と関数と関数の引数は、同じスクリプトファイル内で、すでに定義された、または
インポートされた変数と関数をシャドーイングすることはできません。一方で変数は
Ex コマンドをシャドーイングするので、必要であれば変数の名前を変更してくださ
い。
グローバル変数の前には、スクリプトレベルでも "g:" を付けなければなりません。
グローバル関数は必ず先頭に "g:" を付けなければなりません:
vim9-function-defined-later
グローバル関数はプリフィックス "g:" なしに呼び出すことができますが、それらはコ
ンパイル時に存在していなければなりません。プリフィックスを "g:" をつけること
で、関数が後で定義されても良くなります。例:
もしこのようにすると、たとえ "g:loaded_plugin" が存在しない場合でも、コンパイ
ル時に "PluginFunc" が存在しないというエラーが発生します:
exists_compiled() を使うことでエラーを回避できますが、この場合は
"g:loaded_plugin" が後で定義されている場合でもその関数は呼ばれません:
現在、&opt = value は "opt" オプションに値を設定する目的で使用されているた
め、:substitute コマンドをリピートする目的で ":&" を使用することはできません。
vim9-unpack-ignore
アンパック代入において、アンダースコアは、関数の引数を無視するのと似たように
リストの要素を無視するのに使えます:
アンパックの記法を用いて、一つ以上の変数を一度に宣言することが可能です。
それぞれの変数は型を持つか、値から型を推測することができます:
より読みやすく、後の変更も行いやすいです。
定数
vim9-const vim9-final
定数の働きは言語によって異なります。別の値を代入できない変数を定数とする場合も
あります。JavaScript がその一例です。また、値そのものを不変にすることもあり、
例えばリスト定数の内容を変更することができないとしている場合もあります。
Vim9ではこのどちらも定義することができます。
E1021
変数とその値、両方を定数とするには、:const を使用します。何らかの複合的な値
が変更できないようにする際に使用します。例:
変数の変更のみを禁止するには、:final を使用します。この場合は、中の値自体を
変えることはできます。Java でよく知られるものです。例:
一般に、定数はすべて大文字 (例: ALL_CAPS) で書かれますが、必ずしもそうしなくて
も構いません。
定数宣言は値そのものにのみ適用され、参照先の変数には影響しません。
:call と :eval は不要に
E1190
関数は :call なしで呼ぶことができます:
メソッド呼び出しには eval は必要ありません。Exコマンドと同名の識別子ではない
限り、直接に識別子から呼び出すことができます。関数の場合は、"(" または "->"の
どちらかを改行せずに続けなければなりません。例:
一部の関数と Ex コマンドが紛らわしい場合、コロン (:) を前置することでそれが Ex
コマンドであることを明示することができます。例えば、:substitute コマンドと
substitute() が該当します。substitute( で始まる場合は関数呼び出しですが、
コロンを前置することでコマンドを代わりに使用することができます:
もし式が "!" で始まっているのであれば、それは条件の否定ではなくシェルコマンド
であると解釈されます。したがって、これはシェルコマンドとなります:
Note 変数は使用する前に宣言する必要がありますが、関数は宣言するより前に使用で
きます。これは関数の循環参照を可能にするためです。関数を名前で探さなければなら
ないので、少し効率が悪いです。また、関数名のタイプミスは、関数が呼び出されると
きまで見つかりません。
function() は不要に
ユーザー定義の関数は、function() を使わずとも関数リファレンスとして使用する
ことができます。引数の型と戻り値の型がチェックされます。関数はすでに定義されて
いる必要があります。
function() を使って "func" 型のリファレンスを得た場合、その関数は任意の個数
の引数と任意の戻り値の型 (void を含めて) を持つものとされます。この場合、もし
関数名がクォートで囲まれてるなら、その関数は後から宣言できます。
ラムダ式には -> の代わりに => を使う
vim9-lambda
旧来のスクリプトでは "->" はメソッド呼び出しとラムダ式で混同するおそれがありま
す。また、"{" が見つかったとき、パーサーはラムダ式と辞書の開始を見分けねばなら
ず、そしてそれは引数の型指定により複雑になっています。
この問題を回避するため、Vim9 script ではラムダ式のために違う書式を使用し、それ
は JavaScript に似ています:
"=>" まで含めて、ラムダ式の引数の定義の中では改行することはできません (Vim が
丸カッコで囲まれた式とラムダ式の引数の区別をつけられるようにするため)。 これは
OKです:
旧来の Vim script においては、ラムダ関数はいくつもの余分な引数を与えて呼ぶこと
ができ、そしてその余分な引数を使わないことに対しての警告をする方法がありません
でした。Vim9 script では引数の数は必ず一致しなければなりません。もし任意の
引数、またはその他の引数を受け入れたい場合は、関数が vim9-variable-arguments
を受け入れられるようにする "..._" を使ってください。例:
inline-function E1171
加えて、ラムダ式には {} に複数のステートメントを含むことができます:
閉じの "}" は行の先頭にこなければなりません。後ろに他の文字が続いても良いで
す。例:
す。
command-block E1026
ブロックはユーザーコマンドを定義するのにも使えます。ブロックの内側では Vim9
script の文法が使われます。
もしブロックが辞書を含むのであれば、辞書の閉じカッコは行頭に書かれてはいけませ
ん。さもなくば閉じカッコがブロックの終了としてパースされてしまいます。これは動
作しません:
根拠: "}" がコマンドの後にきてはならないのは、ブロックの閉じカッコを見つけるの
にコマンドのパースが必要だろうからです。一貫性のために、いかなるコマンドも "{"
に続けることはできません。残念ながら、これは "() => { command }" が動作せず、
常に改行が必要であることを意味します。
vim9-curly
辞書リテラルの "{" がステートメントブロックと認識されてしまうのを回避するため
には、括弧で包みます:
さらに、コマンドブロックの開始と混同してしまう場合:
自動行継続
vim9-line-continuation E1097
多くの場合、式が次の行に続くことは明らかです。継続行の先頭に行継続のためのバッ
クスラッシュ (line-continuation 参照) を置く必要はありません。例えば、複数行
にまたぐリストの場合:
角カッコ []、波カッコ {}、または丸カッコの中‘以外で’二項演算子御使用する場
合、その前後で改行することができます。例:
"->" を使用したメソッド呼び出し、そしてドット (.) を使用したメンバー参照の場
合、その前に改行を置くことができます:
複数のコマンドのリストを引数に持つコマンドでは、行の先頭に置かれた文字 | は行
継続を表します:
Note これはヒアドキュメントの最初の行は | で始めることができないことを意味しま
す:
してください。あるいは一時的にフラグ "C" を 'cpoptions' に追加してください:
前で変更され、かつ :enddef の後ろで元に戻されなければなりません。
例えば長い Ex コマンドを分割しているときのような、依然として行連結にバックス
ラッシュが必要な場所では、'#\ ' でコメントを開始することができます:
クスラッシュ抜きで行連結が使用され、かつ行が | で開始しているときにも必要で
す:
E1050
行頭の演算子と識別できるようにするために、範囲指定の前にはコロンを置きま
す。"start" と "print" をつなげる例:
次のように書くと、"start" を代入して、1行表示します:
「範囲」の後ろには必ず Ex コマンドが続かなければなりません。コロンを付けてい
ない時は :call 抜きで関数を呼ぶことができますが、「範囲」の後ろではそれが必
要です。
MyFunc()
:% call MyFunc()
Note +cmd の引数にはコロンは不要です:
関数の定義部においても、引数の間で改行をおくことができます:
継続行を識別することは容易ではないため、コマンドの解析はより厳格化されていま
す。例えば、一行目のエラーにより、2行目は別のコマンドとみなされます:
"_cb: Func})" に保存して閉じます。Vim9 script の中ではこの種のミスを回避するた
めに、コマンド名と引数の間にはスペースを置かなくてはなりません。
E1144
ただし、コマンドの引数に置いたコマンドは認識されません。例えば、"windo echo
expr" に続く "expr" の式の中で改行しても認識されません。
Notes:
- "enddef" は継続行の先頭に置くことはできません。それは関数の終端を意味します。
- 代入式の左辺を複数の行に分割できません。特にリストのアンパック :let-unpack
を使用する場合は注意が必要です。これはOKです:
はOKです:
れるような場合では、Vim にとってコマンドのパースが困難です。このような場合で
では、バックスラッシュを使った行継続を使わなければなりません。
ホワイトスペース
E1004 E1068 E1069 E1074 E1127 E1202
Vim9 script ではホワイトスペースの適切な使用を強制します。これはもはや許可され
ません:
なりません:
ホワイトスペースは大抵の演算子の周りで必須です。
始まりと終わりと除いて、サブリスト (リストのスライス) の ":" の周りにホワイト
スペースが必要です。
otherlist = mylist[v : count] # v:count は異なる意味を持つ
otherlist = mylist[:] # リストのコピーを作る
otherlist = mylist[v :]
otherlist = mylist[: v]
ホワイトスペースは許可されません:
- 関数名と "(" の間:
:set コマンドでのオプション名と続く "&"、"!"、"<"、"="、"+="、"-=" や "^="
の間にはホワイトスペースは許可されません。
波括弧変数の廃止
波括弧変数 curly-braces-names は使用できません。
コマンド修飾子は無視されない
E1176
コマンド修飾子を使わないコマンドにコマンド修飾子を使うとエラーになります。
E1082
同様に、続くコマンドなしにコマンド修飾子を使うことも今はエラーになります。
辞書リテラル
vim9-literal-dict E1014
従来、Vim は波括弧 {} で辞書リテラルの表記をサポートしてきました:
後に、辞書にシンプルなキーを使用することが非常に一般的であることが明らかになっ
たため、キーをクォーテーションなしで指定できる表記が後方互換的に導入されまし
た:
しかし、この #{} という表記は他の言語に比べて異色なものです。キーには式よりも
リテラルを使うほうが一般的で、JavaScript が使っている構文を考えると、辞書リテ
ラルに {} の表記を使うほうがずっと便利です:
これは英数字、アンダースコアとダッシュのキーで利用できます。異なる文字を使用す
る場合は、シングルクォートまたはダブルクォートで囲まれた文字列を使用します:
キーに式を使用する必要がある場合は、JavaScript と同様に角括弧を使用することが
できます:
キーの型には、文字列、数値、真偽値、浮動小数点のいずれかを指定できます。その他
の型の場合はエラーが発生します。[] を使わない場合は文字列として扱われ、先頭の
0 を保持します。[] を使って式が与えられた場合は、式を評価し、文字列に変換しま
す。先頭の 0 は省かれます:
す:
:xit、:t、:k、:append、:change、:insert の廃止
E1100
これらのコマンドは容易にローカル変数の名前と混同します。
:x や :xit の代わりに :exit を使用できます。
:t の代わりに :copy を使用できます。
:k の代わりに :mark を使用できます。
比較
オプション 'ignorecase' は文字列の比較には作用しません。なので、"=~" は "=~#"
と同じように動作します。
現状では、"is" と "isnot" (expr-is と expr-isnot) を文字列に対して使うと
常に偽を返します。旧来の Vim script では文字列の比較を行うだけでしたが、Vim9
script ではそれらは文字列オブジェクトが同一であることをチェックします。文字列
は使われるときに複製されるので、二つの文字列は同一と判定されることはありませ
ん。 (いつか、もし文字列が複製されるのではなく参照カウントで管理されるようにな
ると、この挙動は変更されるかもしれません)
エラー後の中断
旧来の Vim script では、エラーに遭遇したとき、Vim は後続の行の実行を続けます。
これは長いエラーの列を生みかねず、そしてそのエラーを止めるための CTRL-C の入力
が必要になります。Vim9 script ではコマンドの実行は最初のエラーに遭遇した段階で
終了します。例:
For ループ
E1254
ループ変数は先に宣言されていてはいけません:
ただ、グローバル変数を使うことは可能です:
旧来の Vim script では、リストのループ内で現在または前の項目を削除するための
for ループをつくるために幾つかのトリックがあります。Vim9 script では、単純にイ
ンデックスを使用することで、リストから削除された場合はスキップされます。
旧来のスクリプトの例:
1
2
3
4
コンパイルされた Vim9 script での出力は以下の通り:
1
3
一般的に、反復しているリストを変更してはいけません。必要であれば最初にコピーを
作ります。
条件と式
vim9-boolean
条件と式は、他の言語とおよそ同じように扱われます。いくつかの値は旧来の Vim
script と扱いが異なります:
値 旧来の Vim script Vim9 script
0 falsy falsy
1 truthy truthy
99 truthy エラー!
"0" falsy エラー!
"99" truthy エラー!
"text" falsy エラー!
"??" 演算子か "!" を使用している場合はエラーとなることはなく、すべての値は
falsy か truthy として評価されます。これは JavaScript とほぼ同じですが、空の
リストと辞書は falsy として評価されます:
型 真と評価される値
bool true, v:true または 1
number 非0
float 非0
string 空文字列以外
blob 空ブロブ以外
list 空リスト以外 (JavaScript とは異なります)
dictionary 空辞書以外 (JavaScript とは異なります)
func 関数名があるとき
special true または v:true
job 非 NULL
channel 非 NULL
class 非 NULL
object 非 NULL (TODO: isTrue() が true を返すとき)
真偽値演算子 "||" と "&&" は、値が真偽値、0または1であることを期待します:
"!" を使って論理否定をすると、どのような型に対しても結果は真偽値になります。
"!!" と二重論理否定をすることで、あらゆる型を真偽値に変換することができます:
文字列の結合に .. を使用すると、すべての単純型の被演算子は常に文字列に変換さ
れます:
単純型とは、文字列 (string)、数値 (float)、特殊値 (special) と真偽値 (bool) で
す。他の型では string() を使う必要があります。
false true null E1034
Vim9 script では以下の定義済みの値が使えます:
v:null と同じです。
null の型が "special" であるのに対し、他の "null_" の値の型はそれぞれの名前
で示される型になります。かなり多くの場面で null 値は空の値と同値と扱われます
が、いつでもそうだというわけではありません。スクリプトローカル変数は :unlet
で削除することができないので、これらの null 値はスクリプトローカル変数をクリア
するのに便利です。例:
また、それらの値は引数の既定値とするのにも便利です:
null はどんな値と比較すること可能で、型エラーが発生することはありません。
しかし、null と数値、浮動小数点数、真偽値との比較は常に false になります。
これは null と 0 あるいは false を比較した時に true になる旧来の Vim
script とは異なる点です。
真偽値を文字列に変換するときは、旧来の Vim script のように v:false と
v:true が使われるのではなく、false と true が使われます。v:none につい
ては、他の言語に同等のものが存在しないので、v:none が none に変換されるよ
うになることはありません。
文字列に対してインデックス [idx] や [idx : idx] を使用すると、バイト単位ではな
く文字単位のインデックスとして扱われます。結合文字が含まれています。例:
では文字列 'á' が得られます。
負のインデックスを指定すると、文字列の末尾から数えられます。"[-1]" は最後の文
字です。
最後の文字を除外するには slice() を使用します。
合成文字を分けてカウントするには strcharpart() を使ってください。
インデックスが範囲外の場合は、空文字列になります。
旧来のスクリプトでは "++var" と "--var" は寡黙に処理され、何の効果ももたらしま
せん。これは Vim9 script ではエラーになります。
ゼロから始まる数値は8進数とはみなされません、"0o" から始まる数値だけが8進数と
みなされます: "0o744"。scriptversion-4
気をつけるべきこと
vim9-gotchas
Vim9 は、一般的なプログラミング言語に近づくように設計されていますが、同時に旧
来の Vim コマンドをサポートしようとしています。そのため、いくつかの妥協をしな
ければなりませんでした。ここでは、意外と知られていないことをまとめてみました。
Exコマンドの範囲指定にはコロンを前置する必要があります。
いくつかのExコマンドは Vim9 script の代入式と紛らわしくなります:
コマンド :global や :substitute と式や代入文が紛らわしくなるのを避けるた
め、これらのコマンドが一文字に省略されているとき、一部のセパレータは使うことが
できません: ':'、'-' と '.' が利用不可です。:
同様に、コマンドとセパレータの間にスペースがあってはいけません:
:def で定義した関数はすべてコンパイルされます。旧来の関数は途中で脱出するこ
とができ、それ以降の行はパースされません:
置くことができます:
vim9-user-command
関数のコンパイルによる他の副作用として、ユーザーコマンドの存在がコンパイルの時
点でチェックされます。ユーザーコマンドが後で定義されている場合、エラーとなりま
す。これはOKです:
Note 認識されていないコマンドを "|" でつなぐと、その後のコマンドは認識されませ
ん。次のような記述は endif がないというエラーになります:
その他の変更点
パターンは、明示的に上書きされない限り 'magic' が設定されている状態と同様に作
用します。
オプション 'edcompatible' の値は使用されません。
オプション 'gdefault' の値は使用されません。
また、このwikiも参考になるでしょう。これは Vim9 script のアーリーアダプターに
よって書かれました: https://github.com/lacygoill/wiki/blob/master/vim/vim9.md
:++ :--
++ と -- コマンドが追加されました。1 を足し引きするのとそっくりです。
++var
var += 1
--var
var -= 1
式中で ++var や --var を使うことはまだサポートされていません。
==============================================================================
3. 新しいスタイルの関数 fast-functions
:def E1028
:def[!] {name}([arguments])[: {return-type}]
{name} という名前の新しい関数を定義します。関数の本体
は次の行から :enddef と一致するまで続きます。
E1073
E1011
{name} は必ず 100 バイト未満でなければなりません。
E1003 E1027 E1056 E1059
:return で返される値の型は必ず {return-type} と一致
しなければなりません。{return-type} が省略された、ある
いは "void" である場合は、その関数は何も返さないと想定
されます。
E1077 E1123
{arguments} は 0 あるいはそれ以上の引数の宣言列です。
引数の宣言には 3 つの書式があります:
{name}: {type}
{name} = {value}
{name}: {type} = {value}
最初の書式は必須の引数の書式で、呼び出す側は必ず実引数
を与える必要があります。
2 つ目と 3 つ目の書式は任意の引数の書式です。呼び出す
側が実引数を省略した場合は、{value} が使われます。
関数は呼び出された時、:disassemble が使われたとき、
あるいは :defcompile が使われたときに命令列にコンパ
イルされます。文法および型のエラーはこの時に提示されま
す。
:def を他の :def や :function の内側で大体 50 階
層の深さまでまでネストすることが可能です。
E1117
[!] は :function と同様に使われます。Note Vim9
script において、スクリプトローカル関数は後で削除され
たり再定義されたりしてはいけません。スクリプトローカル
の削除は、同じスクリプトの再読み込みによってのみ行えま
す。
:enddef E1057 E1152 E1173
:enddef :def で定義された関数の終了。:enddef はそれだけで
行にあるべきです。
また、このwikiも参考になるでしょう。これは Vim9 script のアーリーアダプターに
よって書かれました: https://github.com/lacygoill/wiki/blob/master/vim/vim9.md
もし関数が定義されたスクリプトが Vim9 script であるなら、スクリプトローカル変
数はプリフィックス "s:" なしでアクセスすることができます。それらは関数がコンパ
イルされる前に定義されている必要があります。もし関数が定義されたスクリプトが旧
来の Vim script であるなら、スクリプトローカル変数は、コンパイル時に存在しない
場合は、プリフィックス "s:" をつけてアクセスする必要があります。
E1269
Vim9 script では、スクリプトローカル変数はスクリプトレベルで宣言されなければ
なりません。それらは関数内で、旧来の関数内でも作成することはできません。
:defc :defcompile
:defc[ompile] 現在のスクリプトで定義されている関数のうち、まだコンパ
イルされていないものをコンパイルします。これはコンパイ
ル時に見つかったいかなるエラーも報告します。
:defc[ompile] {func}
:defc[ompile] debug {func}
:defc[ompile] profile {func}
必要であれば関数 {func} をコンパイルします。"debug" と
"profile" はコンパイルモードを指定するのに使います。こ
れはコンパイル時に見つかったいかなるエラーも報告しま
す。
:disa :disassemble
:disa[ssemble] {func} {func} 用に生成された命令列を表示します。これはデバ
ッグ及びテスト用です。E1061
Note {func} のコマンドライン補完において、スクリプト
ローカル関数を見つけるのに "s:" を前置することができ
ます。
:disa[ssemble] profile {func}
:disassemble と似ていますが、プロファイルをとるとき
に使われる命令列を表示します。
:disa[ssemble] debug {func}
:disassemble と似ていますが、デバッグをするときに使
われる命令列を表示します。
制約
ローカル変数は文字列式からは見えません。例:
この map の引数は関数のスコープ抜きに評価される文字列式です。代わりにラムダ式
を使ってください:
:edit のような、コンパイルされないコマンドには、バッククォートによる展開が
使え、またそれはローカルスコープを使うことができます。例:
ループ内で定義されたクロージャは同じコンテキストを共有します。例:
クロージャはそのコンテキスト内の変数を見つけられるように、必ずそれが定義された
コンテキストでコンパイルされます。これは関数がコンパイルされた後に :breakadd
で関数がデバッグ対象だとマークされたときを除いて、これは大体正しく行われます。
必ず外側の関数がコンパイルされるより前にブレークポイントを定義するように気をつ
けてください。
「ループ中」の変数は一度しか存在しません。リストに入れた全てのクロージャは同じ
インスタンス、すなわち最後に 4 の値をもつインスタンスを参照します。これは効率
的で、何回もループする場合でも同様です。もしそれぞれのクロージャでコンテキスト
を分けたいのであれば、コンテキストを定義するため関数を呼んでください:
いくらかの場面で、特に旧来の Vim script のコンテキストから Vim9 のクロージャを
呼ぶとき、その評価は失敗するでしょう。 E1248
Note スクリプトレベルにおいて、ループ変数はループの後では無効になります。これ
はループ変数が後で呼ばれるクロージャで使われている場合、例えばタイマーと組み合
わせる場合でも同様です。これは E1302 エラーを発生させます:
ブロックを使って変数を定義し、その変数をクロージャで使う必要があります:
タイマーにおいて :echowindow を使うのは便利です。メッセージはポップアップに
表示され、タイマーがトリガーされたときにユーザーが行っていることに干渉しませ
ん。
旧来の関数から Vim9 の関数への変換
convert_legacy_function_to_vim9
これらが旧来の関数から Vim9 の関数へ変換するために行われる必要のある変更の大部
分です。
- func や function を def に変更する。
- endfunc や endfunction を enddef に変更する。
- 関数の引数に型をつける。
- もし関数が何か返すのであれば、戻り値の型をつける。
- コメントが " に代わって # で始まるように変更する。
例えば旧来の Vim script の関数:
- 引数に使われる "a:" を削除する。 例:
- 変数の宣言に使われる let を var に変更する。
- 変数への値の代入に使われる let を削除する。これは既に宣言されているローカ
ル変数と b: w: g: t: 変数が対象である。
例えば旧来の Vim script の関数:
- 式中の必要なところへホワイトスペースを挿入する。
- 結合に使われる "." を ".." に変更する。
例えば旧来の Vim script の関数:
- 常に行継続にバックスラッシュが必要なわけではない:
==============================================================================
4. 型 vim9-types
E1008 E1009 E1010 E1012
E1013 E1029 E1030
以下の組み込み型がサポートされています:
bool
number
float
string
blob
list<{type}>
dict<{type}>
job
channel
func
func: {type}
func({type}, ...)
func({type}, ...): {type}
void
まだサポートされていません:
tuple<a: {type}, b: {type}, ...>
これらの型は宣言において使えますが、いかなる単純値も実際に "void" 型を持つこと
はありません。void (例えば、戻り値のない関数) を使おうとするとエラーがでます。
E1031 E1186
配列型はありません。代わりに list<{type}> を使ってください。不変のリストに
対しては大量の細かいメモリを割り当てするのを避ける効率的な実装が使われます。
E1005 E1007
部分適用と関数は幾らかの方法で宣言することができます:
func 任意の種類の関数参照。引数や戻り値への型チェッ
クはない。
func: void 任意の数および型の引数で、戻り値はない。
func: {type} 任意の数および型の引数で、特定の型の戻り値があ
る。
func() 引数がなく、値を返さない関数。
func(): void 同上
func(): {type} 引数がなく、戻り値の型がある関数。
func({type}) 引数の型があり、値を返さない関数。
func({type}): {type} 引数の型と戻り値の型がある関数。
func(?{type}) 任意の引数の型があり、値を返さない関数。
func(...{type}) 可変長の引数の型があり、値を返さない関数。
func({type}, ?{type}, ...{type}): {type}
以下をもつ関数:
- 必須の引数の型
- 任意の引数の型
- 可変長引数の型
- 戻り値の型
もし戻り値の型が "void" なら、関数は値を返しません。
関数参照はそれが呼び出し側から見えない追加の引数および・あるいは辞書を保存して
いる場合、Partial にすることもできます。それらは同じように呼び出されるた
め、宣言も同じです。
:type を使ってカスタム型を定義できます:
め、カスタム型は大文字から始まらなければなりません。
{not implemented yet}
そしてクラスとインターフェイスも型として使えます:
変数の型と型キャスト
variable-types
Vim9 script か :def で定義される関数内で宣言された変数は明示的に示された型
か、初期値から推測された型のどちらかの型を持っています。
グローバル、バッファ、ウィンドウ、タブページ変数は特定の型を持たず、値はいつで
も書き換えられ、そしてそれは型の変更も含み得ます。なので、コンパイルされたコー
ドでは "any" 型が仮定されます。
これは "any" 型が望ましくなく、実際の型が常に同じであると想定されるときに問題
になり得ます。例えば、リストを宣言したとき:
左辺への代入の前にリストの型をチェックするための命令が生成され、少々非効率で
す。
type-casting E1104
これを避けるには、型キャストを使ってください:
しそうでないならエラーを与えます。これは型キャストと呼ばれます。
型キャストの文法は: "<" {type} ">" です。"<" の後ろ、あるいは ">" の前にホワイ
トスペースがあってはいけません (小なりと大なりの演算子との混乱を避けるためで
す)。
意味としては、必要であれば実行時の型チェックが行われます。実際に値が変更される
ことはありません。もし型を変える必要があるのであれば、例えば文字列に変換するの
であれば string() 関数を使ってください。あるいは文字列を数値に変換するのであ
れば str2nr() 関数を使ってください。
もし想定されない場所で型が与えられた場合、E1272 を得るかもしれません。
型インターフェイス
type-inference
一般的に: 型が明確な時はいつも型を省略することができます。例えば、変数を宣言
し、値を与えた時:
リストと辞書の型は、要素の値の型の共通のものからきます。もしその値が全て同じ型
をもつなら、その型がリストか辞書に使われます。もし型が混在しているなら、"any"
型が使われます。
関数参照の共通の型は、もしそれらが全て同じ数の引数をもつのでなければ、引数の
数が指定されていないことを示すため "(...)" を使います。例:
list<func(...)>
Vim9 script のスクリプトローカル変数は型がチェックされ、それは変数が旧来の Vim
script の関数内で宣言されたときでも同様です。
型が宣言されたとき、これはリストや辞書に付け加えられます。後のある式がその型を
変えようとすると、エラーが与えられます:
もし型が宣言されていないのであれば、そのときは変えることは許可されます:
変数の宣言においては、推測された型は重要です:
より厳格な型チェック
type-checking
旧来の Vim script では、数値が予期されるところでは、文字列は自動的に数値に変換
されます。これは "123" のような実際の数値に対しては便利でしたが、もし文字列が
数字で始まらないときは予期しない問題 (加えてエラーメッセージなしで) を引き起こ
します。これは頻繁に見つけにくいバグを引き起こします。例:
思いがけないスペースがあるとき:
E1206 E1210 E1212
Vim9 script ではこれは厳格にされています。使われている値が予期される型と一致す
る場合は、ほとんどの場所で前と同じように動作します。時々エラーになり、それゆえ
後方互換性が壊れています。例:
- 真偽値が期待されるところで、値が 0 か 1 でない数値を使う。 E1023
- 数値のオプションを設定するときに文字列を使う。 E1024 E1105
- 文字列が期待されるところで数値を用いる。
一つの影響として、型が宣言された場合は map() に渡されたリストか辞書の要素の
型は変更されてはいけません。これは Vim9 script ではエラーになります:
{訳注: 日本語メッセージの場合: "E1012: 型が不一致です。number が必要で
すが string でした (map() 内)"}
代わりに、新しくリストを作成する mapnew() を使ってください:
もし要素の型が宣言されていない、あるいは "any" と決定されているなら、型はより
具体的なものに変更することができます。例えば、型の混ざったリストが文字列のリス
トに変更される場合:
リスト定数を直接使うのと、変数宣言を通して使うのには少しの差異があります。
理由は型推論で、リスト定数を変数を初期化するのに使うとき、それは同時に宣言した
型をつけます:
リスト定数を直接使うときは、型は宣言されず、変更することが許可されます:
The reasoning behind this is that when a type is declared and the list is
passed around and changed, the declaration must always hold. So that you can
rely on the type to match the declared type. For a constant this is not
needed.
E1158
Same for extend(), use extendnew() instead, and for flatten(), use
flattennew() instead. Since flatten() is intended to always change the
type, it can not be used in Vim9 script.
E1211 E1217 E1218 E1219 E1220 E1221
E1222 E1223 E1224 E1225 E1226 E1227
E1228 E1238 E1250 E1251 E1252 E1253
E1256 E1297 E1298 E1301
Types are checked for most builtin functions to make it easier to spot
mistakes.
==============================================================================
5. Namespace, Import and Export
vim9script vim9-export vim9-import
A Vim9 script can be written to be imported. This means that some items are
intentionally exported, made available to other scripts. When the exporting
script is imported in another script, these exported items can then be used in
that script. All the other items remain script-local in the exporting script
and cannot be accessed by the importing script.
This mechanism exists for writing a script that can be sourced (imported) by
other scripts, while making sure these other scripts only have access to what
you want them to. This also avoids using the global namespace, which has a
risk of name collisions. For example when you have two plugins with similar
functionality.
You can cheat by using the global namespace explicitly. That should be done
only for things that really are global.
Namespace
vim9-namespace
To recognize a file that can be imported the vim9script statement must
appear as the first statement in the file (see vim9-mix for an exception).
It tells Vim to interpret the script in its own namespace, instead of the
global namespace. If a file starts with:
be available as g:myvar from any other script and function.
E1101
The variables at the file level are very much like the script-local "s:"
variables in legacy Vim script, but the "s:" is omitted. And they cannot be
deleted.
In Vim9 script the global "g:" namespace can still be used as before. And the
"w:", "b:" and "t:" namespaces. These have in common that variables are not
declared, have no specific type and they can be deleted. E1304
A side effect of :vim9script is that the 'cpoptions' option is set to the
Vim default value, like with:
The original value of 'cpoptions' is restored at the end of the script, while
flags added or removed in the script are also added to or removed from the
original value to get the same effect. The order of flags may change.
In the vimrc file sourced on startup this does not happen.
vim9-mix
There is one way to use both legacy and Vim9 syntax in one script file:
syntax if possible, but will also work on a Vim version without it.
This can only work in two ways:
1. The "if" statement evaluates to false, the commands up to endif are
skipped and vim9script is then the first command actually executed.
2. The "if" statement evaluates to true, the commands up to endif are
executed and finish bails out before reaching vim9script.
Export
:export :exp
Exporting an item can be written as:
As this suggests, only constants, variables, :def functions and classes can
be exported. {not implemented yet: class, interface}
E1042
:export can only be used in Vim9 script, at the script level.
Import
:import :imp E1094 E1047 E1262
E1048 E1049 E1053 E1071 E1088 E1236
The exported items can be imported in another script. The import syntax has
two forms. The simple form:
Where {filename} is an expression that must evaluate to a string. In this
form the filename should end in ".vim" and the portion before ".vim" will
become the script local name of the namespace. For example:
This makes each exported item in "myscript.vim" available as "myscript.item".
:import-as E1257 E1261
In case the name is long or ambiguous, this form can be used to specify
another name:
In this form {name} becomes a specific script local name for the imported
namespace. Therefore {name} must consist of letters, digits and '_', like
internal-variables. The {longfilename} expression must evaluate to any
filename. For example:
Then you can use "that.item", etc. You are free to choose the name "that".
Use something that will be recognized as referring to the imported script.
Avoid command names, command modifiers and builtin function names, because the
name will shadow them. Better not start the name starts with a capital
letter, since it can then also shadow global user commands and functions.
Also, you cannot use the name for something else in the script, such as a
function or variable name.
In case the dot in the name is undesired, a local reference can be made for a
function:
This also works for constants:
This does not work for variables, since the value would be copied once and
when changing the variable the copy will change, not the original variable.
You will need to use the full name, with the dot.
:import can also be used in legacy Vim script. The imported items still
become script-local, even when the "s:" prefix is not given.
:import can not be used in a function. Imported items are intended to exist
at the script level and only imported once.
The script name after import can be:
- A relative path, starting "." or "..". This finds a file relative to the
location of the script file itself. This is useful to split up a large
plugin into several files.
- An absolute path, starting with "/" on Unix or "D:/" on MS-Windows. This
will rarely be used.
- A path not being relative or absolute. This will be found in the
"import" subdirectories of 'runtimepath' entries. The name will usually be
longer and unique, to avoid loading the wrong file.
Note that "after/import" is not used.
If the name does not end in ".vim" then the use of "as name" is required.
Once a vim9 script file has been imported, the result is cached and used the
next time the same script is imported. It will not be read again.
It is not allowed to import the same script twice, also when using two
different "as" names.
When using the imported name the dot and the item name must be in the same
line, there can be no line break:
When you've imported a function from one script into a vim9 script you can
refer to the imported function in a mapping by prefixing it with <SID>:
When the mapping is defined "<SID>name." will be replaced with <SNR> and the
script ID of the imported script.
An even simpler solution is using <ScriptCmd>:
Note that this does not work for variables, only for functions.
import-legacy legacy-import
:import can also be used in legacy Vim script. The imported namespace still
becomes script-local, even when the "s:" prefix is not given. For example:
And using the "as name" form:
However, the namespace cannot be resolved on its own:
This also affects the use of <SID> in the legacy mapping context. Since
<SID> is only a valid prefix for a function and NOT for a namespace, you
cannot use it
to scope a function in a script local namespace. Instead of prefixing the
function with <SID> you should use<ScriptCmd>. For example:
:import-cycle
The import commands are executed when encountered. If script A imports
script B, and B (directly or indirectly) imports A, this will be skipped over.
At this point items in A after "import B" will not have been processed and
defined yet. Therefore cyclic imports can exist and not result in an error
directly, but may result in an error for items in A after "import B" not being
defined. This does not apply to autoload imports, see the next section.
Importing an autoload script
vim9-autoload import-autoload
For optimal startup speed, loading scripts should be postponed until they are
actually needed. Using the autoload mechanism is recommended:
E1264
1. In the plugin define user commands, functions and/or mappings that refer to
items imported from an autoload script.
This goes in .../plugin/anyname.vim. "anyname.vim" can be freely chosen.
The "SearchForStuff" command is now available to the user.
The "autoload" argument to :import means that the script is not loaded
until one of the items is actually used. The script will be found under
the "autoload" directory in 'runtimepath' instead of the "import"
directory. Alternatively a relative or absolute name can be used, see
below.
2. In the autoload script put the bulk of the code.
This goes in .../autoload/for/search.vim.
Putting the "search.vim" script under the "/autoload/for/" directory has
the effect that "for#search#" will be prefixed to every exported item. The
prefix is obtained from the file name, as you would to manually in a
legacy autoload script. Thus the exported function can be found with
"for#search#Stuff", but you would normally use import autoload and not
use the prefix (which has the side effect of loading the autoload script
when compiling a function that encounters this name).
You can split up the functionality and import other scripts from the
autoload script as you like. This way you can share code between plugins.
Searching for the autoload script in all entries in 'runtimepath' can be a bit
slow. If the plugin knows where the script is located, quite often a relative
path can be used. This avoids the search and should be quite a bit faster.
Another advantage is that the script name does not need to be unique. An
absolute path is also possible. Examples:
For defining a mapping that uses the imported autoload script the special key
<ScriptCmd> is useful. It allows for a command in a mapping to use the
script context of where the mapping was defined.
When compiling a :def function and a function in an autoload script is
encountered, the script is not loaded until the :def function is called.
This also means you get any errors only at runtime, since the argument and
return types are not known yet. If you would use the name with '#' characters
then the autoload script IS loaded.
Be careful to not refer to an item in an autoload script that does trigger
loading it unintentionally. For example, when setting an option that takes a
function name, make sure to use a string, not a function reference:
any errors should be given.
For testing the test_override() function can be used to have the
import autoload load the script right away, so that the items and types can
be checked without waiting for them to be actually used:
==============================================================================
6. Future work: classes vim9-classes
Above "class" was mentioned a few times, but it has not been implemented yet.
Most of Vim9 script can be created without this functionality, and since
implementing classes is going to be a lot of work, it is left for the future.
For now we'll just make sure classes can be added later.
Thoughts:
- class / endclass, the whole class must be in one file
- Class names are always CamelCase (to avoid a name clash with builtin types)
- A single constructor called "constructor"
- Single inheritance with class ThisClass extends BaseClass
- abstract class (class with incomplete implementation)
- interface / endinterface (abstract class without any implementation)
- class SomeClass implements SomeInterface
- Generics for class: class <Tkey, Tentry>
- Generics for function: def <Tkey> GetLast(key: Tkey)
Again, much of this is from TypeScript with a slightly different syntax.
Some things that look like good additions:
- Use a class as an interface (like Dart)
- Extend a class with methods, using an import (like Dart)
- Mixins
- For testing: Mock mechanism
An important class that will be provided is "Promise". Since Vim is single
threaded, connecting asynchronous operations is a natural way of allowing
plugins to do their work without blocking the user. It's a uniform way to
invoke callbacks and handle timeouts and errors.
Some commands have already been reserved:
:class
:endclass
:abstract
:enum
:endenum
:interface
:endinterface
:static
:type
Some examples:
==============================================================================
9. Rationale vim9-rationale
The :def command
Plugin writers have asked for much faster Vim script. Investigations have
shown that keeping the existing semantics of function calls make this close to
impossible, because of the overhead involved with calling a function, setting
up the local function scope and executing lines. There are many details that
need to be handled, such as error messages and exceptions. The need to create
a dictionary for a: and l: scopes, the a:000 list and several others add too
much overhead that cannot be avoided.
Therefore the :def method to define a new-style function had to be added,
which allows for a function with different semantics. Most things still work
as before, but some parts do not. A new way to define a function was
considered the best way to separate the legacy style code from Vim9 style code.
Using "def" to define a function comes from Python. Other languages use
"function" which clashes with legacy Vim script.
Type checking
When compiling lines of Vim commands into instructions as much as possible
should be done at compile time. Postponing it to runtime makes the execution
slower and means mistakes are found only later. For example, when
encountering the "+" character and compiling this into a generic add
instruction, at runtime the instruction would have to inspect the type of the
arguments and decide what kind of addition to do. And when the type is
dictionary throw an error. If the types are known to be numbers then an "add
number" instruction can be used, which is faster. The error can be given at
compile time, no error handling is needed at runtime, since adding two numbers
cannot fail.
The syntax for types, using <type> for compound types, is similar to Java. It
is easy to understand and widely used. The type names are what were used in
Vim before, with some additions such as "void" and "bool".
Removing clutter and weirdness
Once decided that :def functions have different syntax than legacy functions,
we are free to add improvements to make the code more familiar for users who
know popular programming languages. In other words: remove weird things that
only Vim does.
We can also remove clutter, mainly things that were done to make Vim script
backwards compatible with the good old Vi commands.
Examples:
- Drop :call for calling a function and :eval for evaluating an
expression.
- Drop using a leading backslash for line continuation, automatically figure
out where an expression ends.
However, this does require that some things need to change:
- Comments start with # instead of ", to avoid confusing them with strings.
This is good anyway, it is also used by several popular languages.
- Ex command ranges need to be prefixed with a colon, to avoid confusion with
expressions (single quote can be a string or a mark, "/" can be divide or a
search command, etc.).
Goal is to limit the differences. A good criteria is that when the old syntax
is accidentally used you are very likely to get an error message.
Syntax and semantics from popular languages
Script writers have complained that the Vim script syntax is unexpectedly
different from what they are used to. To reduce this complaint popular
languages are used as an example. At the same time, we do not want to abandon
the well-known parts of legacy Vim script.
For many things TypeScript is followed. It's a recent language that is
gaining popularity and has similarities with Vim script. It also has a
mix of static typing (a variable always has a known value type) and dynamic
typing (a variable can have different types, this changes at runtime). Since
legacy Vim script is dynamically typed and a lot of existing functionality
(esp. builtin functions) depends on that, while static typing allows for much
faster execution, we need to have this mix in Vim9 script.
There is no intention to completely match TypeScript syntax and semantics. We
just want to take those parts that we can use for Vim and we expect Vim users
will be happy with. TypeScript is a complex language with its own history,
advantages and disadvantages. To get an idea of the disadvantages read the
book: "JavaScript: The Good Parts". Or find the article "TypeScript: the good
parts" and read the "Things to avoid" section.
People familiar with other languages (Java, Python, etc.) will also find
things in TypeScript that they do not like or do not understand. We'll try to
avoid those things.
Specific items from TypeScript we avoid:
- Overloading "+", using it both for addition and string concatenation. This
goes against legacy Vim script and often leads to mistakes. For that reason
we will keep using ".." for string concatenation. Lua also uses ".." this
way. And it allows for conversion to string for more values.
- TypeScript can use an expression like "99 || 'yes'" in a condition, but
cannot assign the value to a boolean. That is inconsistent and can be
annoying. Vim recognizes an expression with && or || and allows using the
result as a bool. The falsy-operator was added for the mechanism to use a
default value.
- TypeScript considers an empty string as Falsy, but an empty list or dict as
Truthy. That is inconsistent. In Vim an empty list and dict are also
Falsy.
- TypeScript has various "Readonly" types, which have limited usefulness,
since a type cast can remove the immutable nature. Vim locks the value,
which is more flexible, but is only checked at runtime.
- TypeScript has a complicated "import" statement that does not match how the
Vim import mechanism works. A much simpler mechanism is used instead, which
matches that the imported script is only sourced once.
Declarations
Legacy Vim script uses :let for every assignment, while in Vim9 declarations
are used. That is different, thus it's good to use a different command:
:var. This is used in many languages. The semantics might be slightly
different, but it's easily recognized as a declaration.
Using :const for constants is common, but the semantics varies. Some
languages only make the variable immutable, others also make the value
immutable. Since "final" is well known from Java for only making the variable
immutable we decided to use that. And then :const can be used for making
both immutable. This was also used in legacy Vim script and the meaning is
almost the same.
What we end up with is very similar to Dart:
Since legacy and Vim9 script will be mixed and global variables will be
shared, optional type checking is desirable. Also, type inference will avoid
the need for specifying the type in many cases. The TypeScript syntax fits
best for adding types to declarations:
This is how we put types in a declaration:
Two alternatives were considered:
1. Put the type before the name, like Dart:
The first is more familiar for anyone used to C or Java. The second one
doesn't really have an advantage over the first, so let's discard the second.
Since we use type inference the type can be left out when it can be inferred
from the value. This means that after var we don't know if a type or a name
follows. That makes parsing harder, not only for Vim but also for humans.
Also, it will not be allowed to use a variable name that could be a type name,
using var string string is too confusing.
The chosen syntax, using a colon to separate the name from the type, adds
punctuation, but it actually makes it easier to recognize the parts of a
declaration.
Expressions
Expression evaluation was already close to what other languages are doing.
Some details are unexpected and can be improved. For example a boolean
condition would accept a string, convert it to a number and check if the
number is non-zero. This is unexpected and often leads to mistakes, since
text not starting with a number would be converted to zero, which is
considered false. Thus using a string for a condition would often not give an
error and be considered false. That is confusing.
In Vim9 type checking is stricter to avoid mistakes. Where a condition is
used, e.g. with the :if command and the || operator, only boolean-like
values are accepted:
true: true, v:true, 1, 0 < 9
false: false, v:false, 0, 0 > 9
Note that the number zero is false and the number one is true. This is more
permissive than most other languages. It was done because many builtin
functions return these values, and changing that causes more problems than it
solves. After using this for a while it turned out to work well.
If you have any type of value and want to use it as a boolean, use the !!
operator:
true: !!'text' !![99] !!{'x': 1} !!99
false: !!'' !![] !!{}
From a language like JavaScript we have this handy construct:
Therefore the "??" operator was added:
result in a boolean. This is called the falsy-operator.
Import and Export
A problem of legacy Vim script is that by default all functions and variables
are global. It is possible to make them script-local, but then they are not
available in other scripts. This defies the concept of a package that only
exports selected items and keeps the rest local.
In Vim9 script a mechanism very similar to the JavaScript import and export
mechanism is supported. It is a variant to the existing :source command
that works like one would expect:
- Instead of making everything global by default, everything is script-local,
some of these are exported.
- When importing a script the symbols that are imported are explicitly listed,
avoiding name conflicts and failures if functionality is added later.
- The mechanism allows for writing a big, long script with a very clear API:
the exported functions, variables and classes.
- By using relative paths loading can be much faster for an import inside of a
package, no need to search many directories.
- Once an import has been used, its items are cached and loading it again is
not needed.
- The Vim-specific use of "s:" to make things script-local can be dropped.
When sourcing a Vim9 script (from a Vim9 or legacy script), only the items
defined globally can be used, not the exported items. Alternatives
considered:
- All the exported items become available as script-local items. This makes
it uncontrollable what items get defined and likely soon leads to trouble.
- Use the exported items and make them global. Disadvantage is that it's then
not possible to avoid name clashes in the global namespace.
- Completely disallow sourcing a Vim9 script, require using :import. That
makes it difficult to use scripts for testing, or sourcing them from the
command line to try them out.
Note that you CAN also use :import in legacy Vim script, see above.
Compiling functions early
Functions are compiled when called or when :defcompile is used. Why not
compile them early, so that syntax and type errors are reported early?
The functions can't be compiled right away when encountered, because there may
be forward references to functions defined later. Consider defining functions
A, B and C, where A calls B, B calls C, and C calls A again. It's impossible
to reorder the functions to avoid forward references.
An alternative would be to first scan through the file to locate items and
figure out their type, so that forward references are found, and only then
execute the script and compile the functions. This means the script has to be
parsed twice, which is slower, and some conditions at the script level, such
as checking if a feature is supported, are hard to use. An attempt was made
to see if it works, but it turned out to be impossible to make work well.
It would be possible to compile all the functions at the end of the script.
The drawback is that if a function never gets called, the overhead of
compiling it counts anyway. Since startup speed is very important, in most
cases it's better to do it later and accept that syntax and type errors are
only reported then. In case these errors should be found early, e.g. when
testing, a :defcompile command at the end of the script will help out.
Why not use an existing embedded language?
Vim supports interfaces to Perl, Python, Lua, Tcl and a few others. But
these interfaces have never become widely used, for various reasons. When
Vim9 was designed a decision was made to make these interfaces lower priority
and concentrate on Vim script.
Still, plugin writers may find other languages more familiar, want to use
existing libraries or see a performance benefit. We encourage plugin authors
to write code in any language and run it as an external process, using jobs
and channels. We can try to make this easier somehow.
Using an external tool also has disadvantages. An alternative is to convert
the tool into Vim script. For that to be possible without too much
translation, and keeping the code fast at the same time, the constructs of the
tool need to be supported. Since most languages support classes the lack of
support for classes in Vim is then a problem.
Classes
Vim supports a kind-of object oriented programming by adding methods to a
dictionary. With some care this can be made to work, but it does not look
like real classes. On top of that, it's quite slow, because of the use of
dictionaries.
It would be good to support real classes, and this is planned for a later
version. The support is a "minimal common functionality" of class support in
most languages. It will work much like Java, which is the most popular
programming language.
vim:tw=78:ts=8:noet:ft=help:norl:
VIMリファレンスマニュアル by Bram Moolenaar
Vim9 script のコマンドと文法 Vim9 vim9
ほとんどの文法については eval.txt で解説されています。このファイルには Vim9
script の新しい文法と機能について書かれています。
1. Vim9 script とは Vim9-script
2. 変更点 vim9-differences
3. 新しいスタイルの関数 fast-functions
4. 型 vim9-types
5. 名前空間、Import と Export vim9script
6. 将来的な変更: クラス vim9-classes
9. 言語設計の背景 vim9-rationale
==============================================================================
1. Vim9 script とは Vim9-script
Vim script は、互換性の維持に気を配りながら成長してきました。そのため、古い悪
しき仕様を変更できないことが多いほか、Vi との互換性に制約を受けて、より良い解
決策を採用できなくなっています。処理は遅く、実行するたびに各行のパースが行われ
ています。
Vim9 script の主な目的は劇的な性能の向上です。これは、コマンドをより効率よく実
行できる命令にコンパイルすることで実現しています。これにより、10倍から100倍の
実行速度の向上が期待できます。
第2の目的は、Vim script 特有の文法を回避し、より一般的に使われる JavaScript や
TypeScript、Java のようなプログラミング言語に近づけることです。
パフォーマンスの向上は、100% の下位互換性を捨てることによってのみ達成しうるも
のです。例えば、関数の引数を辞書 "a:" から利用できるようにするためには、かなり
のオーバーヘッドが必要になります。そのため、Vim9 script では、この辞書が利用で
きなくなりました。その他の違いは、エラーの処理方法など、より微細なものです。
Vim9 script は以下の場所で使用することができます:
- コマンド :def で定義された関数の中
- コマンド vim9script で始まるスクリプトファイルの中
- 上記のコンテキストで定義された自動コマンド
- コマンド修飾子 vim9cmd が先頭に付いたコマンド
Vim9 script ファイルの中でコマンド :function で関数を定義すると、その中では
旧来の Vim script の記法が、最新の scriptversion とともに有効になります。し
かし、これは混乱を招く可能性があるため、推奨できません。
Vim9 script と旧来の Vim script は同時に利用できます。古いスクリプトを書き換え
なくとも、以前と同様に実行することが可能です。高速化が必要なコードには、:def
で定義する関数を使ったほうが良いかもしれません。
:vim9[cmd] {cmd} :vim9 :vim9cmd E1164
Vim9 script の文法と方式を使用して {cmd} を評価、実行します。
コマンドを入力する時と、旧来のスクリプトや関数内で使用する時に
便利です。
:leg[acy] {cmd} :leg :legacy E1189 E1234
旧来の Vim script の文法と方式を利用して {cmd} を評価、実行し
します。これは Vim9 script、あるいは :def で定義する関数での
み便利です。
Note {cmd} は旧来の Vim script の式として解釈されるため、{cmd}
ではローカル変数 {訳注: Vim9 script におけるローカル変数} を用
いることはできない。
==============================================================================
2. 旧来の Vim script からの変更点 vim9-differences
概要
E1146
Vim9 script と :def で定義する関数を使用する際に最もよく遭遇する変更点の概要
は以下のとおりです:
- コメントは " ではなく、# で始めます:
echo "hello" # コメント
- 行継続文字 (\) はほとんどの場合、必要ありません: echo "hello "
.. yourName
.. ", how are you?"
- 可読性を上げるため、多くの場所にスペースが必要になります。.. yourName
.. ", how are you?"
- 値の代入には :let E1126 を使用せず、変数の宣言には :var を使用します:
var count = 0
count += 3
- :final と :const を使用して、定数を宣言できます:count += 3
final matches = [] # 後でこのリストに追加する
const names = ['Betty', 'Peter'] # 変更できない
- :final は :finally の略語として使用することはできません。const names = ['Betty', 'Peter'] # 変更できない
- 変数と関数のスコープは、明示しない限りスクリプトローカルです。
- 関数を引数の型、戻り値の型とともに宣言します:
def CallMe(count: number, message: string): bool
- 関数は :call なしで呼び出します: writefile(['done'], 'file.txt')
- 古い Exコマンドは使用できません::append
:change
:d 直接 'd' か 'p' かが続いているもの。
:insert
:k
:mode
:open
:s フラグのみ与えて使用されたとき
:t
:xit
- 一部のコマンド、特に制御構文として使われるコマンドは、省略することはできませ
ん。例えば、:throw を :th と書くことはできません。 vim9-no-shorten
- 波括弧変数は使用できません。
- コマンドの前に範囲指定を置くときは、コロン (:) を前置しなくてはなりません:
:%s/this/that
- "@r" としてレジスタを実行することはできません。コロン (:) を前置するか、:exe を使ってください:
:exe @a
- 特に指定しない限り、最新の scriptversion が使われます。- 式による指定のマッピングを定義する際は、その式はそのマッピングが定義されたス
クリプトのコンテキストで評価されます。
# から始まるコメント
旧来の Vim script のコメントは、ダブルクォーテーションで始めます。Vim9 script
のコメントは # で始めます。
# 宣言
var count = 0 # 出現回数
var count = 0 # 出現回数
これは、ダブルクォーテーションは文字列の開始を表す文字でもあるからです。多くの
場所、特に改行を含む式の途中では、文字列とコメントの両方が現れるため、どちらを
意味しているのかわかりにくくなってしまいます。この混乱を避けるために、Vim9
script では、# のみをコメントとして認識します。このコメント形式はシェルスクリ
プトやPythonのコードと同じです。
Vi において # は行番号付きでテキストを表示します。Vim9 script では、代わりに
:number を使用します。
:101 number
可読性を向上するために、コマンドと #、コメント文の間にはスペースをおいてくださ
い:
var name = value # コメント
var name = value# エラー!
E1170var name = value# エラー!
コメントを #{ で始めてはいけません。旧来の Vim script の辞書リテラルと似てお
り、どちらか判別がつきにくいところではエラーになるからです。折り畳みの開始に使
える #{{ や #{{{ はコメントの始まりになっても良いです。
スクリプトファイルの先頭では、Vim は vim9script コマンドが見つかるまでそのス
クリプトが Vim9 script かを知るすべがありません。なのでその行までは旧来のコ
メントを使う必要があります。:
" 旧来のコメント
vim9script
# Vim9 のコメント
vim9script
# Vim9 のコメント
これは不恰好なので、vim9script を一番最初の行に書くのが良いでしょう:
vim9script
# Vim9 コメント
# Vim9 コメント
旧来の Vim script では # は代替ファイル名としても使われます。Vim9 script で
は、代わりに %% を使う必要があります。## の代わりに %%% を使います。(すべての
引数を意味します)
Vim9 関数
E1099
:def で定義された関数はコンパイルされます。処理の実行は多くの場合、通常の関
数に比べて10倍から100倍ほど速くなります。
多くのエラーは関数が実行される前に、コンパイルされる段階で検出されます。読みや
すく理解しやすいコードを強制するために、構文は厳密に定められています。
コンパイルは以下のいずれかのタイミングで実行されます:
- 関数が最初に呼び出されるとき
- 関数が定義された後ろの位置で、スクリプト中に :defcompile コマンドが見つ
かったとき
- 関数に対してコマンド :disassemble が実行されたとき
- コンパイルされた関数から呼び出されたり、関数リファレンスとして使用されたとき
(引数と戻り値の型をチェックできるようにするため)
E1091 E1191
もし関数のコンパイルに失敗した場合は、次その関数が呼ばれたときも再度コンパイル
を試みることはなく、代わりに "E1091: Function is not compiled: {name}" という
エラーを発生させます。{訳注: 日本語メッセージの場合: "E1091: 関数はコンパイル
されていません: {name}"}
コンパイルはまだ作成されていないユーザーコマンドと遭遇したときに失敗するでしょ
う。この場合は execute() を使うことでエラーを関数の実行時に発生するようにす
ることができます。
def MyFunc()
execute('DefinedLater')
enddef
execute('DefinedLater')
enddef
:def は :function が持っているようなオプションを持っていません:
"range"、"abort"、"dict" や "closure" のこと。:def で定義される関数は常にエ
ラーが発生し次第、実行を中断します (:silent! がコマンドに対して使われた場合
やエラーが :try ブロック内で捕捉された場合でない限り) 。また与えられた「範
囲」も受け取らず、"dict" 属性を持つ関数になることもできません。そして常にク
ロージャとなれます。
vim9-no-dict-function
いずれ、「辞書関数」機構を置き換えるクラスが追加されるでしょう。当面は明示的に
辞書を渡す必要があります。:
def DictFunc(self: dict<any>, arg: string)
echo self[arg]
enddef
var ad = {item: 'value', func: DictFunc}
ad.func(ad, 'item')
echo self[arg]
enddef
var ad = {item: 'value', func: DictFunc}
ad.func(ad, 'item')
一方、旧来の辞書関数を呼ぶことはできます:
func Legacy() dict
echo self.value
endfunc
def CallLegacy()
var d = {func: Legacy, value: 'text'}
d.func()
enddef
E1096 E1174 E1175echo self.value
endfunc
def CallLegacy()
var d = {func: Legacy, value: 'text'}
d.func()
enddef
引数の型と戻り値の型を指定する必要があります。型には "any" を指定することがで
き、型のチェックは旧来の関数と同様に実行時に行われます。
E1106
引数を参照する際は、他のプログラミング言語と同様、"a:" をつけずに名前だけで指定
することができます。
引数辞書 "a:" と 引数リスト "a:000" はありません。
vim9-variable-arguments E1055 E1160 E1180
可変長引数を定義する場合は TypeScript のように、最後の引数として名前とlist型で
定義します。例えば、数値の可変長引数の例は以下のとおりです:
def MyFunc(...itemlist: list<number>)
for item in itemlist
...
for item in itemlist
...
関数の引数が任意 (引数に既定値が指定されている場合) のときは、その引数に
v:none を渡すことでその既定値を使うことができます。これは既定値を使いたい引数
の後ろの引数に値を指定したいときに便利です。例:
def MyFunc(one = 'one', last = 'last')
...
enddef
MyFunc(v:none, 'LAST') # 第 1 引数は既定値の 'one' を使う
...
enddef
MyFunc(v:none, 'LAST') # 第 1 引数は既定値の 'one' を使う
vim9-ignored-argument E1181
引数 "_" (アンダースコア) は引数を無視するのに使えます。これは使わないが呼び出
す際に一致するように引数を与えないといけないようなコールバックにおいて一番便利
です。例えば、map() を使っていて、キーと値の 2 つの引数が与えられる時に引数の
キーを無視するには:
map(numberList, (_, v) => v * 2)
引数に "_" を複数回使ってもエラーにはなりません。また、型も指定する必要はありません。
関数と変数はデフォルトでスクリプトローカル
vim9-scopes
Vim9 script でスクリプト直下に :function や :def を使って関数を定義する
と、関数はプリフィックス "s:" をつけた際のように、スクリプトローカルで定義され
ます。グローバルスコープの関数や変数を定義するにはプリフィックス "g:" をつける
必要があります。 スクリプト内の関数のうち他のスクリプトからインポートされるも
のとオートロードスクリプト内の関数について、他のスクリプトで利用できるようにす
るためには "export" をつける必要があります。
def ThisFunction() # スクリプトローカル
def g:ThatFunction() # グローバル
export def Function() # import と import autoload 関数
E1058 E1075def g:ThatFunction() # グローバル
export def Function() # import と import autoload 関数
:def で定義される関数内で :function か :def でネストした関数を名前空間の
指定なしに作成したときは、ネストした関数はその関数が定義されたブロックにローカ
ルな関数になります。またその関数は function() に文字列を用いて渡すことはできず、
その関数自身の参照を渡さなければいけません:
def Outer()
def Inner()
echo 'inner'
enddef
var Fok = function(Inner) # OK
var Fbad = function('Inner') # 動作しない
def Inner()
echo 'inner'
enddef
var Fok = function(Inner) # OK
var Fbad = function('Inner') # 動作しない
詳細: これは "Inner" が実際には生成された名前 {訳注: <lambda>XXX のこと。} を
もつ関数への関数参照になるからです。
関数の中でスクリプトローカル関数を定義することはできません。代わりにローカル関
数を定義して、それをスクリプトローカルな Funcref (これはスクリプトレベルで定義
されていないといけません) に代入することができます。グローバル関数はプリフィッ
クス "g:" を使うことで定義できます。
関数をプリフィックス "s:" や "g:" をつけずに参照した場合、Vim は関数を次のよう
に探します:
- 同じ関数の中、ブロックスコープの中
- スクリプトスコープの中
インポートされた関数は、:import コマンドで決まる名前を前置して見つけられます。
スクリプトローカル関数の参照が "s:" をつけることなく使えるので、スクリプトロー
カル関数の名前はプリフィックス "s:" をつけて宣言した場合でも大文字から始まる必
要があります。旧来の Vim script では "s:funcref" とすることができました。なぜ
なら、"s:funcref" を "funcref" として参照することができなかったからです。しか
し Vim9 script ではそれが可能なので、組み込み関数との名前の干渉を避けるため
に "s:Funcref" のように名前が指定される必要があります。
vim9-s-namespace E1268
Vim9 script において、スクリプトレベルでのプリフィックス "s:" の使用はサポート
されていません。プリフィックスのない全ての関数と変数は全てスクリプトローカルに
なります。
:def で定義される関数内においては、"s:" の使用はスクリプト依存です: 旧来の
Vim script 内ではスクリプトローカルな関数と変数に対して "s:" を使いますが、
Vim9 script 内では使いません。これはこのドキュメントの以下の説明でも同様です。
旧来の関数内においては、スクリプトローカルなアイテムに対しての "s:" の指定は従
来通り必要です。これはスクリプトが Vim9 script であろうが旧来の Vim script で
あろうが関係ありません。
いずれの場合でも、関数は使用されるよりも前に定義されていなくてはなりません。使
用されるタイミングは、コマンド :defcompile によってコンパイルされるとき、ま
たは関数を呼び出す関数がコンパイルされているとき(戻り値の型を確認するため)で
す。
その結果として、名前空間を持たない関数や変数は通常、スクリプト内で定義されてい
るか、インポートされたものかのどちらかで見つけることができます。グローバルな関
数や変数はどこでも定義できます (どこで定義されているか、見つかるといいですね!
しばしば :verbose を使ってどこで最後に値がセットされたか調べることができま
す) 。
E1102
グローバル関数は引き続き、ほとんどいつでも定義し、削除することができます。Vim9
script でのスクリプトローカル関数は、スクリプトが読み込まれたときに一度定義さ
れたきり、そのスクリプト内で削除や置き換えはできません (スクリプトローカル関数の
削除や置き換えはスクリプトの再読み込みをすることでできます)。
関数のコンパイルや、関数の呼び出しが未定義の関数に遭遇したとき、自動コマンド
FuncUndefined は呼び出されません。必要であればオートロード関数を使用したり、
旧来の関数を呼び出すことで FuncUndefined イベントが発生します。
デフォルトでは Vim9 script の再読み込みにより関数と変数がクリアされる
vim9-reload E1149 E1150
旧来の Vim script を2回目に読み込んだときは、何も削除されることはなく、コマン
ドはすでにある変数や関数を置き換えて新しいものを作り、置き換えられなかったもの
はそのまま残しておきます。
Vim9 script を2回目に読み込んだときは、存在するすべてのスクリプトローカルの関
数や変数は削除され、クリーンな状態から開始します。これはプラグインを開発中に、
新しいバージョンを試す際には便利です。いずれかの名前を変えたとしても、古い名前
が残る心配はありません。
消さずに残すには、以下を使用します:
vim9script noclear
これを使用することで、再読み込みの際に任意の場所で finish コマンドにより脱出
することができます。例えば、バッファローカルオプションが関数に設定され、その関
数を2回以上定義する必要がないとき:
vim9script noclear
setlocal completefunc=SomeFunc
if exists('*SomeFunc')
finish
endif
def SomeFunc()
....
setlocal completefunc=SomeFunc
if exists('*SomeFunc')
finish
endif
def SomeFunc()
....
:var、:final や :const で宣言する変数
vim9-declaration :var E1079
E1017 E1020 E1054 E1087 E1108 E1124
ローカル変数は :var で定義する必要があります。ローカル定数は :final または
:const で定義する必要があります。このセクションでは、両者を "変数" と呼ぶこ
とにします。
変数はスクリプトローカルや、関数、コードブロックのスコープで定義できます:
vim9script
var script_var = 123
def SomeFunc()
var func_var = script_var
if cond
var block_var = func_var
...
var script_var = 123
def SomeFunc()
var func_var = script_var
if cond
var block_var = func_var
...
変数は、定義されたコードブロックか、ネストされた配下のブロックで参照することが
できます。コードブロックが終わったあとの処理から参照することはできません:
if cond
var inner = 5
else
var inner = 0
endif
echo inner # エラー!
var inner = 5
else
var inner = 0
endif
echo inner # エラー!
参照したい場合には、ブロックよりも前で宣言しなくてはなりません:
var inner: number
if cond
inner = 5
else
inner = 0
endif
echo inner
if cond
inner = 5
else
inner = 0
endif
echo inner
こちらの方が単純な値については簡潔で早くはありますが。:
var inner = 0
if cond
inner = 5
endif
echo inner
E1025 E1128if cond
inner = 5
endif
echo inner
意図的に続く処理から変数を隠したいとき、ブロックを使うことができます:
{
var temp = 'temp'
...
}
echo temp # エラー!
var temp = 'temp'
...
}
echo temp # エラー!
これは特にユーザーコマンドで便利です:
command -range Rename {
var save = @a
@a = 'some expression'
echo 'do something with ' .. @a
@a = save
}
var save = @a
@a = 'some expression'
echo 'do something with ' .. @a
@a = save
}
また、自動コマンドでも便利です:
au BufWritePre *.go {
var save = winsaveview()
silent! exe ':%! some formatting command'
winrestview(save)
}
var save = winsaveview()
silent! exe ':%! some formatting command'
winrestview(save)
}
多分 :def で定義される関数を使う方が良く動くでしょうが。
E1022 E1103 E1130 E1131 E1133
E1134
変数を型を指定し、初期値なしで宣言した場合、変数は false (bool 型のとき)、空
(string、list、dict などの型のとき)、あるいはゼロ (number、any などの型のとき)
で初期化されます。これは "any" 型を使う時に特に重要で、初期値は数字のゼロとな
ります。例えば、リストを宣言したとき、要素を追加することができます:
var myList: list<number>
myList->add(7)
myList->add(7)
変数を null、例えば null_list、で初期化することは変数を初期化しないこととは
異なります。これはエラーになります:
var myList = null_list
myList->add(7) # E1130: null リストには追加できません
myList->add(7) # E1130: null リストには追加できません
E1016 E1052 E1066
Vim9 script では :let は使用できません。すでに存在する変数に対してはコマンド
を使用せずに代入します。実際に宣言されることがないので、グローバル変数、ウィン
ドウ変数、タブ変数、バッファ変数、そして Vim の定義済変数についてもコマンドを
使用せずに代入します。またそれらの変数は :unlet によって削除することもできま
す。
E1065
変数を :va で宣言することはできず、それは必ず完全な名前の :var として書か
れなければなりません。これはコードを読みやすくするためのものです。
E1178
:lockvar はローカル変数に対しては動作しません。代わりに :const か :final
を使ってください。
exists() 関数と exists_compiled() 関数はローカル変数あるいは引数に対しては
動作しません。
E1006 E1041 E1167 E1168 E1213
変数と関数と関数の引数は、同じスクリプトファイル内で、すでに定義された、または
インポートされた変数と関数をシャドーイングすることはできません。一方で変数は
Ex コマンドをシャドーイングするので、必要であれば変数の名前を変更してくださ
い。
グローバル変数の前には、スクリプトレベルでも "g:" を付けなければなりません。
vim9script
var script_local = 'text'
g:global = 'value'
var Funcref = g:ThatFunction
var script_local = 'text'
g:global = 'value'
var Funcref = g:ThatFunction
グローバル関数は必ず先頭に "g:" を付けなければなりません:
vim9script
def g:GlobalFunc(): string
return 'text'
enddef
echo g:GlobalFunc()
プリフィックス "g:" はオートロード関数に対しては必要ありません。def g:GlobalFunc(): string
return 'text'
enddef
echo g:GlobalFunc()
vim9-function-defined-later
グローバル関数はプリフィックス "g:" なしに呼び出すことができますが、それらはコ
ンパイル時に存在していなければなりません。プリフィックスを "g:" をつけること
で、関数が後で定義されても良くなります。例:
def CallPluginFunc()
if exists('g:loaded_plugin')
g:PluginFunc()
endif
enddef
if exists('g:loaded_plugin')
g:PluginFunc()
endif
enddef
もしこのようにすると、たとえ "g:loaded_plugin" が存在しない場合でも、コンパイ
ル時に "PluginFunc" が存在しないというエラーが発生します:
def CallPluginFunc()
if exists('g:loaded_plugin')
PluginFunc() # エラー、関数が見つからない
endif
enddef
if exists('g:loaded_plugin')
PluginFunc() # エラー、関数が見つからない
endif
enddef
exists_compiled() を使うことでエラーを回避できますが、この場合は
"g:loaded_plugin" が後で定義されている場合でもその関数は呼ばれません:
def CallPluginFunc()
if exists_compiled('g:loaded_plugin')
PluginFunc() # 関数が呼ばれることはないかもしれない
endif
enddef
if exists_compiled('g:loaded_plugin')
PluginFunc() # 関数が呼ばれることはないかもしれない
endif
enddef
現在、&opt = value は "opt" オプションに値を設定する目的で使用されているた
め、:substitute コマンドをリピートする目的で ":&" を使用することはできません。
vim9-unpack-ignore
アンパック代入において、アンダースコアは、関数の引数を無視するのと似たように
リストの要素を無視するのに使えます:
[a, _, c] = theList
残りの要素全部を無視するには: [a, b; _] = longList
E1163 E1080アンパックの記法を用いて、一つ以上の変数を一度に宣言することが可能です。
それぞれの変数は型を持つか、値から型を推測することができます:
var [v1: number, v2] = GetValues()
これは値を持つリストがある時にのみ利用してください。1行に 1変数を宣言する方がより読みやすく、後の変更も行いやすいです。
定数
vim9-const vim9-final
定数の働きは言語によって異なります。別の値を代入できない変数を定数とする場合も
あります。JavaScript がその一例です。また、値そのものを不変にすることもあり、
例えばリスト定数の内容を変更することができないとしている場合もあります。
Vim9ではこのどちらも定義することができます。
E1021
変数とその値、両方を定数とするには、:const を使用します。何らかの複合的な値
が変更できないようにする際に使用します。例:
const myList = [1, 2]
myList = [3, 4] # エラー!
myList[0] = 9 # エラー!
myList->add(3) # エラー!
:final E1125myList = [3, 4] # エラー!
myList[0] = 9 # エラー!
myList->add(3) # エラー!
変数の変更のみを禁止するには、:final を使用します。この場合は、中の値自体を
変えることはできます。Java でよく知られるものです。例:
final myList = [1, 2]
myList = [3, 4] # エラー!
myList[0] = 9 # OK
myList->add(3) # OK
myList = [3, 4] # エラー!
myList[0] = 9 # OK
myList->add(3) # OK
一般に、定数はすべて大文字 (例: ALL_CAPS) で書かれますが、必ずしもそうしなくて
も構いません。
定数宣言は値そのものにのみ適用され、参照先の変数には影響しません。
final females = ["Mary"]
const NAMES = [["John", "Peter"], females]
NAMES[0] = ["Jack"] # エラー!
NAMES[0][0] = "Jack" # エラー!
NAMES[1] = ["Emma"] # エラー!
NAMES[1][0] = "Emma" # OK, females[0] == "Emma"
const NAMES = [["John", "Peter"], females]
NAMES[0] = ["Jack"] # エラー!
NAMES[0][0] = "Jack" # エラー!
NAMES[1] = ["Emma"] # エラー!
NAMES[1][0] = "Emma" # OK, females[0] == "Emma"
:call と :eval は不要に
E1190
関数は :call なしで呼ぶことができます:
writefile(lines, 'file')
:call は引き続き使用できますが、やめたほうが良いでしょう。メソッド呼び出しには eval は必要ありません。Exコマンドと同名の識別子ではない
限り、直接に識別子から呼び出すことができます。関数の場合は、"(" または "->"の
どちらかを改行せずに続けなければなりません。例:
myList->add(123)
g:myList->add(123)
[1, 2, 3]->Process()
{a: 1, b: 2}->Process()
"foobar"->Process()
("foobar")->Process()
'foobar'->Process()
('foobar')->Process()
g:myList->add(123)
[1, 2, 3]->Process()
{a: 1, b: 2}->Process()
"foobar"->Process()
("foobar")->Process()
'foobar'->Process()
('foobar')->Process()
一部の関数と Ex コマンドが紛らわしい場合、コロン (:) を前置することでそれが Ex
コマンドであることを明示することができます。例えば、:substitute コマンドと
substitute() が該当します。substitute( で始まる場合は関数呼び出しですが、
コロンを前置することでコマンドを代わりに使用することができます:
:substitute(pattern (replacement (
もし式が "!" で始まっているのであれば、それは条件の否定ではなくシェルコマンド
であると解釈されます。したがって、これはシェルコマンドとなります:
!shellCommand->something
"!" を否定として用いるには、式を丸カッコで囲んでください: (!expression)->Method()
Note 変数は使用する前に宣言する必要がありますが、関数は宣言するより前に使用で
きます。これは関数の循環参照を可能にするためです。関数を名前で探さなければなら
ないので、少し効率が悪いです。また、関数名のタイプミスは、関数が呼び出されると
きまで見つかりません。
function() は不要に
ユーザー定義の関数は、function() を使わずとも関数リファレンスとして使用する
ことができます。引数の型と戻り値の型がチェックされます。関数はすでに定義されて
いる必要があります。
var Funcref = MyFunction
function() を使って "func" 型のリファレンスを得た場合、その関数は任意の個数
の引数と任意の戻り値の型 (void を含めて) を持つものとされます。この場合、もし
関数名がクォートで囲まれてるなら、その関数は後から宣言できます。
ラムダ式には -> の代わりに => を使う
vim9-lambda
旧来のスクリプトでは "->" はメソッド呼び出しとラムダ式で混同するおそれがありま
す。また、"{" が見つかったとき、パーサーはラムダ式と辞書の開始を見分けねばなら
ず、そしてそれは引数の型指定により複雑になっています。
この問題を回避するため、Vim9 script ではラムダ式のために違う書式を使用し、それ
は JavaScript に似ています:
var Lambda = (arg) => expression
var Lambda = (arg): type => expression
E1157var Lambda = (arg): type => expression
"=>" まで含めて、ラムダ式の引数の定義の中では改行することはできません (Vim が
丸カッコで囲まれた式とラムダ式の引数の区別をつけられるようにするため)。 これは
OKです:
filter(list, (k, v) =>
v > 0)
以下のように記述することはできません:v > 0)
filter(list, (k, v)
=> v > 0)
以下のように記述することもできません:=> v > 0)
filter(list, (k,
v) => v > 0)
ただし、バックスラッシュを使ってパースする前に行をつなげることができます:v) => v > 0)
filter(list, (k,
\ v)
\ => v > 0)
vim9-lambda-arguments E1172\ v)
\ => v > 0)
旧来の Vim script においては、ラムダ関数はいくつもの余分な引数を与えて呼ぶこと
ができ、そしてその余分な引数を使わないことに対しての警告をする方法がありません
でした。Vim9 script では引数の数は必ず一致しなければなりません。もし任意の
引数、またはその他の引数を受け入れたい場合は、関数が vim9-variable-arguments
を受け入れられるようにする "..._" を使ってください。例:
var Callback = (..._) => 'anything'
echo Callback(1, 2, 3) # "anything" を表示する
echo Callback(1, 2, 3) # "anything" を表示する
inline-function E1171
加えて、ラムダ式には {} に複数のステートメントを含むことができます:
var Lambda = (arg) => {
g:was_called = 'yes'
return expression
}
これはタイマーに便利です。例えば:g:was_called = 'yes'
return expression
}
var count = 0
var timer = timer_start(500, (_) => {
count += 1
echom 'Handler called ' .. count
}, {repeat: 3})
var timer = timer_start(500, (_) => {
count += 1
echom 'Handler called ' .. count
}, {repeat: 3})
閉じの "}" は行の先頭にこなければなりません。後ろに他の文字が続いても良いで
す。例:
var d = mapnew(dict, (k, v): string => {
return 'value'
})
いかなるコマンドも "{" の後ろに続いてはいけません。コメントのみが利用可能でreturn 'value'
})
す。
command-block E1026
ブロックはユーザーコマンドを定義するのにも使えます。ブロックの内側では Vim9
script の文法が使われます。
もしブロックが辞書を含むのであれば、辞書の閉じカッコは行頭に書かれてはいけませ
ん。さもなくば閉じカッコがブロックの終了としてパースされてしまいます。これは動
作しません:
command NewCommand {
g:mydict = {
'key': 'value',
} # エラー: ブロックの終了として認識される
}
これを避けるには、'}' を最後の要素の後ろにおいてください:g:mydict = {
'key': 'value',
} # エラー: ブロックの終了として認識される
}
command NewCommand {
g:mydict = {
'key': 'value' }
}
g:mydict = {
'key': 'value' }
}
根拠: "}" がコマンドの後にきてはならないのは、ブロックの閉じカッコを見つけるの
にコマンドのパースが必要だろうからです。一貫性のために、いかなるコマンドも "{"
に続けることはできません。残念ながら、これは "() => { command }" が動作せず、
常に改行が必要であることを意味します。
vim9-curly
辞書リテラルの "{" がステートメントブロックと認識されてしまうのを回避するため
には、括弧で包みます:
var Lambda = (arg) => ({key: 42})
さらに、コマンドブロックの開始と混同してしまう場合:
({
key: value
})->method()
key: value
})->method()
自動行継続
vim9-line-continuation E1097
多くの場合、式が次の行に続くことは明らかです。継続行の先頭に行継続のためのバッ
クスラッシュ (line-continuation 参照) を置く必要はありません。例えば、複数行
にまたぐリストの場合:
var mylist = [
'one',
'two',
]
辞書の場合:'one',
'two',
]
var mydict = {
one: 1,
two: 2,
}
関数の呼び出しで:one: 1,
two: 2,
}
var result = Func(
arg1,
arg2
)
arg1,
arg2
)
角カッコ []、波カッコ {}、または丸カッコの中‘以外で’二項演算子御使用する場
合、その前後で改行することができます。例:
var text = lead
.. middle
.. end
var total = start +
end -
correction
var result = positive
? PosFunc(arg)
: NegFunc(arg)
.. middle
.. end
var total = start +
end -
correction
var result = positive
? PosFunc(arg)
: NegFunc(arg)
"->" を使用したメソッド呼び出し、そしてドット (.) を使用したメンバー参照の場
合、その前に改行を置くことができます:
var result = GetBuilder()
->BuilderSetWidth(333)
->BuilderSetHeight(777)
->BuilderBuild()
var result = MyDict
.member
->BuilderSetWidth(333)
->BuilderSetHeight(777)
->BuilderBuild()
var result = MyDict
.member
複数のコマンドのリストを引数に持つコマンドでは、行の先頭に置かれた文字 | は行
継続を表します:
autocmd BufNewFile *.match if condition
| echo 'match'
| endif
| echo 'match'
| endif
Note これはヒアドキュメントの最初の行は | で始めることができないことを意味しま
す:
var lines =<< trim END
| これは動作しない
END
ヒアドキュメントの先頭行を空行にする、あるいはヒアドキュメントを使わないように| これは動作しない
END
してください。あるいは一時的にフラグ "C" を 'cpoptions' に追加してください:
set cpo+=C
var lines =<< trim END
| これは動作する
END
set cpo-=C
もしヒアドキュメントが関数の中で使われているのであれば、'cpoptions' は :def のvar lines =<< trim END
| これは動作する
END
set cpo-=C
前で変更され、かつ :enddef の後ろで元に戻されなければなりません。
例えば長い Ex コマンドを分割しているときのような、依然として行連結にバックス
ラッシュが必要な場所では、'#\ ' でコメントを開始することができます:
syn region Text
\ start='foo'
#\ コメント
\ end='bar'
これは旧来の Vim script で '"\ ' が使われているのと似ています。またこれはバッ\ start='foo'
#\ コメント
\ end='bar'
クスラッシュ抜きで行連結が使用され、かつ行が | で開始しているときにも必要で
す:
au CursorHold * echom 'BEFORE bar'
#\ 何かのコメント
| echom 'AFTER bar'
#\ 何かのコメント
| echom 'AFTER bar'
E1050
行頭の演算子と識別できるようにするために、範囲指定の前にはコロンを置きま
す。"start" と "print" をつなげる例:
var result = start
+ print
これは以下の記述と同じです: var result = start + print
次のように書くと、"start" を代入して、1行表示します:
var result = start
:+ print
「範囲」の後ろには必ず Ex コマンドが続かなければなりません。コロンを付けてい
ない時は :call 抜きで関数を呼ぶことができますが、「範囲」の後ろではそれが必
要です。
MyFunc()
:% call MyFunc()
Note +cmd の引数にはコロンは不要です:
edit +6 fname
関数の定義部においても、引数の間で改行をおくことができます:
def MyFunc(
text: string,
separator = '-'
): string
text: string,
separator = '-'
): string
継続行を識別することは容易ではないため、コマンドの解析はより厳格化されていま
す。例えば、一行目のエラーにより、2行目は別のコマンドとみなされます:
popup_create(some invalid expression, {
exit_cb: Func})
ここで "exit_cb: Func})" は実際に有効なコマンドです: 変更をファイルexit_cb: Func})
"_cb: Func})" に保存して閉じます。Vim9 script の中ではこの種のミスを回避するた
めに、コマンド名と引数の間にはスペースを置かなくてはなりません。
E1144
ただし、コマンドの引数に置いたコマンドは認識されません。例えば、"windo echo
expr" に続く "expr" の式の中で改行しても認識されません。
Notes:
- "enddef" は継続行の先頭に置くことはできません。それは関数の終端を意味します。
- 代入式の左辺を複数の行に分割できません。特にリストのアンパック :let-unpack
を使用する場合は注意が必要です。これはOKです:
[var1, var2] =
Func()
以下のように記述することはできません:Func()
[var1,
var2] =
Func()
- :echo や :execute のようなコマンドの引数は複数の行に分割できません。これvar2] =
Func()
はOKです:
echo [1,
2] [3,
4]
以下のように記述することはできません:2] [3,
4]
echo [1, 2]
[3, 4]
- いくつかの場合、特に :windo のようなコマンドが他のコマンドの引数として使わ[3, 4]
れるような場合では、Vim にとってコマンドのパースが困難です。このような場合で
では、バックスラッシュを使った行継続を使わなければなりません。
ホワイトスペース
E1004 E1068 E1069 E1074 E1127 E1202
Vim9 script ではホワイトスペースの適切な使用を強制します。これはもはや許可され
ません:
var name=234 # エラー!
var name= 234 # エラー!
var name =234 # エラー!
"=" の前後にホワイトスペースがなければいけません:var name= 234 # エラー!
var name =234 # エラー!
var name = 234 # OK
コマンドの後ろでコメントを開始する # の前にもホワイトスペースが置かれなければなりません:
var name = 234# エラー!
var name = 234 # OK
var name = 234 # OK
ホワイトスペースは大抵の演算子の周りで必須です。
始まりと終わりと除いて、サブリスト (リストのスライス) の ":" の周りにホワイト
スペースが必要です。
otherlist = mylist[v : count] # v:count は異なる意味を持つ
otherlist = mylist[:] # リストのコピーを作る
otherlist = mylist[v :]
otherlist = mylist[: v]
ホワイトスペースは許可されません:
- 関数名と "(" の間:
Func (arg) # エラー!
Func
\ (arg) # エラー!
Func
(arg) # エラー!
Func(arg) # OK
Func(
arg) # OK
Func(
arg # OK
)
E1205Func
\ (arg) # エラー!
Func
(arg) # エラー!
Func(arg) # OK
Func(
arg) # OK
Func(
arg # OK
)
:set コマンドでのオプション名と続く "&"、"!"、"<"、"="、"+="、"-=" や "^="
の間にはホワイトスペースは許可されません。
波括弧変数の廃止
波括弧変数 curly-braces-names は使用できません。
コマンド修飾子は無視されない
E1176
コマンド修飾子を使わないコマンドにコマンド修飾子を使うとエラーになります。
E1082
同様に、続くコマンドなしにコマンド修飾子を使うことも今はエラーになります。
辞書リテラル
vim9-literal-dict E1014
従来、Vim は波括弧 {} で辞書リテラルの表記をサポートしてきました:
let dict = {'key': value}
後に、辞書にシンプルなキーを使用することが非常に一般的であることが明らかになっ
たため、キーをクォーテーションなしで指定できる表記が後方互換的に導入されまし
た:
let dict = #{key: value}
しかし、この #{} という表記は他の言語に比べて異色なものです。キーには式よりも
リテラルを使うほうが一般的で、JavaScript が使っている構文を考えると、辞書リテ
ラルに {} の表記を使うほうがずっと便利です:
var dict = {key: value}
これは英数字、アンダースコアとダッシュのキーで利用できます。異なる文字を使用す
る場合は、シングルクォートまたはダブルクォートで囲まれた文字列を使用します:
var dict = {'key with space': value}
var dict = {"key\twith\ttabs": value}
var dict = {'': value} # 空のキー
E1139var dict = {"key\twith\ttabs": value}
var dict = {'': value} # 空のキー
キーに式を使用する必要がある場合は、JavaScript と同様に角括弧を使用することが
できます:
var dict = {["key" .. nr]: value}
キーの型には、文字列、数値、真偽値、浮動小数点のいずれかを指定できます。その他
の型の場合はエラーが発生します。[] を使わない場合は文字列として扱われ、先頭の
0 を保持します。[] を使って式が与えられた場合は、式を評価し、文字列に変換しま
す。先頭の 0 は省かれます:
var dict = {000123: 'without', [000456]: 'with'}
echo dict
{'456': 'with', '000123': 'without'}
[] 外ではドットは受け入れられないので、浮動小数点数は [] の中でのみ動作しまecho dict
{'456': 'with', '000123': 'without'}
す:
var dict = {[00.013]: 'float'}
echo dict
{'0.013': 'float'}
echo dict
{'0.013': 'float'}
:xit、:t、:k、:append、:change、:insert の廃止
E1100
これらのコマンドは容易にローカル変数の名前と混同します。
:x や :xit の代わりに :exit を使用できます。
:t の代わりに :copy を使用できます。
:k の代わりに :mark を使用できます。
比較
オプション 'ignorecase' は文字列の比較には作用しません。なので、"=~" は "=~#"
と同じように動作します。
現状では、"is" と "isnot" (expr-is と expr-isnot) を文字列に対して使うと
常に偽を返します。旧来の Vim script では文字列の比較を行うだけでしたが、Vim9
script ではそれらは文字列オブジェクトが同一であることをチェックします。文字列
は使われるときに複製されるので、二つの文字列は同一と判定されることはありませ
ん。 (いつか、もし文字列が複製されるのではなく参照カウントで管理されるようにな
ると、この挙動は変更されるかもしれません)
エラー後の中断
旧来の Vim script では、エラーに遭遇したとき、Vim は後続の行の実行を続けます。
これは長いエラーの列を生みかねず、そしてそのエラーを止めるための CTRL-C の入力
が必要になります。Vim9 script ではコマンドの実行は最初のエラーに遭遇した段階で
終了します。例:
vim9script
var x = does-not-exist
echo 'not executed'
var x = does-not-exist
echo 'not executed'
For ループ
E1254
ループ変数は先に宣言されていてはいけません:
var i = 1
for i in [1, 2, 3] # エラー!
for i in [1, 2, 3] # エラー!
ただ、グローバル変数を使うことは可能です:
g:i = 1
for g:i in [1, 2, 3]
echo g:i
endfor
for g:i in [1, 2, 3]
echo g:i
endfor
旧来の Vim script では、リストのループ内で現在または前の項目を削除するための
for ループをつくるために幾つかのトリックがあります。Vim9 script では、単純にイ
ンデックスを使用することで、リストから削除された場合はスキップされます。
旧来のスクリプトの例:
let l = [1, 2, 3, 4]
for i in l
echo i
call remove(l, index(l, i))
endfor
出力は以下の通り:for i in l
echo i
call remove(l, index(l, i))
endfor
1
2
3
4
コンパイルされた Vim9 script での出力は以下の通り:
1
3
一般的に、反復しているリストを変更してはいけません。必要であれば最初にコピーを
作ります。
条件と式
vim9-boolean
条件と式は、他の言語とおよそ同じように扱われます。いくつかの値は旧来の Vim
script と扱いが異なります:
値 旧来の Vim script Vim9 script
0 falsy falsy
1 truthy truthy
99 truthy エラー!
"0" falsy エラー!
"99" truthy エラー!
"text" falsy エラー!
"??" 演算子か "!" を使用している場合はエラーとなることはなく、すべての値は
falsy か truthy として評価されます。これは JavaScript とほぼ同じですが、空の
リストと辞書は falsy として評価されます:
型 真と評価される値
bool true, v:true または 1
number 非0
float 非0
string 空文字列以外
blob 空ブロブ以外
list 空リスト以外 (JavaScript とは異なります)
dictionary 空辞書以外 (JavaScript とは異なります)
func 関数名があるとき
special true または v:true
job 非 NULL
channel 非 NULL
class 非 NULL
object 非 NULL (TODO: isTrue() が true を返すとき)
真偽値演算子 "||" と "&&" は、値が真偽値、0または1であることを期待します:
1 || false == true
0 || 1 == true
0 || false == false
1 && true == true
0 && 1 == false
8 || 0 エラー!
'yes' && 0 エラー!
[] || 99 エラー!
0 || 1 == true
0 || false == false
1 && true == true
0 && 1 == false
8 || 0 エラー!
'yes' && 0 エラー!
[] || 99 エラー!
"!" を使って論理否定をすると、どのような型に対しても結果は真偽値になります。
"!!" と二重論理否定をすることで、あらゆる型を真偽値に変換することができます:
!'yes' == false
!![] == false
!![1, 2, 3] == true
!![] == false
!![1, 2, 3] == true
文字列の結合に .. を使用すると、すべての単純型の被演算子は常に文字列に変換さ
れます:
'hello ' .. 123 == 'hello 123'
'hello ' .. v:true == 'hello true'
'hello ' .. v:true == 'hello true'
単純型とは、文字列 (string)、数値 (float)、特殊値 (special) と真偽値 (bool) で
す。他の型では string() を使う必要があります。
false true null E1034
Vim9 script では以下の定義済みの値が使えます:
true
false
null
null_blob
null_channel
null_dict
null_function
null_job
null_list
null_partial
null_string
true は v:true と同じ、false は v:false と同じ、そしてnull はfalse
null
null_blob
null_channel
null_dict
null_function
null_job
null_list
null_partial
null_string
v:null と同じです。
null の型が "special" であるのに対し、他の "null_" の値の型はそれぞれの名前
で示される型になります。かなり多くの場面で null 値は空の値と同値と扱われます
が、いつでもそうだというわけではありません。スクリプトローカル変数は :unlet
で削除することができないので、これらの null 値はスクリプトローカル変数をクリア
するのに便利です。例:
var theJob = job_start(...)
# ジョブに仕事をさせる
theJob = null_job
# ジョブに仕事をさせる
theJob = null_job
また、それらの値は引数の既定値とするのにも便利です:
def MyFunc(b: blob = null_blob)
if b == null_blob
# 引数 b が与えられなかった
if b == null_blob
# 引数 b が与えられなかった
null はどんな値と比較すること可能で、型エラーが発生することはありません。
しかし、null と数値、浮動小数点数、真偽値との比較は常に false になります。
これは null と 0 あるいは false を比較した時に true になる旧来の Vim
script とは異なる点です。
真偽値を文字列に変換するときは、旧来の Vim script のように v:false と
v:true が使われるのではなく、false と true が使われます。v:none につい
ては、他の言語に同等のものが存在しないので、v:none が none に変換されるよ
うになることはありません。
文字列に対してインデックス [idx] や [idx : idx] を使用すると、バイト単位ではな
く文字単位のインデックスとして扱われます。結合文字が含まれています。例:
echo 'bár'[1]
旧来の Vim script ではこれは文字 0xc3 (不正な文字) となりますが、Vim9 scriptでは文字列 'á' が得られます。
負のインデックスを指定すると、文字列の末尾から数えられます。"[-1]" は最後の文
字です。
最後の文字を除外するには slice() を使用します。
合成文字を分けてカウントするには strcharpart() を使ってください。
インデックスが範囲外の場合は、空文字列になります。
旧来のスクリプトでは "++var" と "--var" は寡黙に処理され、何の効果ももたらしま
せん。これは Vim9 script ではエラーになります。
ゼロから始まる数値は8進数とはみなされません、"0o" から始まる数値だけが8進数と
みなされます: "0o744"。scriptversion-4
気をつけるべきこと
vim9-gotchas
Vim9 は、一般的なプログラミング言語に近づくように設計されていますが、同時に旧
来の Vim コマンドをサポートしようとしています。そのため、いくつかの妥協をしな
ければなりませんでした。ここでは、意外と知られていないことをまとめてみました。
Exコマンドの範囲指定にはコロンを前置する必要があります。
-> 旧来の Vim: 前の行を右にシフト
->func() Vim9: 継続行におけるメソッド呼び出し
:-> Vim9: 前の行を右にシフト
->func() Vim9: 継続行におけるメソッド呼び出し
:-> Vim9: 前の行を右にシフト
%s/a/b 旧来の Vim: すべての行を置換
x = alongname
% another Vim9: 継続行の剰余演算
:%s/a/b Vim9: すべての行を置換
't 旧来の Vim: マーク t へのジャンプ
'text'->func() Vim9: メソッド呼び出し
:'t Vim9: マーク t へのジャンプ
x = alongname
% another Vim9: 継続行の剰余演算
:%s/a/b Vim9: すべての行を置換
't 旧来の Vim: マーク t へのジャンプ
'text'->func() Vim9: メソッド呼び出し
:'t Vim9: マーク t へのジャンプ
いくつかのExコマンドは Vim9 script の代入式と紛らわしくなります:
g:name = value # 代入
:g:pattern:cmd # :グローバルコマンド
:g:pattern:cmd # :グローバルコマンド
コマンド :global や :substitute と式や代入文が紛らわしくなるのを避けるた
め、これらのコマンドが一文字に省略されているとき、一部のセパレータは使うことが
できません: ':'、'-' と '.' が利用不可です。:
g:pattern:cmd # 無効なコマンド - エラー
s:pattern:repl # 無効なコマンド - エラー
g-pattern-cmd # 無効なコマンド - エラー
s-pattern-repl # 無効なコマンド - エラー
g.pattern.cmd # 無効なコマンド - エラー
s.pattern.repl # 無効なコマンド - エラー
s:pattern:repl # 無効なコマンド - エラー
g-pattern-cmd # 無効なコマンド - エラー
s-pattern-repl # 無効なコマンド - エラー
g.pattern.cmd # 無効なコマンド - エラー
s.pattern.repl # 無効なコマンド - エラー
同様に、コマンドとセパレータの間にスペースがあってはいけません:
g /pattern/cmd # 無効なコマンド - エラー
s /pattern/repl # 無効なコマンド - エラー
s /pattern/repl # 無効なコマンド - エラー
:def で定義した関数はすべてコンパイルされます。旧来の関数は途中で脱出するこ
とができ、それ以降の行はパースされません:
func Maybe()
if !has('feature')
return
endif
use-feature
endfunc
Vim9 関数はすべてコンパイルされます:if !has('feature')
return
endif
use-feature
endfunc
def Maybe()
if !has('feature')
return
endif
use-feature # コンパイルエラーが発生する可能性がある
enddef
応急的に、2つの関数に分けることができます:if !has('feature')
return
endif
use-feature # コンパイルエラーが発生する可能性がある
enddef
func Maybe()
if has('feature')
call MaybeInner()
endif
endfunc
if has('feature')
def MaybeInner()
use-feature
enddef
endif
また、偽として評価される定数式の条件をもった if の配下にサポート外のコードをif has('feature')
call MaybeInner()
endif
endfunc
if has('feature')
def MaybeInner()
use-feature
enddef
endif
置くことができます:
def Maybe()
if has('feature')
use-feature
endif
enddef
これには exists_compiled() 関数も同様に使えます。if has('feature')
use-feature
endif
enddef
vim9-user-command
関数のコンパイルによる他の副作用として、ユーザーコマンドの存在がコンパイルの時
点でチェックされます。ユーザーコマンドが後で定義されている場合、エラーとなりま
す。これはOKです:
command -nargs=1 MyCommand echom <q-args>
def Works()
MyCommand 123
enddef
これは "MyCommand" が定義されていないというエラーが発生します:def Works()
MyCommand 123
enddef
def Works()
command -nargs=1 MyCommand echom <q-args>
MyCommand 123
enddef
回避策は、:execute を使用して間接的にコマンドを呼び出すことです:command -nargs=1 MyCommand echom <q-args>
MyCommand 123
enddef
def Works()
command -nargs=1 MyCommand echom <q-args>
execute 'MyCommand 123'
enddef
command -nargs=1 MyCommand echom <q-args>
execute 'MyCommand 123'
enddef
Note 認識されていないコマンドを "|" でつなぐと、その後のコマンドは認識されませ
ん。次のような記述は endif がないというエラーになります:
def Maybe()
if has('feature') | use-feature | endif
enddef
if has('feature') | use-feature | endif
enddef
その他の変更点
パターンは、明示的に上書きされない限り 'magic' が設定されている状態と同様に作
用します。
オプション 'edcompatible' の値は使用されません。
オプション 'gdefault' の値は使用されません。
また、このwikiも参考になるでしょう。これは Vim9 script のアーリーアダプターに
よって書かれました: https://github.com/lacygoill/wiki/blob/master/vim/vim9.md
:++ :--
++ と -- コマンドが追加されました。1 を足し引きするのとそっくりです。
++var
var += 1
--var
var -= 1
式中で ++var や --var を使うことはまだサポートされていません。
==============================================================================
3. 新しいスタイルの関数 fast-functions
:def E1028
:def[!] {name}([arguments])[: {return-type}]
{name} という名前の新しい関数を定義します。関数の本体
は次の行から :enddef と一致するまで続きます。
E1073
E1011
{name} は必ず 100 バイト未満でなければなりません。
E1003 E1027 E1056 E1059
:return で返される値の型は必ず {return-type} と一致
しなければなりません。{return-type} が省略された、ある
いは "void" である場合は、その関数は何も返さないと想定
されます。
E1077 E1123
{arguments} は 0 あるいはそれ以上の引数の宣言列です。
引数の宣言には 3 つの書式があります:
{name}: {type}
{name} = {value}
{name}: {type} = {value}
最初の書式は必須の引数の書式で、呼び出す側は必ず実引数
を与える必要があります。
2 つ目と 3 つ目の書式は任意の引数の書式です。呼び出す
側が実引数を省略した場合は、{value} が使われます。
関数は呼び出された時、:disassemble が使われたとき、
あるいは :defcompile が使われたときに命令列にコンパ
イルされます。文法および型のエラーはこの時に提示されま
す。
:def を他の :def や :function の内側で大体 50 階
層の深さまでまでネストすることが可能です。
E1117
[!] は :function と同様に使われます。Note Vim9
script において、スクリプトローカル関数は後で削除され
たり再定義されたりしてはいけません。スクリプトローカル
の削除は、同じスクリプトの再読み込みによってのみ行えま
す。
:enddef E1057 E1152 E1173
:enddef :def で定義された関数の終了。:enddef はそれだけで
行にあるべきです。
また、このwikiも参考になるでしょう。これは Vim9 script のアーリーアダプターに
よって書かれました: https://github.com/lacygoill/wiki/blob/master/vim/vim9.md
もし関数が定義されたスクリプトが Vim9 script であるなら、スクリプトローカル変
数はプリフィックス "s:" なしでアクセスすることができます。それらは関数がコンパ
イルされる前に定義されている必要があります。もし関数が定義されたスクリプトが旧
来の Vim script であるなら、スクリプトローカル変数は、コンパイル時に存在しない
場合は、プリフィックス "s:" をつけてアクセスする必要があります。
E1269
Vim9 script では、スクリプトローカル変数はスクリプトレベルで宣言されなければ
なりません。それらは関数内で、旧来の関数内でも作成することはできません。
:defc :defcompile
:defc[ompile] 現在のスクリプトで定義されている関数のうち、まだコンパ
イルされていないものをコンパイルします。これはコンパイ
ル時に見つかったいかなるエラーも報告します。
:defc[ompile] {func}
:defc[ompile] debug {func}
:defc[ompile] profile {func}
必要であれば関数 {func} をコンパイルします。"debug" と
"profile" はコンパイルモードを指定するのに使います。こ
れはコンパイル時に見つかったいかなるエラーも報告しま
す。
:disa :disassemble
:disa[ssemble] {func} {func} 用に生成された命令列を表示します。これはデバ
ッグ及びテスト用です。E1061
Note {func} のコマンドライン補完において、スクリプト
ローカル関数を見つけるのに "s:" を前置することができ
ます。
:disa[ssemble] profile {func}
:disassemble と似ていますが、プロファイルをとるとき
に使われる命令列を表示します。
:disa[ssemble] debug {func}
:disassemble と似ていますが、デバッグをするときに使
われる命令列を表示します。
制約
ローカル変数は文字列式からは見えません。例:
def MapList(): list<string>
var list = ['aa', 'bb', 'cc', 'dd']
return range(1, 2)->map('list[v:val]')
enddef
var list = ['aa', 'bb', 'cc', 'dd']
return range(1, 2)->map('list[v:val]')
enddef
この map の引数は関数のスコープ抜きに評価される文字列式です。代わりにラムダ式
を使ってください:
def MapList(): list<string>
var list = ['aa', 'bb', 'cc', 'dd']
return range(1, 2)->map((_, v) => list[v])
enddef
var list = ['aa', 'bb', 'cc', 'dd']
return range(1, 2)->map((_, v) => list[v])
enddef
:edit のような、コンパイルされないコマンドには、バッククォートによる展開が
使え、またそれはローカルスコープを使うことができます。例:
def Replace()
var fname = 'blah.txt'
edit `=fname`
enddef
var fname = 'blah.txt'
edit `=fname`
enddef
ループ内で定義されたクロージャは同じコンテキストを共有します。例:
var flist: list<func>
for i in range(5)
var inloop = i
flist[i] = () => inloop
endfor
echo range(5)->map((i, _) => flist[i]())
# 結果: [4, 4, 4, 4, 4]
E1271for i in range(5)
var inloop = i
flist[i] = () => inloop
endfor
echo range(5)->map((i, _) => flist[i]())
# 結果: [4, 4, 4, 4, 4]
クロージャはそのコンテキスト内の変数を見つけられるように、必ずそれが定義された
コンテキストでコンパイルされます。これは関数がコンパイルされた後に :breakadd
で関数がデバッグ対象だとマークされたときを除いて、これは大体正しく行われます。
必ず外側の関数がコンパイルされるより前にブレークポイントを定義するように気をつ
けてください。
「ループ中」の変数は一度しか存在しません。リストに入れた全てのクロージャは同じ
インスタンス、すなわち最後に 4 の値をもつインスタンスを参照します。これは効率
的で、何回もループする場合でも同様です。もしそれぞれのクロージャでコンテキスト
を分けたいのであれば、コンテキストを定義するため関数を呼んでください:
def GetClosure(i: number): func
var infunc = i
return () => infunc
enddef
var infunc = i
return () => infunc
enddef
var flist: list<func>
for i in range(5)
flist[i] = GetClosure(i)
endfor
echo range(5)->map((i, _) => flist[i]())
# 結果: [0, 1, 2, 3, 4]
for i in range(5)
flist[i] = GetClosure(i)
endfor
echo range(5)->map((i, _) => flist[i]())
# 結果: [0, 1, 2, 3, 4]
いくらかの場面で、特に旧来の Vim script のコンテキストから Vim9 のクロージャを
呼ぶとき、その評価は失敗するでしょう。 E1248
Note スクリプトレベルにおいて、ループ変数はループの後では無効になります。これ
はループ変数が後で呼ばれるクロージャで使われている場合、例えばタイマーと組み合
わせる場合でも同様です。これは E1302 エラーを発生させます:
for n in range(4)
timer_start(500 * n, (_) => {
echowin n
})
endfor
timer_start(500 * n, (_) => {
echowin n
})
endfor
ブロックを使って変数を定義し、その変数をクロージャで使う必要があります:
for n in range(4)
{
var nr = n
timer_start(500 * n, (_) => {
echowin nr
})
}
endfor
{
var nr = n
timer_start(500 * n, (_) => {
echowin nr
})
}
endfor
タイマーにおいて :echowindow を使うのは便利です。メッセージはポップアップに
表示され、タイマーがトリガーされたときにユーザーが行っていることに干渉しませ
ん。
旧来の関数から Vim9 の関数への変換
convert_legacy_function_to_vim9
これらが旧来の関数から Vim9 の関数へ変換するために行われる必要のある変更の大部
分です。
- func や function を def に変更する。
- endfunc や endfunction を enddef に変更する。
- 関数の引数に型をつける。
- もし関数が何か返すのであれば、戻り値の型をつける。
- コメントが " に代わって # で始まるように変更する。
例えば旧来の Vim script の関数:
func MyFunc(text)
" 関数の本体
endfunc
がこうなる:" 関数の本体
endfunc
def MyFunc(text: string): number
# 関数の本体
enddef
# 関数の本体
enddef
- 引数に使われる "a:" を削除する。 例:
return len(a:text)
がこうなる: return len(text)
- 変数の宣言に使われる let を var に変更する。
- 変数への値の代入に使われる let を削除する。これは既に宣言されているローカ
ル変数と b: w: g: t: 変数が対象である。
例えば旧来の Vim script の関数:
let lnum = 1
let lnum += 3
let b:result = 42
がこうなる:let lnum += 3
let b:result = 42
var lnum = 1
lnum += 3
b:result = 42
lnum += 3
b:result = 42
- 式中の必要なところへホワイトスペースを挿入する。
- 結合に使われる "." を ".." に変更する。
例えば旧来の Vim script の関数:
echo line(1).line(2)
がこうなる: echo line(1) .. line(2)
- 常に行継続にバックスラッシュが必要なわけではない:
echo ['one',
\ 'two',
\ 'three'
\ ]
がこうなる:\ 'two',
\ 'three'
\ ]
echo ['one',
'two',
'three'
]
'two',
'three'
]
==============================================================================
4. 型 vim9-types
E1008 E1009 E1010 E1012
E1013 E1029 E1030
以下の組み込み型がサポートされています:
bool
number
float
string
blob
list<{type}>
dict<{type}>
job
channel
func
func: {type}
func({type}, ...)
func({type}, ...): {type}
void
まだサポートされていません:
tuple<a: {type}, b: {type}, ...>
これらの型は宣言において使えますが、いかなる単純値も実際に "void" 型を持つこと
はありません。void (例えば、戻り値のない関数) を使おうとするとエラーがでます。
E1031 E1186
配列型はありません。代わりに list<{type}> を使ってください。不変のリストに
対しては大量の細かいメモリを割り当てするのを避ける効率的な実装が使われます。
E1005 E1007
部分適用と関数は幾らかの方法で宣言することができます:
func 任意の種類の関数参照。引数や戻り値への型チェッ
クはない。
func: void 任意の数および型の引数で、戻り値はない。
func: {type} 任意の数および型の引数で、特定の型の戻り値があ
る。
func() 引数がなく、値を返さない関数。
func(): void 同上
func(): {type} 引数がなく、戻り値の型がある関数。
func({type}) 引数の型があり、値を返さない関数。
func({type}): {type} 引数の型と戻り値の型がある関数。
func(?{type}) 任意の引数の型があり、値を返さない関数。
func(...{type}) 可変長の引数の型があり、値を返さない関数。
func({type}, ?{type}, ...{type}): {type}
以下をもつ関数:
- 必須の引数の型
- 任意の引数の型
- 可変長引数の型
- 戻り値の型
もし戻り値の型が "void" なら、関数は値を返しません。
関数参照はそれが呼び出し側から見えない追加の引数および・あるいは辞書を保存して
いる場合、Partial にすることもできます。それらは同じように呼び出されるた
め、宣言も同じです。
:type を使ってカスタム型を定義できます:
:type MyList list<string>
ユーザー関数と似たように、後から追加される組み込み型との名前の衝突を避けるため、カスタム型は大文字から始まらなければなりません。
{not implemented yet}
そしてクラスとインターフェイスも型として使えます:
:class MyClass
:var mine: MyClass
:var mine: MyClass
:interface MyInterface
:var mine: MyInterface
:var mine: MyInterface
:class MyTemplate<Targ>
:var mine: MyTemplate<number>
:var mine: MyTemplate<string>
:var mine: MyTemplate<number>
:var mine: MyTemplate<string>
:class MyInterface<Targ>
:var mine: MyInterface<number>
:var mine: MyInterface<string>
{not implemented yet}:var mine: MyInterface<number>
:var mine: MyInterface<string>
変数の型と型キャスト
variable-types
Vim9 script か :def で定義される関数内で宣言された変数は明示的に示された型
か、初期値から推測された型のどちらかの型を持っています。
グローバル、バッファ、ウィンドウ、タブページ変数は特定の型を持たず、値はいつで
も書き換えられ、そしてそれは型の変更も含み得ます。なので、コンパイルされたコー
ドでは "any" 型が仮定されます。
これは "any" 型が望ましくなく、実際の型が常に同じであると想定されるときに問題
になり得ます。例えば、リストを宣言したとき:
var l: list<number> = [1, g:two]
コンパイル時には Vim は "g:two" の型を知らず、式の型は list<any> になります。左辺への代入の前にリストの型をチェックするための命令が生成され、少々非効率で
す。
type-casting E1104
これを避けるには、型キャストを使ってください:
var l: list<number> = [1, <number>g:two]
コンパイルされたコードは今度は "g:two" が数値かどうかをチェックするだけで、もしそうでないならエラーを与えます。これは型キャストと呼ばれます。
型キャストの文法は: "<" {type} ">" です。"<" の後ろ、あるいは ">" の前にホワイ
トスペースがあってはいけません (小なりと大なりの演算子との混乱を避けるためで
す)。
意味としては、必要であれば実行時の型チェックが行われます。実際に値が変更される
ことはありません。もし型を変える必要があるのであれば、例えば文字列に変換するの
であれば string() 関数を使ってください。あるいは文字列を数値に変換するのであ
れば str2nr() 関数を使ってください。
もし想定されない場所で型が与えられた場合、E1272 を得るかもしれません。
型インターフェイス
type-inference
一般的に: 型が明確な時はいつも型を省略することができます。例えば、変数を宣言
し、値を与えた時:
var name = 0 # 数値型と推測する
var name = 'hello' # 文字列型と推測する
var name = 'hello' # 文字列型と推測する
リストと辞書の型は、要素の値の型の共通のものからきます。もしその値が全て同じ型
をもつなら、その型がリストか辞書に使われます。もし型が混在しているなら、"any"
型が使われます。
[1, 2, 3] list<number>
['a', 'b', 'c'] list<string>
[1, 'x', 3] list<any>
['a', 'b', 'c'] list<string>
[1, 'x', 3] list<any>
関数参照の共通の型は、もしそれらが全て同じ数の引数をもつのでなければ、引数の
数が指定されていないことを示すため "(...)" を使います。例:
def Foo(x: bool)
enddef
def Bar(x: bool, y: bool)
enddef
var funclist = [Foo, Bar]
echo funclist->typename()
結果はこうなります:enddef
def Bar(x: bool, y: bool)
enddef
var funclist = [Foo, Bar]
echo funclist->typename()
list<func(...)>
Vim9 script のスクリプトローカル変数は型がチェックされ、それは変数が旧来の Vim
script の関数内で宣言されたときでも同様です。
型が宣言されたとき、これはリストや辞書に付け加えられます。後のある式がその型を
変えようとすると、エラーが与えられます:
var ll: list<number> = [1, 2, 3]
ll->extend(['x']) # エラー、'x' は数値ではない
ll->extend(['x']) # エラー、'x' は数値ではない
もし型が宣言されていないのであれば、そのときは変えることは許可されます:
[1, 2, 3]->extend(['x']) # 結果: [1, 2, 3, 'x']
変数の宣言においては、推測された型は重要です:
var ll = [1, 2, 3]
ll->extend(['x']) # エラー、'x' は数値ではない
これは宣言が数値のリストのように見えるためで、それゆえ以下と等しいからです:ll->extend(['x']) # エラー、'x' は数値ではない
var ll: list<number> = [1, 2, 3]
もし、もっと寛容なリストが欲しいのであれば、型を宣言する必要があります: var ll: list<any> = [1, 2, 3]
ll->extend(['x']) # OK
ll->extend(['x']) # OK
より厳格な型チェック
type-checking
旧来の Vim script では、数値が予期されるところでは、文字列は自動的に数値に変換
されます。これは "123" のような実際の数値に対しては便利でしたが、もし文字列が
数字で始まらないときは予期しない問題 (加えてエラーメッセージなしで) を引き起こ
します。これは頻繁に見つけにくいバグを引き起こします。例:
echo 123 == '123'
1思いがけないスペースがあるとき:
echo 123 == ' 123'
0E1206 E1210 E1212
Vim9 script ではこれは厳格にされています。使われている値が予期される型と一致す
る場合は、ほとんどの場所で前と同じように動作します。時々エラーになり、それゆえ
後方互換性が壊れています。例:
- 真偽値が期待されるところで、値が 0 か 1 でない数値を使う。 E1023
- 数値のオプションを設定するときに文字列を使う。 E1024 E1105
- 文字列が期待されるところで数値を用いる。
一つの影響として、型が宣言された場合は map() に渡されたリストか辞書の要素の
型は変更されてはいけません。これは Vim9 script ではエラーになります:
var mylist: list<number> = [1, 2, 3]
echo map(mylist, (i, v) => 'item ' .. i)
E1012: Type mismatch; expected number but got string in map()echo map(mylist, (i, v) => 'item ' .. i)
{訳注: 日本語メッセージの場合: "E1012: 型が不一致です。number が必要で
すが string でした (map() 内)"}
代わりに、新しくリストを作成する mapnew() を使ってください:
var mylist: list<number> = [1, 2, 3]
echo mapnew(mylist, (i, v) => 'item ' .. i)
['item 0', 'item 1', 'item 2']echo mapnew(mylist, (i, v) => 'item ' .. i)
もし要素の型が宣言されていない、あるいは "any" と決定されているなら、型はより
具体的なものに変更することができます。例えば、型の混ざったリストが文字列のリス
トに変更される場合:
var mylist = [1, 2.0, '3']
# typename(mylist) == "list<any>"
map(mylist, (i, v) => 'item ' .. i)
# typename(mylist) == "list<string>"、エラーなし
# typename(mylist) == "list<any>"
map(mylist, (i, v) => 'item ' .. i)
# typename(mylist) == "list<string>"、エラーなし
リスト定数を直接使うのと、変数宣言を通して使うのには少しの差異があります。
理由は型推論で、リスト定数を変数を初期化するのに使うとき、それは同時に宣言した
型をつけます:
var mylist = [1, 2, 3]
# typename(mylist) == "list<number>"
echo map(mylist, (i, v) => 'item ' .. i) # エラー!
# typename(mylist) == "list<number>"
echo map(mylist, (i, v) => 'item ' .. i) # エラー!
リスト定数を直接使うときは、型は宣言されず、変更することが許可されます:
echo map([1, 2, 3], (i, v) => 'item ' .. i) # OK
The reasoning behind this is that when a type is declared and the list is
passed around and changed, the declaration must always hold. So that you can
rely on the type to match the declared type. For a constant this is not
needed.
E1158
Same for extend(), use extendnew() instead, and for flatten(), use
flattennew() instead. Since flatten() is intended to always change the
type, it can not be used in Vim9 script.
E1211 E1217 E1218 E1219 E1220 E1221
E1222 E1223 E1224 E1225 E1226 E1227
E1228 E1238 E1250 E1251 E1252 E1253
E1256 E1297 E1298 E1301
Types are checked for most builtin functions to make it easier to spot
mistakes.
==============================================================================
5. Namespace, Import and Export
vim9script vim9-export vim9-import
A Vim9 script can be written to be imported. This means that some items are
intentionally exported, made available to other scripts. When the exporting
script is imported in another script, these exported items can then be used in
that script. All the other items remain script-local in the exporting script
and cannot be accessed by the importing script.
This mechanism exists for writing a script that can be sourced (imported) by
other scripts, while making sure these other scripts only have access to what
you want them to. This also avoids using the global namespace, which has a
risk of name collisions. For example when you have two plugins with similar
functionality.
You can cheat by using the global namespace explicitly. That should be done
only for things that really are global.
Namespace
vim9-namespace
To recognize a file that can be imported the vim9script statement must
appear as the first statement in the file (see vim9-mix for an exception).
It tells Vim to interpret the script in its own namespace, instead of the
global namespace. If a file starts with:
vim9script
var myvar = 'yes'
Then "myvar" will only exist in this file. While without vim9script it wouldvar myvar = 'yes'
be available as g:myvar from any other script and function.
E1101
The variables at the file level are very much like the script-local "s:"
variables in legacy Vim script, but the "s:" is omitted. And they cannot be
deleted.
In Vim9 script the global "g:" namespace can still be used as before. And the
"w:", "b:" and "t:" namespaces. These have in common that variables are not
declared, have no specific type and they can be deleted. E1304
A side effect of :vim9script is that the 'cpoptions' option is set to the
Vim default value, like with:
:set cpo&vim
One of the effects is that line-continuation is always enabled.The original value of 'cpoptions' is restored at the end of the script, while
flags added or removed in the script are also added to or removed from the
original value to get the same effect. The order of flags may change.
In the vimrc file sourced on startup this does not happen.
vim9-mix
There is one way to use both legacy and Vim9 syntax in one script file:
" comments may go here
if !has('vim9script')
" legacy script commands go here
finish
endif
vim9script
# Vim9 script commands go here
This allows for writing a script that takes advantage of the Vim9 scriptif !has('vim9script')
" legacy script commands go here
finish
endif
vim9script
# Vim9 script commands go here
syntax if possible, but will also work on a Vim version without it.
This can only work in two ways:
1. The "if" statement evaluates to false, the commands up to endif are
skipped and vim9script is then the first command actually executed.
2. The "if" statement evaluates to true, the commands up to endif are
executed and finish bails out before reaching vim9script.
Export
:export :exp
Exporting an item can be written as:
export const EXPORTED_CONST = 1234
export var someValue = ...
export final someValue = ...
export const someValue = ...
export def MyFunc() ...
export class MyClass ...
export interface MyClass ...
E1043 E1044export var someValue = ...
export final someValue = ...
export const someValue = ...
export def MyFunc() ...
export class MyClass ...
export interface MyClass ...
As this suggests, only constants, variables, :def functions and classes can
be exported. {not implemented yet: class, interface}
E1042
:export can only be used in Vim9 script, at the script level.
Import
:import :imp E1094 E1047 E1262
E1048 E1049 E1053 E1071 E1088 E1236
The exported items can be imported in another script. The import syntax has
two forms. The simple form:
import {filename}
Where {filename} is an expression that must evaluate to a string. In this
form the filename should end in ".vim" and the portion before ".vim" will
become the script local name of the namespace. For example:
import "myscript.vim"
This makes each exported item in "myscript.vim" available as "myscript.item".
:import-as E1257 E1261
In case the name is long or ambiguous, this form can be used to specify
another name:
import {longfilename} as {name}
In this form {name} becomes a specific script local name for the imported
namespace. Therefore {name} must consist of letters, digits and '_', like
internal-variables. The {longfilename} expression must evaluate to any
filename. For example:
import "thatscript.vim.v2" as that
E1060 E1258 E1259 E1260Then you can use "that.item", etc. You are free to choose the name "that".
Use something that will be recognized as referring to the imported script.
Avoid command names, command modifiers and builtin function names, because the
name will shadow them. Better not start the name starts with a capital
letter, since it can then also shadow global user commands and functions.
Also, you cannot use the name for something else in the script, such as a
function or variable name.
In case the dot in the name is undesired, a local reference can be made for a
function:
var LongFunc = that.LongFuncName
This also works for constants:
const MAXLEN = that.MAX_LEN_OF_NAME
This does not work for variables, since the value would be copied once and
when changing the variable the copy will change, not the original variable.
You will need to use the full name, with the dot.
:import can also be used in legacy Vim script. The imported items still
become script-local, even when the "s:" prefix is not given.
:import can not be used in a function. Imported items are intended to exist
at the script level and only imported once.
The script name after import can be:
- A relative path, starting "." or "..". This finds a file relative to the
location of the script file itself. This is useful to split up a large
plugin into several files.
- An absolute path, starting with "/" on Unix or "D:/" on MS-Windows. This
will rarely be used.
- A path not being relative or absolute. This will be found in the
"import" subdirectories of 'runtimepath' entries. The name will usually be
longer and unique, to avoid loading the wrong file.
Note that "after/import" is not used.
If the name does not end in ".vim" then the use of "as name" is required.
Once a vim9 script file has been imported, the result is cached and used the
next time the same script is imported. It will not be read again.
It is not allowed to import the same script twice, also when using two
different "as" names.
When using the imported name the dot and the item name must be in the same
line, there can be no line break:
echo that.
name # Error!
echo that
.name # Error!
import-mapname # Error!
echo that
.name # Error!
When you've imported a function from one script into a vim9 script you can
refer to the imported function in a mapping by prefixing it with <SID>:
noremap <silent> ,a :call <SID>name.Function()<CR>
When the mapping is defined "<SID>name." will be replaced with <SNR> and the
script ID of the imported script.
An even simpler solution is using <ScriptCmd>:
noremap ,a <ScriptCmd>name.Function()<CR>
Note that this does not work for variables, only for functions.
import-legacy legacy-import
:import can also be used in legacy Vim script. The imported namespace still
becomes script-local, even when the "s:" prefix is not given. For example:
import "myfile.vim"
call s:myfile.MyFunc()
call s:myfile.MyFunc()
And using the "as name" form:
import "otherfile.vim9script" as that
call s:that.OtherFunc()
call s:that.OtherFunc()
However, the namespace cannot be resolved on its own:
import "that.vim"
echo s:that
" ERROR: E1060: Expected dot after name: s:that
echo s:that
" ERROR: E1060: Expected dot after name: s:that
This also affects the use of <SID> in the legacy mapping context. Since
<SID> is only a valid prefix for a function and NOT for a namespace, you
cannot use it
to scope a function in a script local namespace. Instead of prefixing the
function with <SID> you should use<ScriptCmd>. For example:
noremap ,a <ScriptCmd>:call s:that.OtherFunc()<CR>
:import-cycle
The import commands are executed when encountered. If script A imports
script B, and B (directly or indirectly) imports A, this will be skipped over.
At this point items in A after "import B" will not have been processed and
defined yet. Therefore cyclic imports can exist and not result in an error
directly, but may result in an error for items in A after "import B" not being
defined. This does not apply to autoload imports, see the next section.
Importing an autoload script
vim9-autoload import-autoload
For optimal startup speed, loading scripts should be postponed until they are
actually needed. Using the autoload mechanism is recommended:
E1264
1. In the plugin define user commands, functions and/or mappings that refer to
items imported from an autoload script.
import autoload 'for/search.vim'
command -nargs=1 SearchForStuff search.Stuff(<f-args>)
command -nargs=1 SearchForStuff search.Stuff(<f-args>)
This goes in .../plugin/anyname.vim. "anyname.vim" can be freely chosen.
The "SearchForStuff" command is now available to the user.
The "autoload" argument to :import means that the script is not loaded
until one of the items is actually used. The script will be found under
the "autoload" directory in 'runtimepath' instead of the "import"
directory. Alternatively a relative or absolute name can be used, see
below.
2. In the autoload script put the bulk of the code.
vim9script
export def Stuff(arg: string)
...
export def Stuff(arg: string)
...
This goes in .../autoload/for/search.vim.
Putting the "search.vim" script under the "/autoload/for/" directory has
the effect that "for#search#" will be prefixed to every exported item. The
prefix is obtained from the file name, as you would to manually in a
legacy autoload script. Thus the exported function can be found with
"for#search#Stuff", but you would normally use import autoload and not
use the prefix (which has the side effect of loading the autoload script
when compiling a function that encounters this name).
You can split up the functionality and import other scripts from the
autoload script as you like. This way you can share code between plugins.
Searching for the autoload script in all entries in 'runtimepath' can be a bit
slow. If the plugin knows where the script is located, quite often a relative
path can be used. This avoids the search and should be quite a bit faster.
Another advantage is that the script name does not need to be unique. An
absolute path is also possible. Examples:
import autoload '../lib/implement.vim'
import autoload MyScriptsDir .. '/lib/implement.vim'
import autoload MyScriptsDir .. '/lib/implement.vim'
For defining a mapping that uses the imported autoload script the special key
<ScriptCmd> is useful. It allows for a command in a mapping to use the
script context of where the mapping was defined.
When compiling a :def function and a function in an autoload script is
encountered, the script is not loaded until the :def function is called.
This also means you get any errors only at runtime, since the argument and
return types are not known yet. If you would use the name with '#' characters
then the autoload script IS loaded.
Be careful to not refer to an item in an autoload script that does trigger
loading it unintentionally. For example, when setting an option that takes a
function name, make sure to use a string, not a function reference:
import autoload 'qftf.vim'
&quickfixtextfunc = 'qftf.Func' # autoload script NOT loaded
&quickfixtextfunc = qftf.Func # autoload script IS loaded
On the other hand, it can be useful to load the script early, at a time when&quickfixtextfunc = 'qftf.Func' # autoload script NOT loaded
&quickfixtextfunc = qftf.Func # autoload script IS loaded
any errors should be given.
For testing the test_override() function can be used to have the
import autoload load the script right away, so that the items and types can
be checked without waiting for them to be actually used:
test_override('autoload', 1)
Reset it later with: test_override('autoload', 0)
Or: test_override('ALL', 0)
==============================================================================
6. Future work: classes vim9-classes
Above "class" was mentioned a few times, but it has not been implemented yet.
Most of Vim9 script can be created without this functionality, and since
implementing classes is going to be a lot of work, it is left for the future.
For now we'll just make sure classes can be added later.
Thoughts:
- class / endclass, the whole class must be in one file
- Class names are always CamelCase (to avoid a name clash with builtin types)
- A single constructor called "constructor"
- Single inheritance with class ThisClass extends BaseClass
- abstract class (class with incomplete implementation)
- interface / endinterface (abstract class without any implementation)
- class SomeClass implements SomeInterface
- Generics for class: class <Tkey, Tentry>
- Generics for function: def <Tkey> GetLast(key: Tkey)
Again, much of this is from TypeScript with a slightly different syntax.
Some things that look like good additions:
- Use a class as an interface (like Dart)
- Extend a class with methods, using an import (like Dart)
- Mixins
- For testing: Mock mechanism
An important class that will be provided is "Promise". Since Vim is single
threaded, connecting asynchronous operations is a natural way of allowing
plugins to do their work without blocking the user. It's a uniform way to
invoke callbacks and handle timeouts and errors.
Some commands have already been reserved:
:class
:endclass
:abstract
:enum
:endenum
:interface
:endinterface
:static
:type
Some examples:
abstract class Person
static const prefix = 'xxx'
var name: string
def constructor(name: string)
this.name = name
enddef
static const prefix = 'xxx'
var name: string
def constructor(name: string)
this.name = name
enddef
def display(): void
echo name
enddef
echo name
enddef
abstract def find(string): Person
endclass
endclass
==============================================================================
9. Rationale vim9-rationale
The :def command
Plugin writers have asked for much faster Vim script. Investigations have
shown that keeping the existing semantics of function calls make this close to
impossible, because of the overhead involved with calling a function, setting
up the local function scope and executing lines. There are many details that
need to be handled, such as error messages and exceptions. The need to create
a dictionary for a: and l: scopes, the a:000 list and several others add too
much overhead that cannot be avoided.
Therefore the :def method to define a new-style function had to be added,
which allows for a function with different semantics. Most things still work
as before, but some parts do not. A new way to define a function was
considered the best way to separate the legacy style code from Vim9 style code.
Using "def" to define a function comes from Python. Other languages use
"function" which clashes with legacy Vim script.
Type checking
When compiling lines of Vim commands into instructions as much as possible
should be done at compile time. Postponing it to runtime makes the execution
slower and means mistakes are found only later. For example, when
encountering the "+" character and compiling this into a generic add
instruction, at runtime the instruction would have to inspect the type of the
arguments and decide what kind of addition to do. And when the type is
dictionary throw an error. If the types are known to be numbers then an "add
number" instruction can be used, which is faster. The error can be given at
compile time, no error handling is needed at runtime, since adding two numbers
cannot fail.
The syntax for types, using <type> for compound types, is similar to Java. It
is easy to understand and widely used. The type names are what were used in
Vim before, with some additions such as "void" and "bool".
Removing clutter and weirdness
Once decided that :def functions have different syntax than legacy functions,
we are free to add improvements to make the code more familiar for users who
know popular programming languages. In other words: remove weird things that
only Vim does.
We can also remove clutter, mainly things that were done to make Vim script
backwards compatible with the good old Vi commands.
Examples:
- Drop :call for calling a function and :eval for evaluating an
expression.
- Drop using a leading backslash for line continuation, automatically figure
out where an expression ends.
However, this does require that some things need to change:
- Comments start with # instead of ", to avoid confusing them with strings.
This is good anyway, it is also used by several popular languages.
- Ex command ranges need to be prefixed with a colon, to avoid confusion with
expressions (single quote can be a string or a mark, "/" can be divide or a
search command, etc.).
Goal is to limit the differences. A good criteria is that when the old syntax
is accidentally used you are very likely to get an error message.
Syntax and semantics from popular languages
Script writers have complained that the Vim script syntax is unexpectedly
different from what they are used to. To reduce this complaint popular
languages are used as an example. At the same time, we do not want to abandon
the well-known parts of legacy Vim script.
For many things TypeScript is followed. It's a recent language that is
gaining popularity and has similarities with Vim script. It also has a
mix of static typing (a variable always has a known value type) and dynamic
typing (a variable can have different types, this changes at runtime). Since
legacy Vim script is dynamically typed and a lot of existing functionality
(esp. builtin functions) depends on that, while static typing allows for much
faster execution, we need to have this mix in Vim9 script.
There is no intention to completely match TypeScript syntax and semantics. We
just want to take those parts that we can use for Vim and we expect Vim users
will be happy with. TypeScript is a complex language with its own history,
advantages and disadvantages. To get an idea of the disadvantages read the
book: "JavaScript: The Good Parts". Or find the article "TypeScript: the good
parts" and read the "Things to avoid" section.
People familiar with other languages (Java, Python, etc.) will also find
things in TypeScript that they do not like or do not understand. We'll try to
avoid those things.
Specific items from TypeScript we avoid:
- Overloading "+", using it both for addition and string concatenation. This
goes against legacy Vim script and often leads to mistakes. For that reason
we will keep using ".." for string concatenation. Lua also uses ".." this
way. And it allows for conversion to string for more values.
- TypeScript can use an expression like "99 || 'yes'" in a condition, but
cannot assign the value to a boolean. That is inconsistent and can be
annoying. Vim recognizes an expression with && or || and allows using the
result as a bool. The falsy-operator was added for the mechanism to use a
default value.
- TypeScript considers an empty string as Falsy, but an empty list or dict as
Truthy. That is inconsistent. In Vim an empty list and dict are also
Falsy.
- TypeScript has various "Readonly" types, which have limited usefulness,
since a type cast can remove the immutable nature. Vim locks the value,
which is more flexible, but is only checked at runtime.
- TypeScript has a complicated "import" statement that does not match how the
Vim import mechanism works. A much simpler mechanism is used instead, which
matches that the imported script is only sourced once.
Declarations
Legacy Vim script uses :let for every assignment, while in Vim9 declarations
are used. That is different, thus it's good to use a different command:
:var. This is used in many languages. The semantics might be slightly
different, but it's easily recognized as a declaration.
Using :const for constants is common, but the semantics varies. Some
languages only make the variable immutable, others also make the value
immutable. Since "final" is well known from Java for only making the variable
immutable we decided to use that. And then :const can be used for making
both immutable. This was also used in legacy Vim script and the meaning is
almost the same.
What we end up with is very similar to Dart:
:var name # mutable variable and value
:final name # immutable variable, mutable value
:const name # immutable variable and value
:final name # immutable variable, mutable value
:const name # immutable variable and value
Since legacy and Vim9 script will be mixed and global variables will be
shared, optional type checking is desirable. Also, type inference will avoid
the need for specifying the type in many cases. The TypeScript syntax fits
best for adding types to declarations:
var name: string # string type is specified
...
name = 'John'
const greeting = 'hello' # string type is inferred
...
name = 'John'
const greeting = 'hello' # string type is inferred
This is how we put types in a declaration:
var mylist: list<string>
final mylist: list<string> = ['foo']
def Func(arg1: number, arg2: string): bool
final mylist: list<string> = ['foo']
def Func(arg1: number, arg2: string): bool
Two alternatives were considered:
1. Put the type before the name, like Dart:
var list<string> mylist
final list<string> mylist = ['foo']
def Func(number arg1, string arg2) bool
2. Put the type after the variable name, but do not use a colon, like Go:final list<string> mylist = ['foo']
def Func(number arg1, string arg2) bool
var mylist list<string>
final mylist list<string> = ['foo']
def Func(arg1 number, arg2 string) bool
final mylist list<string> = ['foo']
def Func(arg1 number, arg2 string) bool
The first is more familiar for anyone used to C or Java. The second one
doesn't really have an advantage over the first, so let's discard the second.
Since we use type inference the type can be left out when it can be inferred
from the value. This means that after var we don't know if a type or a name
follows. That makes parsing harder, not only for Vim but also for humans.
Also, it will not be allowed to use a variable name that could be a type name,
using var string string is too confusing.
The chosen syntax, using a colon to separate the name from the type, adds
punctuation, but it actually makes it easier to recognize the parts of a
declaration.
Expressions
Expression evaluation was already close to what other languages are doing.
Some details are unexpected and can be improved. For example a boolean
condition would accept a string, convert it to a number and check if the
number is non-zero. This is unexpected and often leads to mistakes, since
text not starting with a number would be converted to zero, which is
considered false. Thus using a string for a condition would often not give an
error and be considered false. That is confusing.
In Vim9 type checking is stricter to avoid mistakes. Where a condition is
used, e.g. with the :if command and the || operator, only boolean-like
values are accepted:
true: true, v:true, 1, 0 < 9
false: false, v:false, 0, 0 > 9
Note that the number zero is false and the number one is true. This is more
permissive than most other languages. It was done because many builtin
functions return these values, and changing that causes more problems than it
solves. After using this for a while it turned out to work well.
If you have any type of value and want to use it as a boolean, use the !!
operator:
true: !!'text' !![99] !!{'x': 1} !!99
false: !!'' !![] !!{}
From a language like JavaScript we have this handy construct:
GetName() || 'unknown'
However, this conflicts with only allowing a boolean for a condition.Therefore the "??" operator was added:
GetName() ?? 'unknown'
Here you can explicitly express your intention to use the value as-is and notresult in a boolean. This is called the falsy-operator.
Import and Export
A problem of legacy Vim script is that by default all functions and variables
are global. It is possible to make them script-local, but then they are not
available in other scripts. This defies the concept of a package that only
exports selected items and keeps the rest local.
In Vim9 script a mechanism very similar to the JavaScript import and export
mechanism is supported. It is a variant to the existing :source command
that works like one would expect:
- Instead of making everything global by default, everything is script-local,
some of these are exported.
- When importing a script the symbols that are imported are explicitly listed,
avoiding name conflicts and failures if functionality is added later.
- The mechanism allows for writing a big, long script with a very clear API:
the exported functions, variables and classes.
- By using relative paths loading can be much faster for an import inside of a
package, no need to search many directories.
- Once an import has been used, its items are cached and loading it again is
not needed.
- The Vim-specific use of "s:" to make things script-local can be dropped.
When sourcing a Vim9 script (from a Vim9 or legacy script), only the items
defined globally can be used, not the exported items. Alternatives
considered:
- All the exported items become available as script-local items. This makes
it uncontrollable what items get defined and likely soon leads to trouble.
- Use the exported items and make them global. Disadvantage is that it's then
not possible to avoid name clashes in the global namespace.
- Completely disallow sourcing a Vim9 script, require using :import. That
makes it difficult to use scripts for testing, or sourcing them from the
command line to try them out.
Note that you CAN also use :import in legacy Vim script, see above.
Compiling functions early
Functions are compiled when called or when :defcompile is used. Why not
compile them early, so that syntax and type errors are reported early?
The functions can't be compiled right away when encountered, because there may
be forward references to functions defined later. Consider defining functions
A, B and C, where A calls B, B calls C, and C calls A again. It's impossible
to reorder the functions to avoid forward references.
An alternative would be to first scan through the file to locate items and
figure out their type, so that forward references are found, and only then
execute the script and compile the functions. This means the script has to be
parsed twice, which is slower, and some conditions at the script level, such
as checking if a feature is supported, are hard to use. An attempt was made
to see if it works, but it turned out to be impossible to make work well.
It would be possible to compile all the functions at the end of the script.
The drawback is that if a function never gets called, the overhead of
compiling it counts anyway. Since startup speed is very important, in most
cases it's better to do it later and accept that syntax and type errors are
only reported then. In case these errors should be found early, e.g. when
testing, a :defcompile command at the end of the script will help out.
Why not use an existing embedded language?
Vim supports interfaces to Perl, Python, Lua, Tcl and a few others. But
these interfaces have never become widely used, for various reasons. When
Vim9 was designed a decision was made to make these interfaces lower priority
and concentrate on Vim script.
Still, plugin writers may find other languages more familiar, want to use
existing libraries or see a performance benefit. We encourage plugin authors
to write code in any language and run it as an external process, using jobs
and channels. We can try to make this easier somehow.
Using an external tool also has disadvantages. An alternative is to convert
the tool into Vim script. For that to be possible without too much
translation, and keeping the code fast at the same time, the constructs of the
tool need to be supported. Since most languages support classes the lack of
support for classes in Vim is then a problem.
Classes
Vim supports a kind-of object oriented programming by adding methods to a
dictionary. With some care this can be made to work, but it does not look
like real classes. On top of that, it's quite slow, because of the use of
dictionaries.
It would be good to support real classes, and this is planned for a later
version. The support is a "minimal common functionality" of class support in
most languages. It will work much like Java, which is the most popular
programming language.
vim:tw=78:ts=8:noet:ft=help:norl: