vim9.txt For Vim バージョン 9.1. Last change: 2024 May 31
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 におけるローカル変数} を用
いることはできない。
52.6 の 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 を使ってください:
- 式による指定のマッピングを定義する際は、その式はそのマッピングが定義されたス
クリプトのコンテキストで評価されます。
- 文字列にインデックスを付ける場合、インデックスはバイト数ではなく文字数でカウ
ントされます: vim9-string-index
- 予想外の違いがいくつかあるかもしれません: vim9-gotchas.
# から始まるコメント
旧来の 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
「辞書関数」の代わりに Vim9 クラス (Vim9-class) を使用できます。辞書を明示的
に渡すこともできます:
一方、旧来の辞書関数を呼ぶことはできます:
引数の型と戻り値の型を指定する必要があります。型には "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:" をつける
必要があります。スクリプト内の関数のうち他のスクリプトから import されるものと
オートロードスクリプト内の関数について、他のスクリプトで利用できるようにするた
めには "export" をつける必要があります。
:def で定義される関数内で :function か :def でネストした関数を名前空間の
指定なしに作成したときは、ネストした関数はその関数が定義されたブロックにローカ
ルな関数になります。またその関数は function() に文字列を用いて渡すことはでき
ず、その関数自身の参照を渡さなければいけません:
詳細: これは "Inner" が実際には生成された名前 {訳注: <lambda>XXX のこと。} を
もつ関数への関数参照になるからです。
関数の中でスクリプトローカル関数を定義することはできません。代わりにローカル関
数を定義して、それをスクリプトローカルな Funcref (これはスクリプトレベルで定義
されていないといけません) に代入することができます。グローバル関数はプリフィッ
クス "g:" を使うことで定義できます。
関数をプリフィックス "s:" や "g:" をつけずに参照した場合、Vim は関数を次のよう
に探します:
- 同じ関数の中、ブロックスコープの中
- スクリプトスコープの中
import された関数は、: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 によってコンパイルされるとき、ま
たは関数を呼び出す関数がコンパイルされているとき(戻り値の型を確認するため)で
す。
その結果として、名前空間を持たない関数や変数は通常、スクリプト内で定義されてい
るか、import されたものかのどちらかで見つけることができます。グローバルな関数
や変数はどこでも定義できます (どこで定義されているか、見つかるといいですね!し
ばしば :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 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
変数と関数と関数の引数は、同じスクリプトファイル内で、すでに定義された、または
import された変数と関数をシャドーイングすることはできません。一方で変数は
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 E1307
変数とその値、両方を定数とするには、: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 抜きで関数を呼ぶことができますが、「範囲」の後ろではそれが必
要です:
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 ではホワイトスペースの適切な使用を強制します。これはもはや許可され
ません:
なりません:
ホワイトスペースは大抵の演算子の周りで必須です。
始まりと終わりと除いて、サブリスト (リストのスライス) の ":" の周りにホワイト
スペースが必要です:
ホワイトスペースは許可されません:
- 関数名と "(" の間:
: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
一般的に、反復しているリストを変更してはいけません。必要であれば最初にコピーを
作ります。
リストのリストをループする場合、ネストされたリストを変更できます。ループ変数は
"final" であり、変更することはできませんが、その値は変更できます。
E1306
:for ループと :while ループを合わせたループの深さは、10 を超えることはできませ
ん。
条件と式
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 null_blob null_channel
null_class null_dict null_function null_job
null_list null_object null_partial null_string
E1034
Vim9 script では以下の定義済みの値が使えます:
v:null と同じです。
null の型が "special" であるのに対し、他の "null_" の値の型はそれぞれの名前
で示される型になります。かなり多くの場面で null 値は空の値と同値と扱われます
が、いつでもそうだというわけではありません。スクリプトローカル変数は :unlet
で削除することができないので、これらの null 値はスクリプトローカル変数をクリア
するのに便利です。例:
また、それらの値は引数の既定値とするのにも便利です:
null はどんな値と比較すること可能で、型エラーが発生することはありません。
しかし、null と数値、浮動小数点数、真偽値との比較は常に false になります。
これは null と 0 あるいは false を比較した時に true になる旧来の Vim
script とは異なる点です。
vim9-false-true
真偽値を文字列に変換するときは、旧来の Vim script のように v:false と
v:true が使われるのではなく、false と true が使われます。v:none につい
ては、他の言語に同等のものが存在しないので、v:none が none に変換されるよ
うになることはありません。
vim9-string-index
文字列に対してインデックス [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 を使うことはまだサポートされていません。
==============================================================================
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] 現在のスクリプトで定義されている関数とクラス
(class-compile) のうち、まだコンパイルされていないも
のをコンパイルします。これはコンパイル時に見つかったい
かなるエラーも報告します。
:defc[ompile] MyClass クラス内のすべてのメソッドをコンパイルします。
class-compile
:defc[ompile] {func}
:defc[ompile] debug {func}
:defc[ompile] profile {func}
必要であれば関数 {func} をコンパイルします。"debug" と
"profile" はコンパイルモードを指定するのに使います。こ
れはコンパイル時に見つかったいかなるエラーも報告しま
す。
{func} コールは、"ClassName.functionName" とすること
で、クラス内の関数やメソッドもコンパイルできます。
{func} コールは、"ClassName" とすることで、クラス内の
すべての関数とメソッドをコンパイルすることもできます。
: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 の関数:
- 常に行継続にバックスラッシュが必要なわけではない:
式のオプションで関数を呼び出す
expr-option-function
'foldexpr' などのいくつかのオプションの値は、値を取得するために評価される式で
す。評価には、かなりのオーバーヘッドが発生する可能性があります。オーバーヘッド
を最小限に抑え、オプション値を非常に単純に保つ 1 つの方法は、コンパイル済み関
数を定義し、引数なしで呼び出すようにオプションを設定することです。例:
==============================================================================
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}> を使ってください。不変のリストに
対しては大量の細かいメモリを割り当てするのを避ける効率的な実装が使われます。
vim9-func-declaration E1005 E1007
部分適用と関数は幾らかの方法で宣言することができます:
func 任意の種類の関数参照。引数や戻り値への型チェッ
クはない。
func: void 任意の数および型の引数で、戻り値はない。
func: {type} 任意の数および型の引数で、特定の型の戻り値があ
る。
func() 引数がなく、値を返さない関数。
func(): void 同上
func(): {type} 引数がなく、戻り値の型がある関数。
func({type}) 引数の型があり、値を返さない関数。
func({type}): {type} 引数の型と戻り値の型がある関数。
func(?{type}) 任意の引数の型があり、値を返さない関数。
func(...list<{type}>) 可変長引数のリストの型で、値を返さない関数。
func({type}, ?{type}, ...list<{type}>): {type}
以下をもつ関数:
- 必須の引数の型
- 任意の引数の型
- 可変長引数のリストの型
- 戻り値の型
もし戻り値の型が "void" なら、関数は値を返しません。
関数参照はそれが呼び出し側から見えない追加の引数および・あるいは辞書を保存して
いる場合、Partial にすることもできます。それらは同じように呼び出されるた
め、宣言も同じです。
:type を使ってカスタム型を定義できます:
め、カスタム型は大文字から始まらなければなりません。
そしてクラスとインターフェイスも型として使えます:
変数の型と型キャスト
variable-types
Vim9 script か :def で定義される関数内で宣言された変数は明示的に示された型
か、初期値から推測された型のどちらかの型を持っています。
グローバル、バッファ、ウィンドウ、タブページ変数は特定の型を持たず、値はいつで
も書き換えられ、そしてそれは型の変更も含み得ます。なので、コンパイルされたコー
ドでは "any" 型が仮定されます。
これは "any" 型が望ましくなく、実際の型が常に同じであると想定されるときに問題
になり得ます。例えば、リストを宣言したとき:
左辺への代入の前にリストの型をチェックするための命令が生成され、少々非効率で
す。
type-casting E1104
これを避けるには、型キャストを使ってください:
しそうでないならエラーを与えます。これは型キャストと呼ばれます。
型キャストの文法は: "<" {type} ">" です。"<" の後ろ、あるいは ">" の前にホワイ
トスペースがあってはいけません (小なりと大なりの演算子との混乱を避けるためで
す)。
意味としては、必要であれば実行時の型チェックが行われます。実際に値が変更される
ことはありません。もし型を変える必要があるのであれば、例えば文字列に変換するの
であれば string() 関数を使ってください。あるいは文字列を数値に変換するのであ
れば str2nr() 関数を使ってください。
もし想定されない場所で型が与えられた場合、E1272 を得るかもしれません。
型が不完全な場合、例えば、クラスが不明なオブジェクト (通常は NULL オブジェクト)
がある場合などは E1363 になります。
型インターフェイス
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" と決定されているなら、型はより
具体的なものに変更することができます。例えば、型の混ざったリストが文字列のリス
トに変更される場合:
リスト定数を直接使うのと、変数宣言を通して使うのには少しの差異があります。
理由は型推論で、リスト定数を変数を初期化するのに使うとき、それは同時に宣言した
型をつけます:
リスト定数を直接使うときは、型は宣言されず、変更することが許可されます:
この背景となる理由は、型が宣言され、リストが渡されて変更された場合、宣言は常に
保持されている必要があるためです。そのため、宣言された型と一致する型に依存して
います。定数の場合、これは必要ありません。
E1158
extend() も同様で、代わりに extendnew() を、flatten() は、代わりに
flattennew() を使用します。flatten() は常に型を変更することを目的としてい
るため、Vim9 script では使用できません。
引数を指定して funcref に代入すると (vim9-func-declaration を参照)、引数の厳
密な型チェックが行われます。可変数の引数の場合も、型と一致する必要があります:
宛先関数参照に引数が指定されていない場合、引数の型のチェックは行われません:
E1211 E1217 E1218 E1219 E1220 E1221
E1222 E1223 E1224 E1225 E1226 E1227
E1228 E1238 E1250 E1251 E1252 E1256
E1297 E1298 E1301
間違いを発見しやすくするために、ほとんどの組み込み関数で型がチェックされます。
変数のカテゴリー、デフォルトおよび null の扱い
variable-categories null-variables
変数には次のカテゴリがあります:
プリミティブ number, float, boolean
コンテナ string, blob, list, dict
特殊 function, job, channel, user-defined-object
初期化子を使用せずに変数を宣言する場合は、明示的に型を指定する必要があります。
各カテゴリには、異なるデフォルトの初期化セマンティクスがあります。カテゴリごと
の例を次に示します:
Vim にはおなじみの null 値はありません。null_string、null_list、null_job
など、さまざまな null_<type> があらかじめ定義されています。プリミティブは
null_<type> を持ちません。null_<type> の典型的な使用例は以下の通りです:
- 変数をクリアし、そのリソースを解放。
- 関数定義内のパラメータのデフォルトとしては、null-compare を参照。
job などの特殊な変数の場合、リソースをクリアするために null_<type> が使用さ
れます。コンテナ変数の場合、空コンテナを変数に割り当てることによってリソースを
クリアすることもできます。例:
null-anomalies で説明されているように null の複雑な問題を回避できる可能性が
あります。
コンテナ変数と特殊な変数の初期化セマンティクスは異なります。初期化されていない
コンテナはデフォルトで空コンテナになります:
す。null コンテナは空コンテナに似ていますが、異なります。null-anomalies を参
照してください。
特殊変数のデフォルトは null です。これらのジョブの初期化は同等であり、区別でき
ません:
リストまたは辞書が宣言されている時に、項目の型が指定されておらず推論できない場
合、型は "any" になります:
関数の宣言は特に独特です。vim9-func-declaration を参照。
null-compare
一般的な null 比較セマンティクスでは、null コンテナが空コンテナと等しくない場
合、比較で null_<type> を使用しないでください:
さまざまな種類の引数を受け入れるように、上記の関数シグネチャを変更します:
上記の例では、null リストと空リストを区別することが目的であり、null_list で
はなく null と比較することが正しい選択です。基本的な理由は、
"null_list == null" および "[] != null" であるためです。
"[] == null_list" であるため、null_list との比較は失敗します。次のセクション
で比較結果の詳細が記載されています。
null-details null-anomalies
このセクションでは、null および null_<type> の使用に関する問題について説明しま
す。以下に、null 比較の列挙結果を示します。場合によっては、vim9 の null セマン
ティクスに精通している場合、プログラマは比較やその他の状況で null_<type> を使
用することを選択することがあります。
ドキュメントの他の場所には次のように書かれています:
多くの場合、null 値は空の値と同じように処理されますが、常にそうとは限
りません
以下に例を示します:
null_<type> に等しい 2 つの値は、必ずしも互いに等しいとは限りません:
他のコンテナとは異なり、初期化されていない文字列は null と等しくなります。is
演算子を使用して、null_string かどうかを判断できます:
null_<type> に初期化された変数はすべて、null_<type> と等しく、また null と等し
くなります。例:
初期化されていない変数は通常、null と等しくなります。それはそのタイプによって
異なります:
var s: string s == null
var b: blob b != null ***
var l: list<any> l != null ***
var d: dict<any> d != null ***
var f: func f == null
var j: job j == null
var c: channel c == null
var o: Class o == null
空に初期化された変数は null_<type> と同等です。null ではありません:
var s2: string = "" == null_string != null
var b2: blob = 0z == null_blob != null
var l2: list<any> = [] == null_list != null
var d2: dict<any> = {} == null_dict != null
NOTE: ジョブなどの特殊な変数はデフォルトで null 値になり、対応する空の値はあり
ません。
==============================================================================
5. 名前空間、Import と Export
vim9script vim9-export vim9-import
Vim9 script は、import されるように書くことができます。これは、いくつかの項目
が意図的に export され、他のスクリプトで利用できるようになることを意味します。
export したスクリプトが他のスクリプトで import されると、これらの export され
た項目はそのスクリプトで使用することができます。その他の項目は、export したス
クリプトのスクリプトローカルのままであり、import スクリプトからアクセスするこ
とはできません。
この機構は、他のスクリプトがソース (import される) されうるスクリプトを書くた
めに存在し、他のスクリプトがあなたが望むものだけにアクセスできるようにします。
また、名前衝突の危険性があるグローバル名前空間を使用することも避けられます。例
えば、似たような機能を持つ2つのプラグインがある場合です。
グローバル名前空間を明示的に使用することで、ごまかすことができます。これは、本
当にグローバルなものだけに行うべきことです。
名前空間
vim9-namespace
import 可能なファイルを認識するためには、vim9script ステートメントがファイル
の最初のステートメントとして現れる必要があります (例外については vim9-mix を
参照)。これは Vim に、グローバルな名前空間ではなく、独自の名前空間でスクリプト
を解釈するように指示します。ファイルが次で始まる場合:
がなければ、他のスクリプトや関数から g:myvar として利用できます。
E1101
ファイルレベルの変数は、旧来のVim script のスクリプトローカル変数 "s:" と非常
によく似ていますが、"s:" は省略されています。そして、それらは削除することがで
きません。
Vim9 script では、グローバルな "g:" 名前空間は従来通り使用可能です。また、
"w:"、"b:"、"t: " 名前空間も使用できます。これらの共通点は、変数が宣言されてい
ないこと、特定の型を持っていないこと、削除できることです。 E1304
vim9script の副作用として、'cpoptions' オプションが Vim のデフォルト値に設定
されることが挙げられます:
値はスクリプトの最後に復元され、スクリプトで追加または削除されたフラグも元の値
に追加または削除されて同じ効果を得ることができます。フラグの順序は変更されるこ
とがあります。起動時に読み込まれる vimrc ファイルでは、このようなことは起こ
りません。
vim9-mix
1つのスクリプトファイルで、旧来と Vim9 の両方の構文を使用する方法があります:
ますが、Vim9 のないバージョンでも動作するようになります。
これは、2通りの方法でしか機能しません:
1. "if" 文は false と評価され、endif までのコマンドはスキップされ、
vim9script が実際に実行される最初のコマンドとなる。
2. "if" 文が true に評価されると、endif までのコマンドが実行され、finish は
vim9script に到達する前に脱出します。
Export
:export :exp
項目の export は次のように記述できます:
このことからわかるように、定数、変数、:def 関数、クラスのみが export 可能で
す。
E1042
:export はスクリプトレベルで、Vim9 script でのみ使用できます。
Import
:import :imp E1094 E1047 E1262
E1048 E1049 E1053 E1071 E1088 E1236
export された項目は、別のスクリプトで import することができます。import 構文に
は、2つの形式があります。単純な形式は:
ここで {filename} は文字列として評価される式でなければなりません。この形式では、
ファイル名は ".vim" で終わる必要があり、".vim" より前の部分が名前空間のスクリ
プトローカル名となります。例:
これにより、"myscript.vim" に export された各項目は、"myscript.item" 等として
利用可能になります。
:import-as E1257 E1261
名前が長い場合や曖昧な場合は、この形式を使用して別の名前を指定することができま
す:
この形式では、{name} が import された名前空間の特定のスクリプトローカル名とな
ります。したがって、{name} は internal-variables のように、文字、数字、'_'で
構成されなければなりません。{longfilename} 式は、任意のファイル名で評価する必
要があります。例:
"that.item" 等として使えます。"that" という名称は自由です。import したスクリプ
トを指していると認識されるようなものを使ってください。コマンド名、コマンド修飾
語、組み込み関数名などは避けてください。名前がそれらと被るからです。名前を大文
字で始めないほうがいいです。大文字で始めると、グローバルなユーザーコマンドや関
数と被る可能性があります。また、その名前を関数や変数名など、スクリプト内の他の
何かに使用することはできません。
名前のドットが望ましくない場合は、関数のローカル参照を作成できます:
これは定数に対しても機能します:
これは変数には使えません。なぜなら、値は一度コピーされ、変数を変更するときは元
の変数ではなく、コピーが変更されるからです。ドットを含む完全な名前を使用する必
要があります。
関数内で :import を使用することはできません。import された項目はスクリプトレ
ベルに存在し、一度だけ import されることを意図しています。
import の後のスクリプト名は次のようになります:
- "." または ".." で始まる相対パス。スクリプトファイル自体の位置から相対的に
ファイルを検索する。大きなプラグインを複数のファイルに分割して使用する場合に
便利である。
- 絶対パス。Unixでは "/"、MS-Windowsでは "D:/" で始まる。これはほとんど使用さ
れない。
- 相対パスでも絶対パスでもないパス。これは、'runtimepath' エントリの "import"
サブディレクトリで見つかる。間違ったファイルを読み込まないようにするため、通
常、名前は長く、一意であるべきである。
Note "after/import" は使われない。
名前が ".vim" で終わらない場合は、"as name" を使用する必要があります。
vim9 script ファイルを一度 import すると、その結果はキャッシュされ、次に同じス
クリプトを import するときに使用されます。再度読み込まれることはありません。
2 つの異なる "as" 名を使用する場合も同様に、同じスクリプトを 2 回 import する
ことはできません。
import 名を使用する場合、ドットと項目名は同じ行になければならず、改行すること
はできません:
あるスクリプトから vim9 script に関数を import した場合、import した関数の前に
<SID> を付けることで、マッピングで参照することができます:
マッピングが定義されると、"<SID>name." は <SNR> とimport されたスクリプトのス
クリプトIDに置き換えられます。
さらに簡単な方法として、<ScriptCmd> を使用することができます:
Note これは変数には効かず、関数にしか効かないということです。
import-legacy legacy-import
:import は旧来の Vim script でも使用することができます。"s:" というプリフィ
ックスがない場合でも、import された名前空間はスクリプトローカルとなります。
例:
そして "as name" 形式を使用する:
ただし、名前空間を単独で解決することはできません:
これは、旧来のマッピングコンテキストにおける <SID> の使用にも影響します。
<SID> は関数に対してのみ有効なプリフィックスであり、名前空間に対しては有効で
はありません、スクリプトローカルな名前空間内の関数をスコープするために使用する
ことはできません。関数の前に <SID> を付ける代わりに、<ScriptCmd> を使用す
る必要があります。例:
:import-cycle
import コマンドは、遭遇したときに実行されます。スクリプト A がスクリプト B
を import し、B が (直接または間接的に) A を import した場合、これはスキップさ
れます。この時点では、"import B" 以降の A 内の項目はまだ処理されておらず、定義
されていません。したがって、循環的な import が存在しても、直接的にはエラーにな
りませんが、"import B" 以降の A 内の項目が定義されていないためにエラーになる可
能性があります。これはオートロード import には適用されません。次項参照。
オートロードスクリプトを import する
vim9-autoload import-autoload
起動速度を最適化するために、スクリプトの読み込みは実際に必要になるまで延期する
必要があります。オートロード機構を使用することをお勧めします:
E1264
1. プラグインでは、オートロードスクリプトから import された項目を参照するユー
ザーコマンド、関数、マッピングを定義します。
これは、.../plugin/anyname.vim に入ります。"anyname.vim" は自由に選ぶことが
できます。"SearchForStuff " コマンドが利用できるようになりました。
import の引数 "autoload" は、項目の1つが実際に使用されるまでスクリプトが
ロードされないことを意味します。スクリプトは "import" ディレクトリではなく、
'runtimepath' の "autoload" ディレクトリの下に見つかります。また、相対名や
絶対名を使用することもできます。以下を参照。
2. オートロードスクリプトに、コードの大部分を置きます。
これは、.../autoload/for/search.vim に入ります。
"search.vim" スクリプトを "/autoload/for/" ディレクトリに置くと、export さ
れた項目に "for#search#" というプリフィックスが付くようになります。このプリ
フィックスは、旧来のオートロードスクリプトで手動で行うように、ファイル名か
ら取得されます。したがって、export された関数は "for#search#Stuff" で見つけ
ることができますが、通常は import autoload を使用し、プリフィックスを使用
しません (この名前に遭遇した関数をコンパイルするときにオートロードスクリプ
トをロードするという副作用が発生します)。
機能を分割して、オートロードスクリプトから他のスクリプトを好きなように
import することができます。こうすることで、プラグイン間でコードを共有するこ
とができます。
'runtimepath' のすべてのエントリからオートロードスクリプトを検索するのは、少し時
間がかかることがあります。プラグインがスクリプトの場所を知っている場合、多くの
場合、相対パスが使用されます。これにより、検索が回避され、かなり速くなるはずで
す。また、スクリプト名が一意である必要がないことも利点です。絶対パスも可能です。
例:
import したオートロードスクリプトを使用するマッピングを定義するには、特殊キー
<ScriptCmd> が便利です。これは、マッピングのコマンドで、マッピングが定義され
た場所のスクリプトコンテキストを使用することを可能にします。
:def 関数をコンパイルしているときに、オートロードスクリプトの関数に遭遇する
と、:def 関数が呼び出されるまでスクリプトはロードされません。これは、引数や
戻り値の型がまだ分かっていないため、実行時にのみエラーが発生することも意味しま
す。もし、'#' 文字を含む名前を使用するのであれば、そのオートロードスクリプトは
ロードされます。
オートロードスクリプトの中で、意図せずロードのきっかけとなる項目を参照しないよ
うに注意してください。例えば、関数名を取るオプションを設定する場合、関数参照で
はなく、必ず文字列を使用してください:
ることができるようになり便利です。
test_override() のテスト用関数を使用して、import autoload にスクリプトをす
ぐにロードさせることができます。これにより、項目と型が実際に使用されるのを待た
ずにチェックできるようになります:
==============================================================================
6. クラスとインターフェイス vim9-classes
旧来のスクリプトでは、関数であるメンバーを追加することで、辞書を一種のオブジェ
クトとして使用することができます。しかし、これは非常に非効率的で、すべてのオブ
ジェクトが正しいメンバーを持っていることを確認する作業を作成者が行う必要があり
ます。Dictionary-function を参照。
Vim9 script では、一般的なオブジェクト指向プログラミング言語のように、クラス、
オブジェクト、インターフェイスを持つことができます。これは多くの機能を含んでい
るため、別のヘルプファイルに記載されています: vim9class.txt
==============================================================================
9. 理論的根拠 vim9-rationale
:def コマンド
プラグインの作者から、Vim script をもっと速くしたいとの要望がありました。関数
呼び出しの既存のセマンティクスを維持することは、関数の呼び出し、ローカル関数ス
コープの設定、行の実行に伴うオーバーヘッドのため、不可能に近いことが調査により
判明しました。エラーメッセージや例外など、処理しなければならない細部はたくさん
あります。a: および l: スコープ用の辞書を作成する必要性、a:000 のリスト、その
他いくつかの回避できないオーバーヘッドを追加しすぎています。
したがって、新しいスタイルの関数を定義するための :def メソッドを追加する必要
があり、これにより異なるセマンティクスを持つ関数を定義することができるようにな
りました。ほとんどのことは以前と同じように動作しますが、そうでない部分もありま
す。関数を定義する新しい方法は、旧来のスタイルのコードとVim9スタイルのコードを
分離するための最良の方法であると考えられていました。
"def" を使用して関数を定義するのは、Python に由来します。他の言語は、従来の
Vim script と競合する "function" を使用します。
型チェック
Vim コマンドの行を命令にコンパイルする場合、可能な限りコンパイル時に行うべきで
す。これを実行時に先送りすると、実行速度が遅くなり間違いに気づくのが遅くなりま
す。例えば、"+" 文字に遭遇し、これを一般的な加算命令にコンパイルする場合、実行
時に引数の型を検査し、どのような加算を行うかを決定する必要があります。そして、
その型が辞書であった場合には、エラーを投げます。もし、型が数値であることが分か
っていれば、「数値の加算」命令を使うことができ、その方が高速です。このエラーは
コンパイル時に発生する可能性があり、2 つの数値の加算は失敗しないため、実行時の
エラー処理は必要ありません。
複合型に <type> を使った型の構文は、Javaと似ています。理解しやすく、広く使われ
ています。型名は、以前 Vim で使われていたものに、"void" や "bool" などが追加さ
れています。
乱雑と奇妙さを取り除く
:def 関数が旧来の関数と異なる構文であることが決まれば、一般的なプログラミン
グ言語を知っているユーザーにとってより馴染みやすいコードにするための改良を加え
ることは自由です。言い換えれば、Vim でしかしないような奇妙なことは削除します。
また、Vim script に古き良きViコマンドと後方互換性を持たせるために行われたもの
を中心に、雑多なものを取り除くことができます。
例:
- 関数を呼び出すための :call と式を評価するための :eval を削除します。
- 行の継続のための先頭のバックスラッシュを使用しない。式がどこで終わるかを自動
的に把握します。
しかし、そのためには、いくつかのことを変える必要があります:
- コメントは、文字列と混同しないように、" の代わりに # で始まるようにしました。
これはとにかく良いことで、いくつかの人気のある言語でも使用されています。
- Ex コマンドの範囲は、式との混同を避けるため、先頭にコロンを付ける必要があり
ます (シングルクォートは文字列またはマーク、"/" は割り算または検索コマンド、
等)。
目標は、違いを制限することです。良い基準は、古い構文を誤って使用した場合に、
エラーメッセージが表示される可能性が非常に高いことです。
人気のある言語から構文とセマンティクスを学ぶ
スクリプトの作成者は、Vim script の構文が慣れ親しんでいるものとは予想外に異な
ると不満を漏らしています。この不満を軽減するために、人気のある言語を例として使
用します。同時に、旧来の Vim script のよく知られた部分を放棄することは避けたい
です。
いろいろなことで TypeScript が踏襲されています。これは最近人気が出てきた言語で、
Vim script に似ています。また、静的型付け (変数は常に既知の値型を持つ) と動的
型付け (変数は異なる型を持つことができ、これは実行時に変更される) が混在してい
ます。旧来の Vim script は動的型付けされており、既存の多くの機能 (特に組み込み
関数) は動的型付けに依存していますが、静的型付けはより高速に実行できるため、
Vim9 script でもこの混ぜ合わせが必要です。
TypeScript の構文やセマンティクスに完全に合わせるつもりはありません。私たちは
ただ、Vim に使える部分、Vim ユーザーが喜ぶと予想される部分を取り出したいだけで
す。TypeScript は、独自の歴史、利点と欠点を持つ複雑な言語です。デメリットを知
るには、この本を読んでください: "JavaScript: The Good Parts"。または、
"TypeScript: the good parts" という記事を見つけて、"Things to avoid" セクショ
ンを読んでください。
他の言語 (Java、Pythonなど) に慣れている人も、TypeScript の中で気に入らないこ
とや理解できないことを見つけるでしょう。そういったものを避けるようにします。
TypeScript から避ける特定の項目:
- "+" をオーバーロードして、加算と文字列連結の両方に使用します。これは旧来の
Vim script に反しており、しばしば間違いにつながります。そのため、文字列の連
結には ".." を使い続けることにします。Lua もこの方法で ".." を使っています。
そして、より多くの値のために文字列に変換することができます。
- TypeScript では、"99 || 'yes'" のような式を条件として使うことができますが、
その値を真偽値に代入することができません。これは一貫性がなく、迷惑なことで
す。Vim は && や || を使った式を認識し、その結果を bool として使うことができ
ます。デフォルト値を使う仕組みとして、falsy-operator が追加されました。
- TypeScript では、空の文字列を Falsy と見なしますが、空のリストや辞書を
Truthy と見なしています。これは矛盾しています。Vimでは、空のリストや辞書も
Falsy となります。
- TypeScript にはさまざまな "読み出し専用" 型がありますが、型キャストによって
不変な性質を取り除くことができるため、有用性は限定的です。Vim は値をロックす
るので、より柔軟性がありますが、実行時にしかチェックされません。
- TypeScript には複雑な "import" 文があり、Vim の import の仕組みとマッチしま
せん。代わりに、import されたスクリプトが一度だけ読み込まれることにマッチす
る、よりシンプルなメカニズムが使用されています。
宣言
旧来の Vim script では、すべての代入に :let を使用しますが、Vim9 では宣言が
使用されます。これは異なるので、別のコマンドを使うのがよいでしょう。var で
す。これは多くの言語で使用されています。セマンティクスは若干異なるかもしれませ
んが、宣言として簡単に認識することができます。
定数に :const を使うのは一般的ですが、そのセマンティクスは様々です。変数だけ
を不変にする言語もあれば、値も不変にする言語もあります。Java では "final" が変
数だけを不変にするためによく知られているので、これを使うことにしました。そし
て、:const を使うことで、両方を不変にすることができます。これは旧来の Vim
script でも使われていたもので、意味はほぼ同じです。
最終的に得られるものは、Dart と非常によく似ています:
旧来と Vim9 script が混在し、グローバル変数が共有されるため、オプションで型
チェックを行うことが望ましいです。また、型推論により、多くの場合、型を指定する
必要がなくなります。宣言に型を追加するには、TypeScript の構文が最も適していま
す:
これは、型を宣言に入れる方法です:
2つの選択肢を検討しました:
1. Dart のように名前の前に型を置く:
C や Java に慣れている人には1つ目の方が馴染みやすいと思います。2つ目は1つ目に
対しての優位性があまりないので、2つ目は捨ててしまいましょう。
型推論を使うので、値から推論できる場合は型は省くことができます。つまり、var
の後に型が続くのか名前が続くのかが分からないのです。そのため、Vim だけでなく人
間にとっても構文解析が難しくなります。また、型名になりうる変数名を使うことは許
されないでしょう。var string string を使うのはあまりにも紛らわしいです。
コロンを使用して名前と型を区切る選ばれた構文は、句読点を追加しますが、実際に
は、宣言の部分を認識しやすくします。
式
式の評価は、すでに他の言語が行っているものに近いものでした。いくつかの細部は予
期せぬものであり、改善することができます。例えば、真偽値条件では、文字列を受け
取り、それを数値に変換して、その数値が0でないかどうかをチェックします。これは
予想外のことで、しばしば間違いにつながります。なぜなら、数字で始まらないテキス
トはゼロに変換され、これは偽とみなされるからです。このように、条件に文字列を使
うと、エラーが出ず、偽とみなされることが多いのです。これは紛らわしいです。
Vim9 では、間違いを避けるために型チェックがより厳しくなっています。例えば
:if コマンドや || 演算子など、条件が使われる場所では、真偽値のような値しか
受け付けません:
true: true, v:true, 1, 0 < 9
false: false, v:false, 0, 0 > 9
Note 数字の 0 は偽で、数字の 1 は真です。これは、他の多くの言語よりも寛容です。
これは、多くの組み込み関数がこれらの値を返すため、それを変更すると、解決するよ
りも多くの問題を引き起こすからです。これをしばらく使ってみると、うまくいくこと
が分かりました。
任意の型を持つ値について、それを真偽値として使いたい場合は、!! 演算子を使い
ます:
true: !!'text' !![99] !!{'x': 1} !!99
false: !!'' !![] !!{}
JavaScript のような言語には、このような便利な構文があります:
演算子が追加されました:
きます。これは falsy-operator と呼ばれます。
Import と Export
旧来の Vim script の問題点は、デフォルトですべての関数と変数がグローバルである
ことです。スクリプトローカルにすることは可能ですが、そうすると他のスクリプトで
利用できなくなります。これは、選択された項目のみをエクスポートし、残りをローカ
ルに保つパッケージの概念に反しています。
Vim9 script では、JavaScript の import および export 機構に非常によく似た機構
がサポートされています。これは既存の :source コマンドの亜種で、期待通りに動
作します:
- デフォルトですべてをグローバルにするのではなく、すべてをスクリプトローカルに
することで、その一部をエクスポートすることができる。
- スクリプトの import 時には、import されるシンボルが明示的にリストされるため、
名前の衝突や、後から機能を追加した場合の失敗を回避することができる。
- この機構により、export された関数、変数、クラスという非常に明確なAPIを持つ巨
大で長いスクリプトを書くことができる。
- 相対パスを使用することで、パッケージ内のインポートのロードが非常に速くなり、
多くのディレクトリを検索する必要がない。
- 一度インポートを使用すると、その項目はキャッシュされ、再ロードする必要はな
い。
- スクリプトローカルにするための Vim 特有の "s:" の使用をやめることができる。
(Vim9 または旧来の script から) Vim9 script を読み込む場合、グローバルに定義さ
れた項目のみが使用可能で、export された項目は使用できません。代替案を検討しま
した:
- export された項目は、すべてスクリプトローカル項目として利用できるようにする。
これにより、どの項目が定義されるかは制御できず、すぐにトラブルに発展する可能
性がある。
- export された項目を使用し、それらをグローバルにする。デメリットは、グローバ
ルな名前空間での名前の衝突を避けることができなくなることである。
- Vim9 script を読み込むことを完全に禁止し、:import を使うことを要求する。こ
れにより、スクリプトをテストに使用したり、コマンドラインからスクリプトを取得
して試したりすることが難しくなる。
Note 上記の通り、旧来の Vim script でも :import を使用することができます。
関数を早期にコンパイルする
関数は、呼び出されたとき、または :defcompile が使用されたときにコンパイルさ
れます。なぜ、早期にコンパイルして、構文エラーや型エラーが早期に報告されるよう
にしないのでしょうか?
なぜなら、後に定義される関数への前方参照が存在する可能性があるからです。例え
ば、A、B、C という関数を定義し、A が B を呼び出し、B が C を呼び出し、C がまた
A を呼び出すとします。前方参照を避けるために関数を並べ替えることは不可能です。
別の方法としては、まずファイルをスキャンして項目を見つけ、そのタイプを把握し、
前方参照が見つかるようにしてから、スクリプトを実行し、関数をコンパイルする方法
があります。この場合、スクリプトを2回解析する必要があり、遅いです。また、機能
がサポートされているかどうかのチェックなど、スクリプトレベルでの条件もあるた
め、使いにくいです。うまくいくかどうか試行錯誤しましたが、うまく動作させること
は不可能であることが判明しました。
スクリプトの最後にすべての関数をコンパイルすることは可能でしょう。しかし、ある
関数が一度も呼び出されない場合、その関数をコンパイルするためのオーバーヘッド
が、いずれにせよカウントされてしまうという欠点があります。起動速度は非常に重要
なので、ほとんどの場合、コンパイルは後回しにした方がよく、構文や型のエラーはそ
の時にしか報告されないことを受け入れることができます。例えばテスト時など、これ
らのエラーを早期に発見する必要がある場合は、スクリプトの最後の :defcompile
コマンドが効果的です。
なぜ既存の組み込み言語を使わないのか?
Vim は Perl、Python、Lua、Tcl、その他いくつかへのインターフェイスをサポートし
ています。しかし、これらのインターフェイスは様々な理由で広く使われるようになる
ことはありませんでした。Vim9 が設計されたとき、これらのインターフェイスの優先
順位を下げ、Vim script に集中させることが決定されました。
それでも、プラグイン作成者は他の馴染みのある言語を探したり、既存のライブラリを
使用したり、性能の恩恵を確認したりするかもしれません。我々は、プラグインの作者
が任意の言語でコードを書き、ジョブやチャネルを使って外部プロセスとして実行する
ことを推奨します。我々は、これをどうにかして簡単にできるように努力することがで
きます。
外部ツールの使用にはデメリットもあります。代替案としては、ツールを Vim script
に変換することです。この場合、過度の翻訳を行ずに、同時にコードを高速に保つに
は、ツールの構成要素がサポートされている必要があります。ほとんどの言語がクラス
をサポートしているので、Vim でクラスがサポートされていないのは問題です。
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 におけるローカル変数} を用
いることはできない。
52.6 の 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 が使われます。- 式による指定のマッピングを定義する際は、その式はそのマッピングが定義されたス
クリプトのコンテキストで評価されます。
- 文字列にインデックスを付ける場合、インデックスはバイト数ではなく文字数でカウ
ントされます: vim9-string-index
- 予想外の違いがいくつかあるかもしれません: vim9-gotchas.
# から始まるコメント
旧来の 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
「辞書関数」の代わりに Vim9 クラス (Vim9-class) を使用できます。辞書を明示的
に渡すこともできます:
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:" をつける
必要があります。スクリプト内の関数のうち他のスクリプトから import されるものと
オートロードスクリプト内の関数について、他のスクリプトで利用できるようにするた
めには "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 された関数は、: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 によってコンパイルされるとき、ま
たは関数を呼び出す関数がコンパイルされているとき(戻り値の型を確認するため)で
す。
その結果として、名前空間を持たない関数や変数は通常、スクリプト内で定義されてい
るか、import されたものかのどちらかで見つけることができます。グローバルな関数
や変数はどこでも定義できます (どこで定義されているか、見つかるといいですね!し
ばしば :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 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
変数と関数と関数の引数は、同じスクリプトファイル内で、すでに定義された、または
import された変数と関数をシャドーイングすることはできません。一方で変数は
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 E1307
変数とその値、両方を定数とするには、: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 の文法が使われます。
これはヒアドキュメントの使用例です:
com SomeCommand {
g:someVar =<< trim eval END
ccc
ddd
END
}
g:someVar =<< trim eval END
ccc
ddd
END
}
もしブロックが辞書を含むのであれば、辞書の閉じカッコは行頭に書かれてはいけませ
ん。さもなくば閉じカッコがブロックの終了としてパースされてしまいます。これは動
作しません:
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()
:% 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]
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
一般的に、反復しているリストを変更してはいけません。必要であれば最初にコピーを
作ります。
リストのリストをループする場合、ネストされたリストを変更できます。ループ変数は
"final" であり、変更することはできませんが、その値は変更できます。
E1306
:for ループと :while ループを合わせたループの深さは、10 を超えることはできませ
ん。
条件と式
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 null_blob null_channel
null_class null_dict null_function null_job
null_list null_object null_partial null_string
E1034
Vim9 script では以下の定義済みの値が使えます:
true
false
null
null_blob
null_channel
null_class
null_dict
null_function
null_job
null_list
null_object
null_partial
null_string
true は v:true と同じ、false は v:false と同じ、そしてnull はfalse
null
null_blob
null_channel
null_class
null_dict
null_function
null_job
null_list
null_object
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)
# Note: null_blob ではなく null と比較し、
# デフォルト値と空 blob を区別する。
if b == null
# 引数 b が与えられなかった
null に対するテストについての詳細は、null-compare を参照。# Note: null_blob ではなく null と比較し、
# デフォルト値と空 blob を区別する。
if b == null
# 引数 b が与えられなかった
null はどんな値と比較すること可能で、型エラーが発生することはありません。
しかし、null と数値、浮動小数点数、真偽値との比較は常に false になります。
これは null と 0 あるいは false を比較した時に true になる旧来の Vim
script とは異なる点です。
vim9-false-true
真偽値を文字列に変換するときは、旧来の Vim script のように v:false と
v:true が使われるのではなく、false と true が使われます。v:none につい
ては、他の言語に同等のものが存在しないので、v:none が none に変換されるよ
うになることはありません。
vim9-string-index
文字列に対してインデックス [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 += 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] 現在のスクリプトで定義されている関数とクラス
(class-compile) のうち、まだコンパイルされていないも
のをコンパイルします。これはコンパイル時に見つかったい
かなるエラーも報告します。
:defc[ompile] MyClass クラス内のすべてのメソッドをコンパイルします。
class-compile
:defc[ompile] {func}
:defc[ompile] debug {func}
:defc[ompile] profile {func}
必要であれば関数 {func} をコンパイルします。"debug" と
"profile" はコンパイルモードを指定するのに使います。こ
れはコンパイル時に見つかったいかなるエラーも報告しま
す。
{func} コールは、"ClassName.functionName" とすること
で、クラス内の関数やメソッドもコンパイルできます。
{func} コールは、"ClassName" とすることで、クラス内の
すべての関数とメソッドをコンパイルすることもできます。
: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'
]
式のオプションで関数を呼び出す
expr-option-function
'foldexpr' などのいくつかのオプションの値は、値を取得するために評価される式で
す。評価には、かなりのオーバーヘッドが発生する可能性があります。オーバーヘッド
を最小限に抑え、オプション値を非常に単純に保つ 1 つの方法は、コンパイル済み関
数を定義し、引数なしで呼び出すようにオプションを設定することです。例:
vim9script
def MyFoldFunc(): any
... v:lnum の行の折り畳みレベルを計算する
return level
enddef
set foldexpr=s:MyFoldFunc()
def MyFoldFunc(): any
... v:lnum の行の折り畳みレベルを計算する
return level
enddef
set foldexpr=s:MyFoldFunc()
==============================================================================
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}> を使ってください。不変のリストに
対しては大量の細かいメモリを割り当てするのを避ける効率的な実装が使われます。
vim9-func-declaration E1005 E1007
部分適用と関数は幾らかの方法で宣言することができます:
func 任意の種類の関数参照。引数や戻り値への型チェッ
クはない。
func: void 任意の数および型の引数で、戻り値はない。
func: {type} 任意の数および型の引数で、特定の型の戻り値があ
る。
func() 引数がなく、値を返さない関数。
func(): void 同上
func(): {type} 引数がなく、戻り値の型がある関数。
func({type}) 引数の型があり、値を返さない関数。
func({type}): {type} 引数の型と戻り値の型がある関数。
func(?{type}) 任意の引数の型があり、値を返さない関数。
func(...list<{type}>) 可変長引数のリストの型で、値を返さない関数。
func({type}, ?{type}, ...list<{type}>): {type}
以下をもつ関数:
- 必須の引数の型
- 任意の引数の型
- 可変長引数のリストの型
- 戻り値の型
もし戻り値の型が "void" なら、関数は値を返しません。
関数参照はそれが呼び出し側から見えない追加の引数および・あるいは辞書を保存して
いる場合、Partial にすることもできます。それらは同じように呼び出されるた
め、宣言も同じです。
:type を使ってカスタム型を定義できます:
:type MyList list<string>
ユーザー関数と似たように、後から追加される組み込み型との名前の衝突を避けるため、カスタム型は大文字から始まらなければなりません。
そしてクラスとインターフェイスも型として使えます:
: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 を得るかもしれません。
型が不完全な場合、例えば、クラスが不明なオブジェクト (通常は NULL オブジェクト)
がある場合などは E1363 になります。
型インターフェイス
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
この背景となる理由は、型が宣言され、リストが渡されて変更された場合、宣言は常に
保持されている必要があるためです。そのため、宣言された型と一致する型に依存して
います。定数の場合、これは必要ありません。
E1158
extend() も同様で、代わりに extendnew() を、flatten() は、代わりに
flattennew() を使用します。flatten() は常に型を変更することを目的としてい
るため、Vim9 script では使用できません。
引数を指定して funcref に代入すると (vim9-func-declaration を参照)、引数の厳
密な型チェックが行われます。可変数の引数の場合も、型と一致する必要があります:
var FuncRef: func(string, number, bool): number
FuncRef = (v1: string, v2: number, v3: bool) => 777 # OK
FuncRef = (v1: string, v2: number, v3: number) => 777 # エラー!
# 可変数の引数は同じ型でなければならない
var FuncVA: func(...list<string>): number
FuncVA = (...v: list<number>): number => v # エラー!
FuncVA = (...v: list<any>): number => v # OK, `any` ランタイム確認
FuncVA = (v1: string, v: string2): number => 333 # エラー!
FuncVA = (v: list<string>): number => 3 # エラー!
FuncRef = (v1: string, v2: number, v3: bool) => 777 # OK
FuncRef = (v1: string, v2: number, v3: number) => 777 # エラー!
# 可変数の引数は同じ型でなければならない
var FuncVA: func(...list<string>): number
FuncVA = (...v: list<number>): number => v # エラー!
FuncVA = (...v: list<any>): number => v # OK, `any` ランタイム確認
FuncVA = (v1: string, v: string2): number => 333 # エラー!
FuncVA = (v: list<string>): number => 3 # エラー!
宛先関数参照に引数が指定されていない場合、引数の型のチェックは行われません:
var FuncUnknownArgs: func: number
FuncUnknownArgs = (v): number => v # OK
FuncUnknownArgs = (v1: string, v2: string): number => 3 # OK
FuncUnknownArgs = (...v1: list<string>): number => 333 # OK
FuncUnknownArgs = (v): number => v # OK
FuncUnknownArgs = (v1: string, v2: string): number => 3 # OK
FuncUnknownArgs = (...v1: list<string>): number => 333 # OK
E1211 E1217 E1218 E1219 E1220 E1221
E1222 E1223 E1224 E1225 E1226 E1227
E1228 E1238 E1250 E1251 E1252 E1256
E1297 E1298 E1301
間違いを発見しやすくするために、ほとんどの組み込み関数で型がチェックされます。
変数のカテゴリー、デフォルトおよび null の扱い
variable-categories null-variables
変数には次のカテゴリがあります:
プリミティブ number, float, boolean
コンテナ string, blob, list, dict
特殊 function, job, channel, user-defined-object
初期化子を使用せずに変数を宣言する場合は、明示的に型を指定する必要があります。
各カテゴリには、異なるデフォルトの初期化セマンティクスがあります。カテゴリごと
の例を次に示します:
var num: number # プリミティブのデフォルトは 0 相当
var cont: list<string> # コンテナのデフォルトは空コンテナ
var spec: job # 特殊変数のデフォルトは null
var cont: list<string> # コンテナのデフォルトは空コンテナ
var spec: job # 特殊変数のデフォルトは null
Vim にはおなじみの null 値はありません。null_string、null_list、null_job
など、さまざまな null_<type> があらかじめ定義されています。プリミティブは
null_<type> を持ちません。null_<type> の典型的な使用例は以下の通りです:
- 変数をクリアし、そのリソースを解放。
- 関数定義内のパラメータのデフォルトとしては、null-compare を参照。
job などの特殊な変数の場合、リソースをクリアするために null_<type> が使用さ
れます。コンテナ変数の場合、空コンテナを変数に割り当てることによってリソースを
クリアすることもできます。例:
var j: job = job_start(...)
# ... ジョブはその仕事を行う
j = null_job # 変数をクリアしてジョブのリソースを解放する
# ... ジョブはその仕事を行う
j = null_job # 変数をクリアしてジョブのリソースを解放する
var l: list<any>
# ... リストにたくさんのものを追加
l = [] # 変数をクリアしてコンテナリソースを解放する
null_<type> ではなく空コンテナを使用してコンテナ変数をクリアすると、# ... リストにたくさんのものを追加
l = [] # 変数をクリアしてコンテナリソースを解放する
null-anomalies で説明されているように null の複雑な問題を回避できる可能性が
あります。
コンテナ変数と特殊な変数の初期化セマンティクスは異なります。初期化されていない
コンテナはデフォルトで空コンテナになります:
var l1: list<string> # 空コンテナ
var l2: list<string> = [] # 空コンテナ
var l3: list<string> = null_list # null コンテナ
"l1" と "l2" は同等で区別できない初期化です。ただし、"l3" は null コンテナでvar l2: list<string> = [] # 空コンテナ
var l3: list<string> = null_list # null コンテナ
す。null コンテナは空コンテナに似ていますが、異なります。null-anomalies を参
照してください。
特殊変数のデフォルトは null です。これらのジョブの初期化は同等であり、区別でき
ません:
var j1: job
var j2: job = null_job
var j3 = null_job
var j2: job = null_job
var j3 = null_job
リストまたは辞書が宣言されている時に、項目の型が指定されておらず推論できない場
合、型は "any" になります:
var d1 = {} # 型は "dict<any>"
var d2 = null_dict # 型は "dict<any>"
var d2 = null_dict # 型は "dict<any>"
関数の宣言は特に独特です。vim9-func-declaration を参照。
null-compare
一般的な null 比較セマンティクスでは、null コンテナが空コンテナと等しくない場
合、比較で null_<type> を使用しないでください:
vim9script
def F(arg: list<string> = null_list)
if arg == null
echo "null"
else
echo printf("not null, %sempty", empty(arg) ? '' : 'not ')
endif
enddef
F() # 出力: "null"
F(null_list) # 出力: "null"
F([]) # 出力: "not null, empty"
F(['']) # 出力: "not null, not empty"
上記の関数は文字列のリストを取得し、それについてレポートします。def F(arg: list<string> = null_list)
if arg == null
echo "null"
else
echo printf("not null, %sempty", empty(arg) ? '' : 'not ')
endif
enddef
F() # 出力: "null"
F(null_list) # 出力: "null"
F([]) # 出力: "not null, empty"
F(['']) # 出力: "not null, not empty"
さまざまな種類の引数を受け入れるように、上記の関数シグネチャを変更します:
def F(arg: list<any> = null_list) # あらゆるタイプのリスト
def F(arg: any = null) # あらゆるタイプ
def F(arg: any = null) # あらゆるタイプ
上記の例では、null リストと空リストを区別することが目的であり、null_list で
はなく null と比較することが正しい選択です。基本的な理由は、
"null_list == null" および "[] != null" であるためです。
"[] == null_list" であるため、null_list との比較は失敗します。次のセクション
で比較結果の詳細が記載されています。
null-details null-anomalies
このセクションでは、null および null_<type> の使用に関する問題について説明しま
す。以下に、null 比較の列挙結果を示します。場合によっては、vim9 の null セマン
ティクスに精通している場合、プログラマは比較やその他の状況で null_<type> を使
用することを選択することがあります。
ドキュメントの他の場所には次のように書かれています:
多くの場合、null 値は空の値と同じように処理されますが、常にそうとは限
りません
以下に例を示します:
vim9script
var s1: list<string>
var s2: list<string> = null_list
echo s1 # 出力: "[]"
echo s2 # 出力: "[]"
var s1: list<string>
var s2: list<string> = null_list
echo s1 # 出力: "[]"
echo s2 # 出力: "[]"
echo s1 + ['a'] # 出力: "['a']"
echo s2 + ['a'] # 出力: "['a']"
echo s2 + ['a'] # 出力: "['a']"
echo s1->add('a') # 出力: "['a']"
echo s2->add('a') # E1130: Can not add to null list
echo s2->add('a') # E1130: Can not add to null list
null_<type> に等しい 2 つの値は、必ずしも互いに等しいとは限りません:
vim9script
echo {} == null_dict # true
echo null_dict == null # true
echo {} == null # false
echo {} == null_dict # true
echo null_dict == null # true
echo {} == null # false
他のコンテナとは異なり、初期化されていない文字列は null と等しくなります。is
演算子を使用して、null_string かどうかを判断できます:
vim9script
var s1: string
var s2 = null_string
echo s1 == null # true - これは想定外
echo s2 == null # true
echo s2 is null_string # true
var s1: string
var s2 = null_string
echo s1 == null # true - これは想定外
echo s2 == null # true
echo s2 is null_string # true
var b1: blob
var b2 = null_blob
echo b1 == null # false
echo b2 == null # true
var b2 = null_blob
echo b1 == null # false
echo b2 == null # true
null_<type> に初期化された変数はすべて、null_<type> と等しく、また null と等し
くなります。例:
vim9script
var x = null_blob
echo x == null_blob # true
echo x == null # true
var x = null_blob
echo x == null_blob # true
echo x == null # true
初期化されていない変数は通常、null と等しくなります。それはそのタイプによって
異なります:
var s: string s == null
var b: blob b != null ***
var l: list<any> l != null ***
var d: dict<any> d != null ***
var f: func f == null
var j: job j == null
var c: channel c == null
var o: Class o == null
空に初期化された変数は null_<type> と同等です。null ではありません:
var s2: string = "" == null_string != null
var b2: blob = 0z == null_blob != null
var l2: list<any> = [] == null_list != null
var d2: dict<any> = {} == null_dict != null
NOTE: ジョブなどの特殊な変数はデフォルトで null 値になり、対応する空の値はあり
ません。
==============================================================================
5. 名前空間、Import と Export
vim9script vim9-export vim9-import
Vim9 script は、import されるように書くことができます。これは、いくつかの項目
が意図的に export され、他のスクリプトで利用できるようになることを意味します。
export したスクリプトが他のスクリプトで import されると、これらの export され
た項目はそのスクリプトで使用することができます。その他の項目は、export したス
クリプトのスクリプトローカルのままであり、import スクリプトからアクセスするこ
とはできません。
この機構は、他のスクリプトがソース (import される) されうるスクリプトを書くた
めに存在し、他のスクリプトがあなたが望むものだけにアクセスできるようにします。
また、名前衝突の危険性があるグローバル名前空間を使用することも避けられます。例
えば、似たような機能を持つ2つのプラグインがある場合です。
グローバル名前空間を明示的に使用することで、ごまかすことができます。これは、本
当にグローバルなものだけに行うべきことです。
名前空間
vim9-namespace
import 可能なファイルを認識するためには、vim9script ステートメントがファイル
の最初のステートメントとして現れる必要があります (例外については vim9-mix を
参照)。これは Vim に、グローバルな名前空間ではなく、独自の名前空間でスクリプト
を解釈するように指示します。ファイルが次で始まる場合:
vim9script
var myvar = 'yes'
"myvar" はこのファイルの中にしか存在しないことになります。一方、vim9scriptvar myvar = 'yes'
がなければ、他のスクリプトや関数から g:myvar として利用できます。
E1101
ファイルレベルの変数は、旧来のVim script のスクリプトローカル変数 "s:" と非常
によく似ていますが、"s:" は省略されています。そして、それらは削除することがで
きません。
Vim9 script では、グローバルな "g:" 名前空間は従来通り使用可能です。また、
"w:"、"b:"、"t: " 名前空間も使用できます。これらの共通点は、変数が宣言されてい
ないこと、特定の型を持っていないこと、削除できることです。 E1304
vim9script の副作用として、'cpoptions' オプションが Vim のデフォルト値に設定
されることが挙げられます:
:set cpo&vim
効果の1つは、line-continuation が常に有効であることです。'cpoptions' の元の値はスクリプトの最後に復元され、スクリプトで追加または削除されたフラグも元の値
に追加または削除されて同じ効果を得ることができます。フラグの順序は変更されるこ
とがあります。起動時に読み込まれる vimrc ファイルでは、このようなことは起こ
りません。
vim9-mix
1つのスクリプトファイルで、旧来と Vim9 の両方の構文を使用する方法があります:
" コメントはこちら
if !has('vim9script')
" 旧来のスクリプトのコマンドはこちら
finish
endif
vim9script
# Vim9 script のコマンドはこちら
これにより、可能であれば Vim9 script 構文を利用したスクリプトを書くことができif !has('vim9script')
" 旧来のスクリプトのコマンドはこちら
finish
endif
vim9script
# Vim9 script のコマンドはこちら
ますが、Vim9 のないバージョンでも動作するようになります。
これは、2通りの方法でしか機能しません:
1. "if" 文は false と評価され、endif までのコマンドはスキップされ、
vim9script が実際に実行される最初のコマンドとなる。
2. "if" 文が true に評価されると、endif までのコマンドが実行され、finish は
vim9script に到達する前に脱出します。
Export
:export :exp
項目の export は次のように記述できます:
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 ...
このことからわかるように、定数、変数、:def 関数、クラスのみが export 可能で
す。
E1042
:export はスクリプトレベルで、Vim9 script でのみ使用できます。
Import
:import :imp E1094 E1047 E1262
E1048 E1049 E1053 E1071 E1088 E1236
export された項目は、別のスクリプトで import することができます。import 構文に
は、2つの形式があります。単純な形式は:
import {filename}
ここで {filename} は文字列として評価される式でなければなりません。この形式では、
ファイル名は ".vim" で終わる必要があり、".vim" より前の部分が名前空間のスクリ
プトローカル名となります。例:
import "myscript.vim"
これにより、"myscript.vim" に export された各項目は、"myscript.item" 等として
利用可能になります。
:import-as E1257 E1261
名前が長い場合や曖昧な場合は、この形式を使用して別の名前を指定することができま
す:
import {longfilename} as {name}
この形式では、{name} が import された名前空間の特定のスクリプトローカル名とな
ります。したがって、{name} は internal-variables のように、文字、数字、'_'で
構成されなければなりません。{longfilename} 式は、任意のファイル名で評価する必
要があります。例:
import "thatscript.vim.v2" as that
E1060 E1258 E1259 E1260"that.item" 等として使えます。"that" という名称は自由です。import したスクリプ
トを指していると認識されるようなものを使ってください。コマンド名、コマンド修飾
語、組み込み関数名などは避けてください。名前がそれらと被るからです。名前を大文
字で始めないほうがいいです。大文字で始めると、グローバルなユーザーコマンドや関
数と被る可能性があります。また、その名前を関数や変数名など、スクリプト内の他の
何かに使用することはできません。
名前のドットが望ましくない場合は、関数のローカル参照を作成できます:
var LongFunc = that.LongFuncName
これは定数に対しても機能します:
const MAXLEN = that.MAX_LEN_OF_NAME
これは変数には使えません。なぜなら、値は一度コピーされ、変数を変更するときは元
の変数ではなく、コピーが変更されるからです。ドットを含む完全な名前を使用する必
要があります。
関数内で :import を使用することはできません。import された項目はスクリプトレ
ベルに存在し、一度だけ import されることを意図しています。
import の後のスクリプト名は次のようになります:
- "." または ".." で始まる相対パス。スクリプトファイル自体の位置から相対的に
ファイルを検索する。大きなプラグインを複数のファイルに分割して使用する場合に
便利である。
- 絶対パス。Unixでは "/"、MS-Windowsでは "D:/" で始まる。これはほとんど使用さ
れない。
- 相対パスでも絶対パスでもないパス。これは、'runtimepath' エントリの "import"
サブディレクトリで見つかる。間違ったファイルを読み込まないようにするため、通
常、名前は長く、一意であるべきである。
Note "after/import" は使われない。
名前が ".vim" で終わらない場合は、"as name" を使用する必要があります。
vim9 script ファイルを一度 import すると、その結果はキャッシュされ、次に同じス
クリプトを import するときに使用されます。再度読み込まれることはありません。
2 つの異なる "as" 名を使用する場合も同様に、同じスクリプトを 2 回 import する
ことはできません。
import 名を使用する場合、ドットと項目名は同じ行になければならず、改行すること
はできません:
echo that.
name # エラー!
echo that
.name # エラー!
import-mapname # エラー!
echo that
.name # エラー!
あるスクリプトから vim9 script に関数を import した場合、import した関数の前に
<SID> を付けることで、マッピングで参照することができます:
noremap <silent> ,a :call <SID>name.Function()<CR>
マッピングが定義されると、"<SID>name." は <SNR> とimport されたスクリプトのス
クリプトIDに置き換えられます。
さらに簡単な方法として、<ScriptCmd> を使用することができます:
noremap ,a <ScriptCmd>name.Function()<CR>
Note これは変数には効かず、関数にしか効かないということです。
import-legacy legacy-import
:import は旧来の Vim script でも使用することができます。"s:" というプリフィ
ックスがない場合でも、import された名前空間はスクリプトローカルとなります。
例:
import "myfile.vim"
call s:myfile.MyFunc()
call s:myfile.MyFunc()
そして "as name" 形式を使用する:
import "otherfile.vim9script" as that
call s:that.OtherFunc()
call s:that.OtherFunc()
ただし、名前空間を単独で解決することはできません:
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
これは、旧来のマッピングコンテキストにおける <SID> の使用にも影響します。
<SID> は関数に対してのみ有効なプリフィックスであり、名前空間に対しては有効で
はありません、スクリプトローカルな名前空間内の関数をスコープするために使用する
ことはできません。関数の前に <SID> を付ける代わりに、<ScriptCmd> を使用す
る必要があります。例:
noremap ,a <ScriptCmd>:call s:that.OtherFunc()<CR>
:import-cycle
import コマンドは、遭遇したときに実行されます。スクリプト A がスクリプト B
を import し、B が (直接または間接的に) A を import した場合、これはスキップさ
れます。この時点では、"import B" 以降の A 内の項目はまだ処理されておらず、定義
されていません。したがって、循環的な import が存在しても、直接的にはエラーにな
りませんが、"import B" 以降の A 内の項目が定義されていないためにエラーになる可
能性があります。これはオートロード import には適用されません。次項参照。
オートロードスクリプトを import する
vim9-autoload import-autoload
起動速度を最適化するために、スクリプトの読み込みは実際に必要になるまで延期する
必要があります。オートロード機構を使用することをお勧めします:
E1264
1. プラグインでは、オートロードスクリプトから import された項目を参照するユー
ザーコマンド、関数、マッピングを定義します。
import autoload 'for/search.vim'
command -nargs=1 SearchForStuff search.Stuff(<f-args>)
command -nargs=1 SearchForStuff search.Stuff(<f-args>)
これは、.../plugin/anyname.vim に入ります。"anyname.vim" は自由に選ぶことが
できます。"SearchForStuff " コマンドが利用できるようになりました。
import の引数 "autoload" は、項目の1つが実際に使用されるまでスクリプトが
ロードされないことを意味します。スクリプトは "import" ディレクトリではなく、
'runtimepath' の "autoload" ディレクトリの下に見つかります。また、相対名や
絶対名を使用することもできます。以下を参照。
2. オートロードスクリプトに、コードの大部分を置きます。
vim9script
export def Stuff(arg: string)
...
export def Stuff(arg: string)
...
これは、.../autoload/for/search.vim に入ります。
"search.vim" スクリプトを "/autoload/for/" ディレクトリに置くと、export さ
れた項目に "for#search#" というプリフィックスが付くようになります。このプリ
フィックスは、旧来のオートロードスクリプトで手動で行うように、ファイル名か
ら取得されます。したがって、export された関数は "for#search#Stuff" で見つけ
ることができますが、通常は import autoload を使用し、プリフィックスを使用
しません (この名前に遭遇した関数をコンパイルするときにオートロードスクリプ
トをロードするという副作用が発生します)。
機能を分割して、オートロードスクリプトから他のスクリプトを好きなように
import することができます。こうすることで、プラグイン間でコードを共有するこ
とができます。
'runtimepath' のすべてのエントリからオートロードスクリプトを検索するのは、少し時
間がかかることがあります。プラグインがスクリプトの場所を知っている場合、多くの
場合、相対パスが使用されます。これにより、検索が回避され、かなり速くなるはずで
す。また、スクリプト名が一意である必要がないことも利点です。絶対パスも可能です。
例:
import autoload '../lib/implement.vim'
import autoload MyScriptsDir .. '/lib/implement.vim'
import autoload MyScriptsDir .. '/lib/implement.vim'
import したオートロードスクリプトを使用するマッピングを定義するには、特殊キー
<ScriptCmd> が便利です。これは、マッピングのコマンドで、マッピングが定義され
た場所のスクリプトコンテキストを使用することを可能にします。
:def 関数をコンパイルしているときに、オートロードスクリプトの関数に遭遇する
と、:def 関数が呼び出されるまでスクリプトはロードされません。これは、引数や
戻り値の型がまだ分かっていないため、実行時にのみエラーが発生することも意味しま
す。もし、'#' 文字を含む名前を使用するのであれば、そのオートロードスクリプトは
ロードされます。
オートロードスクリプトの中で、意図せずロードのきっかけとなる項目を参照しないよ
うに注意してください。例えば、関数名を取るオプションを設定する場合、関数参照で
はなく、必ず文字列を使用してください:
import autoload 'qftf.vim'
&quickfixtextfunc = 'qftf.Func' # オートロードスクリプトはロードされ
# ない
&quickfixtextfunc = qftf.Func # オートロードスクリプトはロードされる
一方で、スクリプトを早めに読み込むことで、エラーが発生した場合に、その旨を伝え&quickfixtextfunc = 'qftf.Func' # オートロードスクリプトはロードされ
# ない
&quickfixtextfunc = qftf.Func # オートロードスクリプトはロードされる
ることができるようになり便利です。
test_override() のテスト用関数を使用して、import autoload にスクリプトをす
ぐにロードさせることができます。これにより、項目と型が実際に使用されるのを待た
ずにチェックできるようになります:
test_override('autoload', 1)
後でリセットします: test_override('autoload', 0)
または: test_override('ALL', 0)
==============================================================================
6. クラスとインターフェイス vim9-classes
旧来のスクリプトでは、関数であるメンバーを追加することで、辞書を一種のオブジェ
クトとして使用することができます。しかし、これは非常に非効率的で、すべてのオブ
ジェクトが正しいメンバーを持っていることを確認する作業を作成者が行う必要があり
ます。Dictionary-function を参照。
Vim9 script では、一般的なオブジェクト指向プログラミング言語のように、クラス、
オブジェクト、インターフェイスを持つことができます。これは多くの機能を含んでい
るため、別のヘルプファイルに記載されています: vim9class.txt
==============================================================================
9. 理論的根拠 vim9-rationale
:def コマンド
プラグインの作者から、Vim script をもっと速くしたいとの要望がありました。関数
呼び出しの既存のセマンティクスを維持することは、関数の呼び出し、ローカル関数ス
コープの設定、行の実行に伴うオーバーヘッドのため、不可能に近いことが調査により
判明しました。エラーメッセージや例外など、処理しなければならない細部はたくさん
あります。a: および l: スコープ用の辞書を作成する必要性、a:000 のリスト、その
他いくつかの回避できないオーバーヘッドを追加しすぎています。
したがって、新しいスタイルの関数を定義するための :def メソッドを追加する必要
があり、これにより異なるセマンティクスを持つ関数を定義することができるようにな
りました。ほとんどのことは以前と同じように動作しますが、そうでない部分もありま
す。関数を定義する新しい方法は、旧来のスタイルのコードとVim9スタイルのコードを
分離するための最良の方法であると考えられていました。
"def" を使用して関数を定義するのは、Python に由来します。他の言語は、従来の
Vim script と競合する "function" を使用します。
型チェック
Vim コマンドの行を命令にコンパイルする場合、可能な限りコンパイル時に行うべきで
す。これを実行時に先送りすると、実行速度が遅くなり間違いに気づくのが遅くなりま
す。例えば、"+" 文字に遭遇し、これを一般的な加算命令にコンパイルする場合、実行
時に引数の型を検査し、どのような加算を行うかを決定する必要があります。そして、
その型が辞書であった場合には、エラーを投げます。もし、型が数値であることが分か
っていれば、「数値の加算」命令を使うことができ、その方が高速です。このエラーは
コンパイル時に発生する可能性があり、2 つの数値の加算は失敗しないため、実行時の
エラー処理は必要ありません。
複合型に <type> を使った型の構文は、Javaと似ています。理解しやすく、広く使われ
ています。型名は、以前 Vim で使われていたものに、"void" や "bool" などが追加さ
れています。
乱雑と奇妙さを取り除く
:def 関数が旧来の関数と異なる構文であることが決まれば、一般的なプログラミン
グ言語を知っているユーザーにとってより馴染みやすいコードにするための改良を加え
ることは自由です。言い換えれば、Vim でしかしないような奇妙なことは削除します。
また、Vim script に古き良きViコマンドと後方互換性を持たせるために行われたもの
を中心に、雑多なものを取り除くことができます。
例:
- 関数を呼び出すための :call と式を評価するための :eval を削除します。
- 行の継続のための先頭のバックスラッシュを使用しない。式がどこで終わるかを自動
的に把握します。
しかし、そのためには、いくつかのことを変える必要があります:
- コメントは、文字列と混同しないように、" の代わりに # で始まるようにしました。
これはとにかく良いことで、いくつかの人気のある言語でも使用されています。
- Ex コマンドの範囲は、式との混同を避けるため、先頭にコロンを付ける必要があり
ます (シングルクォートは文字列またはマーク、"/" は割り算または検索コマンド、
等)。
目標は、違いを制限することです。良い基準は、古い構文を誤って使用した場合に、
エラーメッセージが表示される可能性が非常に高いことです。
人気のある言語から構文とセマンティクスを学ぶ
スクリプトの作成者は、Vim script の構文が慣れ親しんでいるものとは予想外に異な
ると不満を漏らしています。この不満を軽減するために、人気のある言語を例として使
用します。同時に、旧来の Vim script のよく知られた部分を放棄することは避けたい
です。
いろいろなことで TypeScript が踏襲されています。これは最近人気が出てきた言語で、
Vim script に似ています。また、静的型付け (変数は常に既知の値型を持つ) と動的
型付け (変数は異なる型を持つことができ、これは実行時に変更される) が混在してい
ます。旧来の Vim script は動的型付けされており、既存の多くの機能 (特に組み込み
関数) は動的型付けに依存していますが、静的型付けはより高速に実行できるため、
Vim9 script でもこの混ぜ合わせが必要です。
TypeScript の構文やセマンティクスに完全に合わせるつもりはありません。私たちは
ただ、Vim に使える部分、Vim ユーザーが喜ぶと予想される部分を取り出したいだけで
す。TypeScript は、独自の歴史、利点と欠点を持つ複雑な言語です。デメリットを知
るには、この本を読んでください: "JavaScript: The Good Parts"。または、
"TypeScript: the good parts" という記事を見つけて、"Things to avoid" セクショ
ンを読んでください。
他の言語 (Java、Pythonなど) に慣れている人も、TypeScript の中で気に入らないこ
とや理解できないことを見つけるでしょう。そういったものを避けるようにします。
TypeScript から避ける特定の項目:
- "+" をオーバーロードして、加算と文字列連結の両方に使用します。これは旧来の
Vim script に反しており、しばしば間違いにつながります。そのため、文字列の連
結には ".." を使い続けることにします。Lua もこの方法で ".." を使っています。
そして、より多くの値のために文字列に変換することができます。
- TypeScript では、"99 || 'yes'" のような式を条件として使うことができますが、
その値を真偽値に代入することができません。これは一貫性がなく、迷惑なことで
す。Vim は && や || を使った式を認識し、その結果を bool として使うことができ
ます。デフォルト値を使う仕組みとして、falsy-operator が追加されました。
- TypeScript では、空の文字列を Falsy と見なしますが、空のリストや辞書を
Truthy と見なしています。これは矛盾しています。Vimでは、空のリストや辞書も
Falsy となります。
- TypeScript にはさまざまな "読み出し専用" 型がありますが、型キャストによって
不変な性質を取り除くことができるため、有用性は限定的です。Vim は値をロックす
るので、より柔軟性がありますが、実行時にしかチェックされません。
- TypeScript には複雑な "import" 文があり、Vim の import の仕組みとマッチしま
せん。代わりに、import されたスクリプトが一度だけ読み込まれることにマッチす
る、よりシンプルなメカニズムが使用されています。
宣言
旧来の Vim script では、すべての代入に :let を使用しますが、Vim9 では宣言が
使用されます。これは異なるので、別のコマンドを使うのがよいでしょう。var で
す。これは多くの言語で使用されています。セマンティクスは若干異なるかもしれませ
んが、宣言として簡単に認識することができます。
定数に :const を使うのは一般的ですが、そのセマンティクスは様々です。変数だけ
を不変にする言語もあれば、値も不変にする言語もあります。Java では "final" が変
数だけを不変にするためによく知られているので、これを使うことにしました。そし
て、:const を使うことで、両方を不変にすることができます。これは旧来の Vim
script でも使われていたもので、意味はほぼ同じです。
最終的に得られるものは、Dart と非常によく似ています:
:var name # 可変な変数と値
:final name # 不変な変数、可変な値
:const name # 不変な変数と値
:final name # 不変な変数、可変な値
:const name # 不変な変数と値
旧来と Vim9 script が混在し、グローバル変数が共有されるため、オプションで型
チェックを行うことが望ましいです。また、型推論により、多くの場合、型を指定する
必要がなくなります。宣言に型を追加するには、TypeScript の構文が最も適していま
す:
var name: string # 文字列型が指定される
...
name = 'John'
const greeting = 'hello' # 文字列型が推測される
...
name = 'John'
const greeting = 'hello' # 文字列型が推測される
これは、型を宣言に入れる方法です:
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
2つの選択肢を検討しました:
1. Dart のように名前の前に型を置く:
var list<string> mylist
final list<string> mylist = ['foo']
def Func(number arg1, string arg2) bool
2. 変数名の後に型を入れるが、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
C や Java に慣れている人には1つ目の方が馴染みやすいと思います。2つ目は1つ目に
対しての優位性があまりないので、2つ目は捨ててしまいましょう。
型推論を使うので、値から推論できる場合は型は省くことができます。つまり、var
の後に型が続くのか名前が続くのかが分からないのです。そのため、Vim だけでなく人
間にとっても構文解析が難しくなります。また、型名になりうる変数名を使うことは許
されないでしょう。var string string を使うのはあまりにも紛らわしいです。
コロンを使用して名前と型を区切る選ばれた構文は、句読点を追加しますが、実際に
は、宣言の部分を認識しやすくします。
式
式の評価は、すでに他の言語が行っているものに近いものでした。いくつかの細部は予
期せぬものであり、改善することができます。例えば、真偽値条件では、文字列を受け
取り、それを数値に変換して、その数値が0でないかどうかをチェックします。これは
予想外のことで、しばしば間違いにつながります。なぜなら、数字で始まらないテキス
トはゼロに変換され、これは偽とみなされるからです。このように、条件に文字列を使
うと、エラーが出ず、偽とみなされることが多いのです。これは紛らわしいです。
Vim9 では、間違いを避けるために型チェックがより厳しくなっています。例えば
:if コマンドや || 演算子など、条件が使われる場所では、真偽値のような値しか
受け付けません:
true: true, v:true, 1, 0 < 9
false: false, v:false, 0, 0 > 9
Note 数字の 0 は偽で、数字の 1 は真です。これは、他の多くの言語よりも寛容です。
これは、多くの組み込み関数がこれらの値を返すため、それを変更すると、解決するよ
りも多くの問題を引き起こすからです。これをしばらく使ってみると、うまくいくこと
が分かりました。
任意の型を持つ値について、それを真偽値として使いたい場合は、!! 演算子を使い
ます:
true: !!'text' !![99] !!{'x': 1} !!99
false: !!'' !![] !!{}
JavaScript のような言語には、このような便利な構文があります:
GetName() || 'unknown'
ただし、これは、条件に真偽値のみを許可することと競合します。したがって、"??"演算子が追加されました:
GetName() ?? 'unknown'
ここでは、値をそのまま使用し、真偽値にしないという意図を明示的に表すことができます。これは falsy-operator と呼ばれます。
Import と Export
旧来の Vim script の問題点は、デフォルトですべての関数と変数がグローバルである
ことです。スクリプトローカルにすることは可能ですが、そうすると他のスクリプトで
利用できなくなります。これは、選択された項目のみをエクスポートし、残りをローカ
ルに保つパッケージの概念に反しています。
Vim9 script では、JavaScript の import および export 機構に非常によく似た機構
がサポートされています。これは既存の :source コマンドの亜種で、期待通りに動
作します:
- デフォルトですべてをグローバルにするのではなく、すべてをスクリプトローカルに
することで、その一部をエクスポートすることができる。
- スクリプトの import 時には、import されるシンボルが明示的にリストされるため、
名前の衝突や、後から機能を追加した場合の失敗を回避することができる。
- この機構により、export された関数、変数、クラスという非常に明確なAPIを持つ巨
大で長いスクリプトを書くことができる。
- 相対パスを使用することで、パッケージ内のインポートのロードが非常に速くなり、
多くのディレクトリを検索する必要がない。
- 一度インポートを使用すると、その項目はキャッシュされ、再ロードする必要はな
い。
- スクリプトローカルにするための Vim 特有の "s:" の使用をやめることができる。
(Vim9 または旧来の script から) Vim9 script を読み込む場合、グローバルに定義さ
れた項目のみが使用可能で、export された項目は使用できません。代替案を検討しま
した:
- export された項目は、すべてスクリプトローカル項目として利用できるようにする。
これにより、どの項目が定義されるかは制御できず、すぐにトラブルに発展する可能
性がある。
- export された項目を使用し、それらをグローバルにする。デメリットは、グローバ
ルな名前空間での名前の衝突を避けることができなくなることである。
- Vim9 script を読み込むことを完全に禁止し、:import を使うことを要求する。こ
れにより、スクリプトをテストに使用したり、コマンドラインからスクリプトを取得
して試したりすることが難しくなる。
Note 上記の通り、旧来の Vim script でも :import を使用することができます。
関数を早期にコンパイルする
関数は、呼び出されたとき、または :defcompile が使用されたときにコンパイルさ
れます。なぜ、早期にコンパイルして、構文エラーや型エラーが早期に報告されるよう
にしないのでしょうか?
なぜなら、後に定義される関数への前方参照が存在する可能性があるからです。例え
ば、A、B、C という関数を定義し、A が B を呼び出し、B が C を呼び出し、C がまた
A を呼び出すとします。前方参照を避けるために関数を並べ替えることは不可能です。
別の方法としては、まずファイルをスキャンして項目を見つけ、そのタイプを把握し、
前方参照が見つかるようにしてから、スクリプトを実行し、関数をコンパイルする方法
があります。この場合、スクリプトを2回解析する必要があり、遅いです。また、機能
がサポートされているかどうかのチェックなど、スクリプトレベルでの条件もあるた
め、使いにくいです。うまくいくかどうか試行錯誤しましたが、うまく動作させること
は不可能であることが判明しました。
スクリプトの最後にすべての関数をコンパイルすることは可能でしょう。しかし、ある
関数が一度も呼び出されない場合、その関数をコンパイルするためのオーバーヘッド
が、いずれにせよカウントされてしまうという欠点があります。起動速度は非常に重要
なので、ほとんどの場合、コンパイルは後回しにした方がよく、構文や型のエラーはそ
の時にしか報告されないことを受け入れることができます。例えばテスト時など、これ
らのエラーを早期に発見する必要がある場合は、スクリプトの最後の :defcompile
コマンドが効果的です。
なぜ既存の組み込み言語を使わないのか?
Vim は Perl、Python、Lua、Tcl、その他いくつかへのインターフェイスをサポートし
ています。しかし、これらのインターフェイスは様々な理由で広く使われるようになる
ことはありませんでした。Vim9 が設計されたとき、これらのインターフェイスの優先
順位を下げ、Vim script に集中させることが決定されました。
それでも、プラグイン作成者は他の馴染みのある言語を探したり、既存のライブラリを
使用したり、性能の恩恵を確認したりするかもしれません。我々は、プラグインの作者
が任意の言語でコードを書き、ジョブやチャネルを使って外部プロセスとして実行する
ことを推奨します。我々は、これをどうにかして簡単にできるように努力することがで
きます。
外部ツールの使用にはデメリットもあります。代替案としては、ツールを Vim script
に変換することです。この場合、過度の翻訳を行ずに、同時にコードを高速に保つに
は、ツールの構成要素がサポートされている必要があります。ほとんどの言語がクラス
をサポートしているので、Vim でクラスがサポートされていないのは問題です。
vim:tw=78:ts=8:noet:ft=help:norl: