vim9class.txt For Vim バージョン 9.1. Last change: 2024 Apr 13
VIMリファレンスマニュアル by Bram Moolenaar
Vim9 のクラス、オブジェクト、インターフェイス、型定義と列挙型。 vim9-class
1. 概要 Vim9-class-overview
2. 単純なクラス Vim9-simple-class
3. クラス変数とメソッド Vim9-class-member
4. 抽象クラスを使う Vim9-abstract-class
5. インターフェイスを使う Vim9-using-interface
6. さらなるクラスの詳細 Vim9-class
7. 型定義 Vim9-type
8. 列挙型 Vim9-enum
9. 論理的根拠
10. 後でやる
==============================================================================
1. 概要 Vim9-class-overview
聞こえのいい呼び名は「オブジェクト指向プログラミング」である。このテーマに関す
る教材はたくさんある。ここでは、あなたが基本をすでに理解していることを前提とし
て、Vim9 script が提供するものを文書化する。この機能を効果的に使用する方法に
関する役立つヒントも加えている。Vim9 のクラスとオブジェクトは、旧来の Vim
script や従来の関数では使用できない。
基本アイテムはオブジェクト:
- オブジェクトは状態を保存する。これには、それぞれ値を持つことができる 1 つ以
上の変数が含まれる。
- オブジェクトは、その状態を使用したり操作したりする関数を提供する。これらの関
数は「オブジェクト上で」呼び出され、これが従来のデータとデータを操作するコー
ドの分離とは異なる点である。
- オブジェクトには、型付きのメンバー変数とメソッドを備えた明確に定義されたイン
ターフェイスを持つ。
- オブジェクトはクラスから生成され、すべてのオブジェクトは同じインターフェイス
を持つ。これは実行時に変更されることはなく、動的なものではない。
オブジェクトはクラスによってのみ生成さる。クラスは以下を提供する:
- コンストラクタとしての new() メソッド、これはクラスのオブジェクトを返す。
このメソッドはクラス名で呼び出される: MyClass.new()。
- クラスのすべてのオブジェクトで状態を共有する: クラス変数(クラスメンバ)。
- スーパークラスとサブクラス、継承によるクラスの階層。
インターフェイスはオブジェクトのプロパティを指定するために使用される:
- オブジェクトはいくつかのインターフェイスを実装する宣言ができる。
- 同じインターフェイスを実装した異なるオブジェクトを同じように扱うことができ
る。
クラス階層は単一の継承になる。それ以外の場合は、必要に応じてインターフェイスを
使用すること。
クラスのモデリング ~
好きな方法でクラスをモデル化できる。何を構築しているのかを念頭に置き、現実世界
をモデル化しようとしないこと。これは、特に教師が現実世界のオブジェクトを使用し
てクラス関係を説明するので、モデルが現実世界を反映している必要があると考えるか
もしれず、混乱を招く可能性がある。そうではない! モデルは目的に合ったものでな
ければならない。
多くの場合、合成(オブジェクトに他のオブジェクトが含まれる)の方が継承(オブジェ
クトが別のオブジェクトを拡張する)よりも優れていることを念頭に置くこと。最適な
クラスモデルを見つけるために時間を無駄にする必要はない。あるいは、正方形が長方
形なのか、それとも長方形が正方形なのかを議論するのは時間の無駄である。それは問
題ではない。
==============================================================================
2. 単純なクラス Vim9-simple-class
簡単な例から始めよう: テキストの位置を保存するクラスだ (これをより効率的に行う
方法は後述する): >
new() メソッドを使用して、このクラスからオブジェクトを作成できる: >
オブジェクト変数 "lnum" と "col" には直接アクセスできる: >
他のオブジェクト指向言語を使用したことがある場合は、Vim ではクラス定義内で宣言
されたオブジェクトのメンバが一貫してプリフィックス "this." で参照されることに
気づくだろう。これは、Java や TypeScript などの言語とは異なる。この命名規則に
より、オブジェクトのメンバを簡単に見つけることができる。また、変数にプリフィッ
クス "this." がない場合、それがオブジェクト変数ではないことが分かる。
E1411
クラス定義の外からオブジェクトのメソッドや変数にアクセスするには、オブジェクト
名の後にドットを付け、その後にメンバを続ける: >
E1405 E1406
クラス名を式として使用することはできない。クラス名は、代入の左辺では使用できな
い。
オブジェクト変数の書き込みアクセス ~
read-only-variable
では、オブジェクト変数を直接変更してみよう: >
これはエラーになる! デフォルトでは、オブジェクト変数は読み取ることはできても、
設定することはできないからである。TextPosition クラスがそのためのメソッドを提
供しているのはそのためだ: >
オブジェクト変数の読み取りは許可するが設定は許可しないのが、最も一般的で安全な
方法である。ほとんどの場合、値を使用しても問題はないが、値を設定すると注意が必
要な副作用が発生する可能性がある。この場合、SetLnum() メソッドは行番号が有効か
どうかをチェックし、エラーを返すか、最も近い有効な値を使用する。
:public public-variable E1331
副作用を気にせず、オブジェクト変数をいつでも変更できるようにしたい場合は、オブ
ジェクト変数をパブリックにすることができる: >
これで、SetLnum(), SetCol() および SetPosition() メソッドは必要なくなり、
"pos.lnum" を直接設定してもエラーが発生しなくなる。
E1326
存在しないオブジェクト変数を設定しようとすると、エラーが発生する: >
E1376
クラス名を使用してオブジェクト変数にアクセスすることはできない。
Protected 変数 ~
protected-variable E1332 E1333
一方、オブジェクト変数をクラスまたはそのサブクラスの外部から直接読み取られたく
ない場合は、オブジェクト変数を保護することができる。これは、名前の前にアンダー
スコアを付けることによって行われる: >
次に、protected 変数の値を取得するメソッドを提供する必要がある。これらは一般に
ゲッターと呼ばれる。"Get" で始まる名前を使用することをお勧めする: >
この例はあまり役に立たない。変数はパブリックでもよいだろう。値を確認するときに
便利である。例えば、行番号を総行数に制限する: >
Protected メソッド ~
private-method E1366
オブジェクトメソッドを同じクラスの他のメソッドからのみアクセス可能にし、クラス
の外部からは使用しないようにしたい場合は、オブジェクトメソッドを protected に
できる。これを行うには、メソッド名の前にアンダースコアを付ける: >
クラス外の protected メソッドにアクセスすると、エラーが発生する (上記のクラス
を使用した場合): >
new() メソッドの単純化 ~
new() constructor
default-constructor および multiple-constructors も参照。
多くのコンストラクタはオブジェクト変数の値を受け取る。したがって、次のようなパ
ターンがよく見られる: >
E1390
このテキストを記述する必要があるだけでなく、各変数の型も 2 回記述されている。
これは非常に一般的なので、new() を記述する短い方法が提供されている: >
セマンティクスは理解しやすい。"this." を含むオブジェクト変数名を new() の引数
として指定すると、new() 呼び出しで指定された値がそのオブジェクト変数に割り当て
られることを意味する。このメカニズムは Dart 言語から来ている。
new() を使用して変数をパブリックにするこの方法をまとめると、最初のクラス定義よ
りもはるかに短いクラス定義になる: >
新しいオブジェクトを構築するシーケンスは次のとおり:
1. メモリが割り当てられ、クリアされる。すべての値はゼロ/偽/空でである。
2. 初期化子を持つ宣言されたオブジェクト変数ごとに、式が評価され、変数に割り当
てられる。これは、変数がクラスで宣言された順序で発生する。
3. "this.name" 形式の new() メソッドの引数が割り当てられる。
4. new() メソッドの本体が実行される。
クラスが親クラスを拡張する場合も、同じことが起こる。2 番目のステップでは、最初
に親クラスのオブジェクト変数が初期化される。親が "super()" または "new()" を呼
び出す必要はない。
E1365
new() メソッドを定義するときは、戻り値の型を指定しないこと。常にクラスのオブ
ジェクトを返す。
E1386
オブジェクトメソッドを呼び出すときは、メソッド名の前にオブジェクト変数名を付け
る必要がある。クラス名を使用してオブジェクトメソッドを呼び出すことはできない。
==============================================================================
3. クラス変数とメソッド Vim9-class-member
:static E1337 E1338 E1368
クラスメンバは "static" で宣言される。これらは、定義されているクラス内でプリ
フィックスなしの名前によって使用される: >
名前はそのまま使用されるため、メソッドの引数名やローカル変数名による名前のシャ
ドウイングは許可されていない。
E1374 E1375 E1384 E1385
クラスメンバが定義されているクラスの外にあるクラスメンバにアクセスするには、ク
ラス名のプリフィックスを使用する必要がある。オブジェクトを使用してクラスメンバ
にアクセスすることはできない。
オブジェクトメンバと同様に、名前の最初の文字としてアンダースコアを使用すること
でアクセスを保護することができ、プリフィックス "public" を付けることでアクセス
を公開することができる: >
class-method
クラスメソッドも "static" で宣言される。クラス変数は使用できるが、オブジェクト
変数にはアクセスできず、"this" キーワードは使用できない。
>
クラス内では、クラスメソッドを名前で直接呼び出すことができるが、クラスの外で
は、クラス名がプリフィックスされている必要がある:
`OtherThing.ClearTotalSize()`。また、クラス変数初期化子、ラムダ式、入れ子になっ
た関数といった特殊なコンテキストでは、パブリッククラスメソッドに名前プリフィッ
クスを使用しなければならない:
>
オブジェクトメソッドと同様に、メソッド名の最初の文字としてアンダースコアを使用
することで、アクセスを protected にすることができる: >
E1370
Note コンストラクタは "static" として宣言できないことに注意。これらは staticの
ように呼び出されるが、オブジェクトメソッドとして実行される。これらは "this"に
アクセスできる。
拡張クラス内のスーパークラスのクラスメソッドとクラス変数にアクセスするには、定
義クラスの外部からの場合と同様に、クラス名のプリフィックスを使用する必要がある:
>
クラス変数とメソッドは子クラスに継承されない。子クラスは、スーパークラスと同じ
名前の静的変数またはメソッドを宣言できる。メンバが使用されるクラスに応じて、対
応するクラスのメンバが使用される。子クラスのクラスメンバの型は、スーパークラス
のクラスメンバの型とは異なる場合がある。
クラスメソッドまたはオブジェクトメソッド名の 2 重アンダースコア (__) プリフィッ
クスは、将来の使用のために予約されている。
object-final-variable E1409
:final キーワードを使用すると、クラス変数またはオブジェクト変数を定数にする
ことができる。例: >
final 変数はコンストラクタ関数からのみ変更できる。例: >
Note final 変数の値は変更できることに注意。例: >
E1408
final 変数はインターフェイスではサポートされていない。クラスまたはオブジェクト
のメソッドを final にすることはできない。
object-const-variable
:const キーワードを使用すると、クラス変数またはオブジェクト変数を作成し、そ
の値を定数にすることができる。例: >
const 変数はコンストラクタ関数からのみ変更できる。例: >
const 変数およびその値は変更できない。例: >
E1410
const 変数はインターフェイスではサポートされていない。クラスまたはオブジェクト
のメソッドを const にすることはできない。
==============================================================================
4. 抽象クラスを使う Vim9-abstract-class
抽象クラスは、少なくとも 1 つのサブクラスのベースを形成する。クラスモデルでは、
いくつかのクラスが共有できる同じプロパティを持っていることがよくあるが、これら
のプロパティを持つクラスにはオブジェクトを作成するための十分な状態がない。サブ
クラスは、オブジェクトの作成に使用する前に、抽象クラスを拡張し、不足している状
態やメソッドを追加する必要がある。
例えば、Shape クラスには色と太さを格納できる。Shape オブジェクトを作成すること
はできない。形状の種類に関する情報が欠落している。Shape クラスは、オブジェクト
を作成できる Square クラスと Triangle クラスのベースとして機能する。例: >
抽象クラスは、new() メソッドがないことを除き、通常のクラスと同じ方法で定義され
る。 E1359
abstract-method E1371 E1372
抽象メソッドは、抽象クラス内でメソッド定義時に "abstract" プリフィックスを使用
することで定義できる: >
抽象クラスの静的メソッドを抽象メソッドにすることはできない。
E1373
抽象クラスを拡張する非抽象クラスは、すべての抽象メソッドを実装する必要がある。
シグネチャ (引数、引数の型、戻り値の型) はまったく同じである必要がある。メソッ
ドの戻り値の型がクラスの場合、そのクラスまたはそのサブクラスの 1 つを拡張メソッ
ドで使用できる。
==============================================================================
5. インターフェイスを使う Vim9-using-interface
Shape, Square および Triangle を使用した上記の例は、オブジェクトの表面積を計算
するメソッドを追加するとさらに便利になる。そのために、数値を返す 1 つのメソッ
ド Surface() を指定する HasSurface というインターフェイスを作成する。この例は
上記の例を拡張したものである: >
E1348 E1349 E1367 E1382 E1383
クラスがインターフェイスの実装を宣言した場合、インターフェイスで指定されたすべ
ての項目が同じ型でクラス内に出現する必要がある。
インターフェイス名は型として使用できる: >
E1378 E1379 E1380 E1387
インターフェイスには、オブジェクトメソッドと読み取り専用のオブジェクト変数のみ
を含めることができる。インターフェイスには、読み書き可能または protected オブ
ジェクト変数、protected オブジェクトメソッド、クラス変数、およびクラスメソッド
を含めることはできまない。
インターフェイスは、"extends" を使用して別のインターフェイスを拡張できる。サブ
インターフェイスは、スーパーインターフェイスからすべてのインスタンス変数とメ
ソッドを継承する。
==============================================================================
6. さらなるクラスの詳細 Vim9-class Class class
クラスの定義 ~
:class :endclass :abstract
クラスは `:class` と `:endclass` の間で定義される。クラス全体は 1 つのスクリプ
トファイルで定義される。後からクラスに追加することはできない。
クラスは Vim9 script ファイル内でのみ定義できる。 E1316
関数内でクラスを定義することはできない。 E1429
スクリプトファイル内に複数のクラスを定義することは可能である。しかし、通常はメ
インクラスを 1 つだけエクスポートする方が良い。型、列挙型、ヘルパークラスを定
義すると便利である。
`:abstract` キーワードをプリフィックスとして付けたり、`:export` を使用したりで
きる。これにより、次のようなバリエーションが得られる: >
E1314
クラス名は CamelCased にする必要がある。そして、大文字で始める必要がある。これ
により、組み込み型との衝突が回避される。
E1315
クラス名の後に、これらのオプションの項目を使用できる。それぞれ 1 回のみ出現で
きる。これらは任意の順序で出現できるが、この順序が推奨される: >
各変数とメソッドの名前は 1 回だけ使用できる。同じ名前で異なる型の引数を持つメ
ソッドを定義することはできない。パブリックと protected のメンバー変数を同じ名
前で使用することはできない。スーパークラスで使用したオブジェクト変数名を子クラ
スで再利用することはできない。
オブジェクト変数の初期化 ~
変数の型がクラスで明示的に指定されていない場合、クラス定義時に "any" に設定さ
れる。オブジェクトがクラスからインスタンス化されると、変数の型が設定される。
次の予約キーワード名は、オブジェクトまたはクラス変数名として使用できない:
"super", "this", "true", "false", "null", "null_blob", "null_dict",
"null_function", "null_list", "null_partial", "null_string", "null_channel" お
よび "null_job"。
クラスを拡張する ~
extends
クラスは他のクラスを拡張できる。 E1352 E1353 E1354
基本的な考え方は、既存のクラスの上に構築し、それにプロパティを追加することだ。
拡張されたクラスは "base class" (基底クラス) または "super class" (スーパーク
ラス) と呼ばれる。新しいクラスは "child class" (子クラス) と呼ばれる。
基底クラスのオブジェクト変数はすべて子クラスに引き継がれる。(他の言語とは異な
り) それらをオーバーライドすることはできない。
E1356 E1357 E1358
基底クラスのオブジェクトメソッドは無効にすることができる。署名 (引数、引数の
型、戻り値の型) はまったく同じである必要がある。メソッドの戻り値の型がクラスの
場合、そのクラスまたはそのサブクラスの 1 つを拡張メソッドで使用できる。基底ク
ラスのメソッドは、先頭に "super." を付けることで呼び出すことができる。
E1377
子クラスのメソッド (パブリックまたは protected) のアクセスレベルは、スーパーク
ラスと同じである必要がある。
基底クラスの他のオブジェクトメソッドは、子クラスによって引き継がれる。
"new" で始まるメソッドを含むクラスメソッドは、オブジェクトメソッドと同様に無効
にすることができる。基底クラスのメソッドは、クラス名 (クラスメソッドの場合) ま
たは "super." のプリフィックスを付けることで呼び出すことができる。
他の言語とは異なり、基底クラスのコンストラクタを呼び出す必要はない。実際に呼び
出すことはできない。基底クラスからの初期化を子クラスでも行う必要がある場合は、
それをオブジェクトメソッドに入れて、すべての constructor() からそのメソッドを
呼び出す。
基底クラスで new() メソッドが指定されていない場合は、自動的に new() メソッドが
作成される。このメソッドは子クラスには引き継がれない。子クラスは独自の new()
メソッドを定義できるが、メソッドがなければ、new() メソッドが自動的に追加され
る。
インターフェイスを実装するクラス ~
implements E1346 E1347 E1389
クラスは 1 つ以上のインターフェイスを実装できる。"implements" キーワードは 1 回
のみ使用できる E1350 。複数のインターフェイスをコンマで区切って指定できる。
各インターフェイス名は 1 回だけ出現できる。 E1351
インターフェイスを定義するクラス ~
specifies
クラスは、名前付きインターフェイスを使用して、そのインターフェイス、オブジェク
ト変数およびメソッドを宣言できる。これにより、多くの言語、特に Java で頻繁に行
われる、インターフェイスを個別に指定する必要がなくなった。
クラス内の項目 ~
E1318 E1325 E1388
クラス内では、`:class` と `:endclass` の間に、次の項目を含めることができる:
- オブジェクト変数宣言: >
オブジェクト変数の場合は、型を指定する必要がある。最善の方法は、": {type}" を
使用してこれを明示的に行うことである。単純な型の場合、"= 123" などの初期化子を
使用することもでき、Vim はその型が数値であると認識する。より複雑な型や、型が不
完全になる場合には、これを行わないこと。
例: >
E1330
"void", "null" および "v:none" 等、一部の型は使用できない。
組み込みオブジェクトメソッド ~
builtin-object-methods
empty()、len() および string() などの一部の組み込み関数はオブジェクトで
使用できる。オブジェクトは、これらの組み込み関数と同じ名前のメソッドを実装し
て、オブジェクト固有の値を返すことができる。
E1412
以下の組み込みメソッドがサポートされている:
object-empty()
empty() empty() 関数によって呼び出され、オブジェクトが空かどうかを確認
する。このメソッドが存在しない場合は、true が返される。このメソッ
ドは引数を受け入れず、真偽値を返す必要がある。
object-len()
len() len() 関数によって呼び出され、オブジェクトの長さを返す。このメ
ソッドがクラスにない場合はエラーが発生し、ゼロが返される。このメ
ソッドは引数を受け入れず、数値を返す必要がある。
object-string()
string() string() 関数によって呼び出され、オブジェクトのテキスト表現を取
得する。オブジェクトに対する :echo コマンドでも使用される。この
メソッドがクラスにない場合は、組み込みのデフォルトのテキスト表現
が使用される。このメソッドは引数を受け入れず、文字列を返す必要が
ある。
E1413
クラスメソッドを組み込みメソッドとして使用することはできない。
インターフェイスの定義 ~
Interface :interface :endinterface
インターフェイスは `:interface` と `:endinterface` の間で定義される。プリフィッ
クスとして `:export` を付けることができる: >
インターフェイスは、クラスと同じようにオブジェクト変数を宣言できるが、初期化子
は必要ない。
E1345
インターフェイスは、引数と戻り値の型を含む `:def` を使用してメソッドを宣言でき
るが、本体や `:enddef` は使用しない。例: >
インターフェイス名は大文字で始めなければならない。 E1343
"Has" プリフィックスを使うことで、インターフェイス名であることが推測しやすくな
り、それが何を提供するものであるかのヒントを与えることができる。
インターフェイスは Vim9 script ファイルでのみ定義できる。 E1342
インターフェイスは他のインターフェイスを "implement" することはできないが、他
のインターフェイスを "extend" することはできる。 E1381
null オブジェクト ~
変数がオブジェクトの型を持つように宣言されているが初期化されていない場合、値は
null になる。この null オブジェクトを使用しようとすると、Vim はどのクラスを使
用することになっていたのかが分からないことがよくある。この場合、Vim は変数名が
正しいかどうかをチェックできず、変数名が無効な場合でも "Using a null object"
エラーが発生する。 E1360 E1362
デフォルトコンストラクタ ~
default-constructor
new() メソッドを使用せずにクラスを定義した場合は、自動的に new() メソッドが定
義される。このデフォルトコンストラクタには、指定された順序ですべてのオブジェク
ト変数の引数が含まれる。したがって、クラスが次のような場合: >
デフォルトコンストラクタは次のようになる: >
"= v:none" のデフォルト値では、引数はオプションとなる。したがって、引数なしで
`new()` を呼び出すこともできる。割り当ては行われず、オブジェクト変数のデフォル
ト値が使用される。これは、デフォルト値を使用した、より便利な例である: >
コンストラクタに必須の引数を持たせたい場合は、それを自分で記述する必要がああ
る。例えば、上記の AutoNew クラスで名前を取得する必要がある場合は、次のように
コンストラクタを定義できる: >
デフォルトの new() メソッドを使用する場合、クラス内のオブジェクト変数の順序を
後で変更すると、デフォルトの new() メソッドの呼び出し元もすべて変更する必要が
ある。これを回避するために、new() メソッドを引数なしで明示的に定義することがで
きる。
E1328
Note ここでは "v:none" 以外のデフォルト値を使用できないことに注意。オブジェク
ト変数を初期化したい場合は、それらが宣言されている場所で実行すること。この方法
では、デフォルト値を 1 箇所で確認するだけで済む。
すべてのオブジェクト変数は、アクセス保護されたものを含め、デフォルトのコンスト
ラクタで使用されます。
クラスが別のクラスを拡張する場合、そのクラスのオブジェクト変数が最初に来る。
複数のコンストラクタ ~
multiple-constructors
通常、クラスには new() コンストラクタが 1 つだけある。コンストラクタが同じ引数
を使用して呼び出されることが多い場合は、それらの引数を 2 番目のコンストラクタ
メソッドに入れることでコードを簡素化できる。例えば、黒という色をよく使う場合
は、次のようにする: >
毎回色を繰り返す代わりに、それを含むコンストラクタを追加できる: >
Note メソッド名は "new" で始まる必要があることに注意。"new()" というメソッドが
ない場合は、他のコンストラクタメソッドがあっても、デフォルトコンストラクタが追
加される。
クラス内のメソッドをコンパイルする ~
class-compile
:defcompile コマンドを使用すると、クラスで定義されているすべてのクラスメソッ
ドおよびオブジェクトメソッドをコンパイルできる: >
==============================================================================
7. 型定義 typealias Vim9-type :type
E1393 E1395 E1396 E1397 E1398
型定義は、型指定に名前をつけることである。これは "型エイリアス" とも呼ばれる。
型エイリアスは、組み込み型が使える場所であればどこでも使うことができる。例: >
E1394
型エイリアスは大文字で始める必要がある。既存の型のみエイリアスできる。
E1399
型エイリアスはスクリプトレベルでのみ作成でき、関数内では作成できない。型エイリ
アスはエクスポートし、スクリプト間で使用できる。
E1400 E1401 E1402 E1403 E1407
型エイリアスを式として使用することはできない。型エイリアスは代入の左辺では使用
できない。
型エイリアスの名前について、typename() 関数はエイリアスされた型を返す: >
==============================================================================
8. 列挙型 Vim9-enum :enum :endenum
enum E1418 E1419 E1420
列挙型は、値のリストの 1 つを持つことができる型である。例: >
enumvalue E1422
列挙値はコンマで区切られる。複数の列挙値を 1 行にリストできる。最後の列挙値の
後にコンマを付けてはならない。
列挙値には、列挙名に続いて値の名前を使用してアクセスする: >
列挙型はクラスとして扱われ、各列挙値は本質的にそのクラスのインスタンスである。
new() メソッドを使用した一般的なオブジェクトのインスタンス化とは異なり、この
方法では列挙型インスタンスを作成できない。
列挙型は Vim9 script ファイルでのみ定義できる。 E1414
列挙型は関数内で定義できない。
E1415
列挙名は大文字で始めなければならない。列挙型内の列挙値の名前は、大文字または小
文字で始めることができる。
E1416
列挙型はインターフェイスを実装できるが、クラスを拡張することはできない: >
enum-constructor
列挙型内の列挙値値オブジェクトは、new() メソッドを使用する他のオブジェクトと
同じように構築される。関数を呼び出すのと同じように、引数を列挙値名の後に指定す
ることで、列挙型コンストラクタに引数を渡すことができる。デフォルトのコンストラ
クタには引数がない。
E1417
列挙型には、クラス変数、クラスメソッド、オブジェクト変数、およびオブジェクトメ
ソッドを含めることができる。列挙型内のメソッドを :abstract メソッドにするこ
とはできない。
次の例は、オブジェクト変数とオブジェクトメソッドを含む列挙型を示している: >
E1421 E1423 E1424 E1425
列挙型とその値は不変である。数値型または文字列型として使用することはできない。
列挙値は変更可能なインスタンス変数を宣言できる。
enum-name
各列挙値オブジェクトには、列挙値の名前を含む "name" インスタンス変数がある。こ
れは読み取り専用の変数である。
enum-ordinal E1426
各列挙値には、0 から始まる序数が関連付けられている。列挙値の序数には、インスタ
ンス変数 "ordinal" を使用してアクセスできる。これは読み取り専用の変数である。
Note 列挙型内の列挙値の順序が変更されると、その序数の値も変更されることに注意。
enum-values
列挙型内のすべての値には、列挙型オブジェクトのリストである "values" クラス変数
を使用してアクセスできる。これは読み取り専用の変数である。
例: >
列挙型は、列挙値オブジェクトのクラス変数と、列挙値の名前と列挙値の序数のオブ
ジェクト変数を持つクラスである: >
上記の列挙型定義は、以下のクラス定義と同等である: >
==============================================================================
9. 論理的根拠
Vim9 クラスのほとんどの選択肢は、Java, TypeScript, Dart 等の人気のある最近開
発された言語から来ている。構文は、クラス全体を波括弧で囲む代わりに `endclass`
を使用する等、Vim script の動作に合わせて作成されている。
オブジェクト指向言語の一般的な構造の一部は、この種のプログラミングがまだ新し
かったかなり昔に選択されたもので、後に最適ではないことが判明した。この時点まで
に、これらの構造は広く使用されており、それらを変更するという選択肢はなかった。
Vim では、クラスがまったく新しいものであるため、さまざまな選択をする自由があ
る。「古い」言語が使用するものよりも、構文をより単純かつ一貫性のあるものにする
ことができる。あまり流用することなく、既存の言語から知っているものとほとんど同
じになるはずだ。
最近開発された言語の中には、Vim には必要のないあらゆる種類の派手な機能を追加し
ているものもある。しかし、中には私たちが活用したいと思うような素晴らしいアイデ
アもある。
したがって、我々は一般的な言語をベースにし、悪いアイデアに見えるものを削除し、
理解しやすいいくつかの優れた機能を追加することになる。
我々が決断を下す際に用いる主なルール:
- シンプルに。
- 奇をてらわず、他の言語がやっていることをほとんどやる。
- 過去の過ちを避ける。
- ほとんどのことを明白にし、スクリプト作成者が、物事がどのように機能するかを理
解するためにヘルプを参照する必要をなくす。
- 一貫性を保つ。
- 大規模なプロジェクトではなく、平均的な規模のプラグインを目指すこと。
コンストラクタに new() を使用する ~
多くの言語では、コンストラクタメソッドのクラス名が使用される。欠点は、多くの場
合、名前が長いことだ。また、クラス名を変更する場合は、すべてのコンストラクタメ
ソッドの名前を変更する必要がある。大したことではないが、それでもデメリットはあ
る。
TypeScript などの他の言語では、"constructor()" 等の特定の名前が使用される。そ
の方が良さそうである。しかしながら、"new" または "new()" を使用して新しいオブ
ジェクトを作成することと、"constructor()" との明らかな関係はない。
Vim9 script の場合 すべてのコンストラクタに同じメソッド名を使用するスクリプ
トは正しい選択であるように思えた。これを new() と呼び出すことで、呼び出し元と
呼び出されるメソッドの関係が明確になる。
コンストラクタのオーバーロードを行わない ~
Vim script では、旧来と Vim9 の両スクリプトとも、メソッドのオーバーロードは
ない。つまり、異なる種類の引数で同じメソッド名を使用することはできない。した
がって、new() コンストラクタも 1 つだけ存在する。
Vim9 script では引数が型付けされているため、オーバーロードをサポートできる。
ただし、これはすぐに複雑になる。new() 呼び出しを見て、いくつかの new() メソッ
ドのどれが実際に呼び出されているのかを知るためには、引数の型を調べなければなら
ない。そして、そのためにはかなりの量のコードを検査する必要がある。例えば、引数
の 1 つがメソッドの戻り値である場合、そのメソッドを見つけて、そのメソッドがど
のような型を返しているかを確認する必要がある。
代わりに、すべてのコンストラクタには、"new" で始まる異なる名前を付ける必要があ
る。こうすることで、異なる引数を持つ複数のコンストラクタが可能になり、同時にど
のコンストラクタが使用されているかを確認するのが非常に簡単になる。引数の型も適
切に確認できる。
メソッドのオーバーロードを行わない ~
コンストラクタの場合と同じ理由である。多くの場合、引数の型が明らかではないた
め、実際にどのメソッドが呼び出されているかを把握することが困難になる。メソッド
に別の名前を付けるだけで、型チェックによって意図したとおりに動作することが確認
される。これにより、実際には必要のないポリモーフィズムが排除される。
単一の継承とインターフェイス ~
一部の言語は多重継承をサポートしている。これは場合によっては便利だが、クラスの
動作規則が非常に複雑になる。
代わりに、インターフェイスを使用してサポートされる内容を宣言する方がはるかに簡
単である。非常に人気のある Java 言語はこの方法で実行しており、Vim にはこれで十
分だ。ここでは「シンプルに」ルールが適用される。
クラスがインターフェイスをサポートしていることを明示的に宣言すると、クラスの目
的が簡単に分かる。また、適切な型チェックを行うことも可能になる。インターフェイ
スが変更されると、その変更も変更されたかどうかがチェックされる。メソッドがたま
たま一致したからといって、クラスがインターフェイスを実装しているとみなすメカニ
ズムは脆弱で、分かりにくい問題を引き起こすため、やめておこう。
あらゆる場所で "this.variable" を使用する ~
さまざまなプログラミング言語のオブジェクト変数には、多くの場合、場所に応じてさ
まざまな方法でアクセスできる。曖昧さを避けるために "this." を前置しなければな
らないこともある。通常は "this." なしで宣言される。 これは非常に矛盾しており、
時に混乱を招く。
非常に一般的な問題は、コンストラクタで引数がオブジェクト変数と同じ名前を使用し
ていることである。その場合、これらの変数では "this." を本体に前置する必要があ
るが、他の変数ではその必要はなく、しばしば省略される。
このため、"this." を含む変数と含まない変数が混在することになり、一貫性が失われ
る。
Vim9 のクラスでは、宣言されたメソッドと変数には常にプリフィックス "this." が
使用される。シンプルで一貫性がある。クラス内部のコードを見る時にも、どの変数参
照がオブジェクト変数で、どれがそうでないかが直接明らかになる。
クラス変数の使用 ~
"static variable" を使ってクラス変数を宣言するのはよくあることで、特に目新しい
ことはない。Vim9 script では、クラス変数は名前によって直接アクセスできる。
スクリプトローカル変数をメソッド内で使用する方法とよく似ている。オブジェクト変
数には常に "this." を先頭につけてアクセスするので、それがどんな変数なのかもす
ぐに分かる。
TypeScript は、クラス内でもクラス変数名の前にクラス名を付ける。これには 2 つの
問題がある: クラス名はかなり長くなり、かなりのスペースが必要になることと、クラ
ス名が変更されたときに、これらの場所もすべて変更する必要があることだ。
オブジェクト変数とクラス変数の宣言 ~
主な選択肢は、変数宣言のように "var" を使うかどうかである。TypeScript では使用
していない: >
Vim のオブジェクト変数は以下のように宣言できる: >
一部のユーザーは、これは宣言というよりも代入のように見えると指摘した。"var" を
追加し、"this." を省略すると、次のように変更される: >
また、"static" キーワードを使ってクラス変数を宣言できるようにする必要もある。
ここでは "var" を省略することもできる: >
または、"static" の前にそれを使う: >
あるいは、"static" の後: >
この方が "static def Func()" に近い。
"var" を使うか使わないかの明確な好みはない。省略する主な理由は2つある:
1. TypeScript およびその他の一般的な言語では使用されていない。
2. ごちゃごちゃしない。
しかしながら、クラスやオブジェクトの変数やメソッドには、一般的な変数や関数の宣
言構文を再利用するのが一般的である。Vim9 でもメソッドには一般的な関数宣言構文
を再利用している。そのため、一貫性を保つために、これらの宣言では "var" を必要
とする。
"ClassName.new()" を使用してオブジェクトを構築する ~
多くの言語では、オブジェクトの作成に "new" 演算子を使用するが、コンストラクタ
はコマンドではなく引数を持つメソッドとして定義されているため、実際には少し奇妙
である。TypeScript にも "new" キーワードはあるが、メソッドは "constructor()"
と呼ばれており、両者の関係が分かりにくい。
Vim9 script では、コンストラクタメソッドは new() と呼ばれ、new() として呼び
出される。シンプルで簡単だ。他の言語では "new ClassName()" が使用されるが、
ClassName() メソッドはなく、ClassName というクラス内の別名のメソッドである。か
なり混乱する。
Vim9class のアクセスモード ~
vim9-access-modes
Vim9class でサポートされる変数アクセスモードとその意味は、
public-variable どこからでも読み書きできる
read-only-variable どこからでも読み取れ、クラスおよびサブクラス内
から書き込みできる
protected-variable クラスおよびサブクラス内から読み書きできる
メソッドのアクセスモードは似ているが、読み取り専用モードがない。
オブジェクト変数へのデフォルトの読み取りアクセス ~
ユーザーの中には、オブジェクト変数のアクセスルールが非対称であると指摘する人も
いる。まあ、それは意図的だ。値の変更は、値の読み取りとは大きく異なるアクション
である。読み取り操作には副作用はなく、オブジェクトに影響を与えることなく何度で
も実行できる。値を変更すると、多くの副作用が発生する可能性があり、他のオブジェ
クトに影響を与える波及効果も生じる可能性がある。
オブジェクト変数を追加する時、通常はこれについてあまり考えず、ただ型を正しくす
るだけである。通常、値は new() メソッドで設定される。したがって、ほとんどの場
合、デフォルトで読み取りアクセスのみが「機能」する。また、直接書き込むとエラー
が発生するため、本当にそれを許可する必要があるのか疑問に感じる。これは、間違い
の少ないコードを作成するのに役立つ。
アンダースコアを使用してオブジェクト変数を protected にする ~
オブジェクト変数がプライベートの場合、クラス内 (およびサブクラス内) でのみ読み
取りおよび変更でき、クラス外では使用できない。
オブジェクト変数が保護されている場合、その変数はクラス内 (およびサブクラス内)
でのみ読み取りおよび変更でき、クラス外では使用できない。アンダースコアを先頭に
付けることは、それを可視化する簡単な方法である。さまざまなプログラミング言語が
これを推奨している。
もし気が変わって、クラス外からオブジェクト変数にアクセスできるようにしたい場合
は、すべてのアンダースコアを削除する必要がある。
名前はクラス (およびサブクラス) の中にしか現れないため、見つけて変更するのは簡
単である。
逆の方法ははるかに困難である: クラス内のオブジェクト変数の先頭にアンダースコア
を追加して protected にすることは簡単にできるが、他の場所で使用されている場合
は追跡して変更する必要がある。"set" メソッド呼び出しにする必要がある場合があ
る。これは、アクセスを奪うには、そのアクセスが存在するすべての場所に対して作業
を行う必要があるという現実の問題を反映している。
別の方法としては、"public" がアクセスを別の方向に変更するのと同様に、
"protected" キーワードを使用することもできる。まぁ、これはキーワードの数を減ら
すためなのだが。
オブジェクト変数に private はない ~
一部の言語では、オブジェクト変数へのアクセスを制御するいくつかの方法が提供され
ている。
最もよく知られているのは "protected" だが、その意味は言語によって異なる。他に
は "shared", "private", "package" さらには "friend" もある。
これらのルールは人生をより困難にする。多くの人が同じ複雑なコードに取り組むプロ
ジェクトでは、ミスを犯しやすいので、これは当然のことだ。特に、クラスモデルに対
するリファクタリングやその他の変更の場合。
Vim script は、1 人または小規模なチームが作業するプラグインで使用されることが
想定されている。複雑なルールは事態をさらに複雑にするだけであり、ルールによって
提供される追加の安全性は実際には必要ない。アクセスの詳細は指定せず、単純なまま
にしておく。
==============================================================================
10. 後でやる
newSomething() コンストラクタは別のコンストラクタを呼び出すことができるか? 可
能な場合、制限は何?
考えていること:
- クラスのジェネリクス: `class <Tkey, Tentry>`
- 関数のジェネリクス: `def <Tkey> GetLast(key: Tkey)`
- ミックスイン: 役立つかどうかは分からない。簡単にするために省略する。
追加として良さそうなものをいくつか:
- テスト用: モック機構
提供すべき重要なクラスは "Promise" だ。Vim はシングルスレッドであるため、非同
期操作を接続することは、ユーザーをブロックすることなくプラグインが作業を実行で
きるようにする自然な方法である。これは、コールバックを呼び出し、タイムアウトや
エラーを処理するための統一された方法である。
vim:tw=78:ts=8:noet:ft=help:norl:
VIMリファレンスマニュアル by Bram Moolenaar
Vim9 のクラス、オブジェクト、インターフェイス、型定義と列挙型。 vim9-class
1. 概要 Vim9-class-overview
2. 単純なクラス Vim9-simple-class
3. クラス変数とメソッド Vim9-class-member
4. 抽象クラスを使う Vim9-abstract-class
5. インターフェイスを使う Vim9-using-interface
6. さらなるクラスの詳細 Vim9-class
7. 型定義 Vim9-type
8. 列挙型 Vim9-enum
9. 論理的根拠
10. 後でやる
==============================================================================
1. 概要 Vim9-class-overview
聞こえのいい呼び名は「オブジェクト指向プログラミング」である。このテーマに関す
る教材はたくさんある。ここでは、あなたが基本をすでに理解していることを前提とし
て、Vim9 script が提供するものを文書化する。この機能を効果的に使用する方法に
関する役立つヒントも加えている。Vim9 のクラスとオブジェクトは、旧来の Vim
script や従来の関数では使用できない。
基本アイテムはオブジェクト:
- オブジェクトは状態を保存する。これには、それぞれ値を持つことができる 1 つ以
上の変数が含まれる。
- オブジェクトは、その状態を使用したり操作したりする関数を提供する。これらの関
数は「オブジェクト上で」呼び出され、これが従来のデータとデータを操作するコー
ドの分離とは異なる点である。
- オブジェクトには、型付きのメンバー変数とメソッドを備えた明確に定義されたイン
ターフェイスを持つ。
- オブジェクトはクラスから生成され、すべてのオブジェクトは同じインターフェイス
を持つ。これは実行時に変更されることはなく、動的なものではない。
オブジェクトはクラスによってのみ生成さる。クラスは以下を提供する:
- コンストラクタとしての new() メソッド、これはクラスのオブジェクトを返す。
このメソッドはクラス名で呼び出される: MyClass.new()。
- クラスのすべてのオブジェクトで状態を共有する: クラス変数(クラスメンバ)。
- スーパークラスとサブクラス、継承によるクラスの階層。
インターフェイスはオブジェクトのプロパティを指定するために使用される:
- オブジェクトはいくつかのインターフェイスを実装する宣言ができる。
- 同じインターフェイスを実装した異なるオブジェクトを同じように扱うことができ
る。
クラス階層は単一の継承になる。それ以外の場合は、必要に応じてインターフェイスを
使用すること。
クラスのモデリング ~
好きな方法でクラスをモデル化できる。何を構築しているのかを念頭に置き、現実世界
をモデル化しようとしないこと。これは、特に教師が現実世界のオブジェクトを使用し
てクラス関係を説明するので、モデルが現実世界を反映している必要があると考えるか
もしれず、混乱を招く可能性がある。そうではない! モデルは目的に合ったものでな
ければならない。
多くの場合、合成(オブジェクトに他のオブジェクトが含まれる)の方が継承(オブジェ
クトが別のオブジェクトを拡張する)よりも優れていることを念頭に置くこと。最適な
クラスモデルを見つけるために時間を無駄にする必要はない。あるいは、正方形が長方
形なのか、それとも長方形が正方形なのかを議論するのは時間の無駄である。それは問
題ではない。
==============================================================================
2. 単純なクラス Vim9-simple-class
簡単な例から始めよう: テキストの位置を保存するクラスだ (これをより効率的に行う
方法は後述する): >
class TextPosition
var lnum: number
var col: number
var lnum: number
var col: number
def new(lnum: number, col: number)
this.lnum = lnum
this.col = col
enddef
this.lnum = lnum
this.col = col
enddef
def SetLnum(lnum: number)
this.lnum = lnum
enddef
this.lnum = lnum
enddef
def SetCol(col: number)
this.col = col
enddef
this.col = col
enddef
def SetPosition(lnum: number, col: number)
this.lnum = lnum
this.col = col
enddef
endclass
< object Objectthis.lnum = lnum
this.col = col
enddef
endclass
new() メソッドを使用して、このクラスからオブジェクトを作成できる: >
var pos = TextPosition.new(1, 1)
<オブジェクト変数 "lnum" と "col" には直接アクセスできる: >
echo $'The text position is ({pos.lnum}, {pos.col})'
< E1317 E1327 :this他のオブジェクト指向言語を使用したことがある場合は、Vim ではクラス定義内で宣言
されたオブジェクトのメンバが一貫してプリフィックス "this." で参照されることに
気づくだろう。これは、Java や TypeScript などの言語とは異なる。この命名規則に
より、オブジェクトのメンバを簡単に見つけることができる。また、変数にプリフィッ
クス "this." がない場合、それがオブジェクト変数ではないことが分かる。
E1411
クラス定義の外からオブジェクトのメソッドや変数にアクセスするには、オブジェクト
名の後にドットを付け、その後にメンバを続ける: >
pos.lnum
pos.SetCol(10)
<pos.SetCol(10)
E1405 E1406
クラス名を式として使用することはできない。クラス名は、代入の左辺では使用できな
い。
オブジェクト変数の書き込みアクセス ~
read-only-variable
では、オブジェクト変数を直接変更してみよう: >
pos.lnum = 9
< E1335これはエラーになる! デフォルトでは、オブジェクト変数は読み取ることはできても、
設定することはできないからである。TextPosition クラスがそのためのメソッドを提
供しているのはそのためだ: >
pos.SetLnum(9)
オブジェクト変数の読み取りは許可するが設定は許可しないのが、最も一般的で安全な
方法である。ほとんどの場合、値を使用しても問題はないが、値を設定すると注意が必
要な副作用が発生する可能性がある。この場合、SetLnum() メソッドは行番号が有効か
どうかをチェックし、エラーを返すか、最も近い有効な値を使用する。
:public public-variable E1331
副作用を気にせず、オブジェクト変数をいつでも変更できるようにしたい場合は、オブ
ジェクト変数をパブリックにすることができる: >
public var lnum: number
public var col: number
public var col: number
これで、SetLnum(), SetCol() および SetPosition() メソッドは必要なくなり、
"pos.lnum" を直接設定してもエラーが発生しなくなる。
E1326
存在しないオブジェクト変数を設定しようとすると、エラーが発生する: >
pos.other = 9
< E1326: Member not found on object "TextPosition": other ~E1376
クラス名を使用してオブジェクト変数にアクセスすることはできない。
Protected 変数 ~
protected-variable E1332 E1333
一方、オブジェクト変数をクラスまたはそのサブクラスの外部から直接読み取られたく
ない場合は、オブジェクト変数を保護することができる。これは、名前の前にアンダー
スコアを付けることによって行われる: >
var _lnum: number
var _col: number
var _col: number
次に、protected 変数の値を取得するメソッドを提供する必要がある。これらは一般に
ゲッターと呼ばれる。"Get" で始まる名前を使用することをお勧めする: >
def GetLnum(): number
return this._lnum
enddef
return this._lnum
enddef
def GetCol(): number
return this._col
enddef
return this._col
enddef
この例はあまり役に立たない。変数はパブリックでもよいだろう。値を確認するときに
便利である。例えば、行番号を総行数に制限する: >
def GetLnum(): number
if this._lnum > this._lineCount
return this._lineCount
endif
return this._lnum
enddef
<if this._lnum > this._lineCount
return this._lineCount
endif
return this._lnum
enddef
Protected メソッド ~
private-method E1366
オブジェクトメソッドを同じクラスの他のメソッドからのみアクセス可能にし、クラス
の外部からは使用しないようにしたい場合は、オブジェクトメソッドを protected に
できる。これを行うには、メソッド名の前にアンダースコアを付ける: >
class SomeClass
def _Foo(): number
return 10
enddef
def Bar(): number
return this._Foo()
enddef
endclass
<def _Foo(): number
return 10
enddef
def Bar(): number
return this._Foo()
enddef
endclass
クラス外の protected メソッドにアクセスすると、エラーが発生する (上記のクラス
を使用した場合): >
var a = SomeClass.new()
a._Foo()
<a._Foo()
new() メソッドの単純化 ~
new() constructor
default-constructor および multiple-constructors も参照。
多くのコンストラクタはオブジェクト変数の値を受け取る。したがって、次のようなパ
ターンがよく見られる: >
class SomeClass
var lnum: number
var col: number
var lnum: number
var col: number
def new(lnum: number, col: number)
this.lnum = lnum
this.col = col
enddef
endclass
<this.lnum = lnum
this.col = col
enddef
endclass
E1390
このテキストを記述する必要があるだけでなく、各変数の型も 2 回記述されている。
これは非常に一般的なので、new() を記述する短い方法が提供されている: >
def new(this.lnum, this.col)
enddef
enddef
セマンティクスは理解しやすい。"this." を含むオブジェクト変数名を new() の引数
として指定すると、new() 呼び出しで指定された値がそのオブジェクト変数に割り当て
られることを意味する。このメカニズムは Dart 言語から来ている。
new() を使用して変数をパブリックにするこの方法をまとめると、最初のクラス定義よ
りもはるかに短いクラス定義になる: >
class TextPosition
public var lnum: number
public var col: number
public var lnum: number
public var col: number
def new(this.lnum, this.col)
enddef
enddef
def SetPosition(lnum: number, col: number)
this.lnum = lnum
this.col = col
enddef
endclass
this.lnum = lnum
this.col = col
enddef
endclass
新しいオブジェクトを構築するシーケンスは次のとおり:
1. メモリが割り当てられ、クリアされる。すべての値はゼロ/偽/空でである。
2. 初期化子を持つ宣言されたオブジェクト変数ごとに、式が評価され、変数に割り当
てられる。これは、変数がクラスで宣言された順序で発生する。
3. "this.name" 形式の new() メソッドの引数が割り当てられる。
4. new() メソッドの本体が実行される。
クラスが親クラスを拡張する場合も、同じことが起こる。2 番目のステップでは、最初
に親クラスのオブジェクト変数が初期化される。親が "super()" または "new()" を呼
び出す必要はない。
E1365
new() メソッドを定義するときは、戻り値の型を指定しないこと。常にクラスのオブ
ジェクトを返す。
E1386
オブジェクトメソッドを呼び出すときは、メソッド名の前にオブジェクト変数名を付け
る必要がある。クラス名を使用してオブジェクトメソッドを呼び出すことはできない。
==============================================================================
3. クラス変数とメソッド Vim9-class-member
:static E1337 E1338 E1368
クラスメンバは "static" で宣言される。これらは、定義されているクラス内でプリ
フィックスなしの名前によって使用される: >
class OtherThing
var size: number
static var totalSize: number
var size: number
static var totalSize: number
def new(this.size)
totalSize += this.size
enddef
endclass
< E1340 E1341totalSize += this.size
enddef
endclass
名前はそのまま使用されるため、メソッドの引数名やローカル変数名による名前のシャ
ドウイングは許可されていない。
E1374 E1375 E1384 E1385
クラスメンバが定義されているクラスの外にあるクラスメンバにアクセスするには、ク
ラス名のプリフィックスを使用する必要がある。オブジェクトを使用してクラスメンバ
にアクセスすることはできない。
オブジェクトメンバと同様に、名前の最初の文字としてアンダースコアを使用すること
でアクセスを保護することができ、プリフィックス "public" を付けることでアクセス
を公開することができる: >
class OtherThing
static var total: number # 誰でも読み取り可、クラスのみ書き込
# み可
static var _sum: number # クラスのみ読み書き可
public static var result: number # 誰でも読み書き可
endclass
<static var total: number # 誰でも読み取り可、クラスのみ書き込
# み可
static var _sum: number # クラスのみ読み書き可
public static var result: number # 誰でも読み書き可
endclass
class-method
クラスメソッドも "static" で宣言される。クラス変数は使用できるが、オブジェクト
変数にはアクセスできず、"this" キーワードは使用できない。
>
class OtherThing
var size: number
static var totalSize: number
var size: number
static var totalSize: number
# 合計サイズをクリアし、以前の値を返す。
static def ClearTotalSize(): number
var prev = totalSize
totalSize = 0
return prev
enddef
endclass
static def ClearTotalSize(): number
var prev = totalSize
totalSize = 0
return prev
enddef
endclass
クラス内では、クラスメソッドを名前で直接呼び出すことができるが、クラスの外で
は、クラス名がプリフィックスされている必要がある:
`OtherThing.ClearTotalSize()`。また、クラス変数初期化子、ラムダ式、入れ子になっ
た関数といった特殊なコンテキストでは、パブリッククラスメソッドに名前プリフィッ
クスを使用しなければならない:
>
class OtherThing
static var name: string = OtherThing.GiveName()
static var name: string = OtherThing.GiveName()
static def GiveName(): string
def DoGiveName(): string
return OtherThing.NameAny()
enddef
def DoGiveName(): string
return OtherThing.NameAny()
enddef
return DoGiveName()
enddef
enddef
static def NameAny(): string
return "any"
enddef
endclass
<return "any"
enddef
endclass
オブジェクトメソッドと同様に、メソッド名の最初の文字としてアンダースコアを使用
することで、アクセスを protected にすることができる: >
class OtherThing
static def _Foo()
echo "Foo"
enddef
def Bar()
_Foo()
enddef
endclass
<static def _Foo()
echo "Foo"
enddef
def Bar()
_Foo()
enddef
endclass
E1370
Note コンストラクタは "static" として宣言できないことに注意。これらは staticの
ように呼び出されるが、オブジェクトメソッドとして実行される。これらは "this"に
アクセスできる。
拡張クラス内のスーパークラスのクラスメソッドとクラス変数にアクセスするには、定
義クラスの外部からの場合と同様に、クラス名のプリフィックスを使用する必要がある:
>
vim9script
class Vehicle
static var nextID: number = 1000
static def GetID(): number
nextID += 1
return nextID
enddef
endclass
class Car extends Vehicle
var myID: number
def new()
this.myID = Vehicle.GetID()
enddef
endclass
<class Vehicle
static var nextID: number = 1000
static def GetID(): number
nextID += 1
return nextID
enddef
endclass
class Car extends Vehicle
var myID: number
def new()
this.myID = Vehicle.GetID()
enddef
endclass
クラス変数とメソッドは子クラスに継承されない。子クラスは、スーパークラスと同じ
名前の静的変数またはメソッドを宣言できる。メンバが使用されるクラスに応じて、対
応するクラスのメンバが使用される。子クラスのクラスメンバの型は、スーパークラス
のクラスメンバの型とは異なる場合がある。
クラスメソッドまたはオブジェクトメソッド名の 2 重アンダースコア (__) プリフィッ
クスは、将来の使用のために予約されている。
object-final-variable E1409
:final キーワードを使用すると、クラス変数またはオブジェクト変数を定数にする
ことができる。例: >
class A
final v1 = [1, 2] # final オブジェクト変数
public final v2 = {x: 1} # final オブジェクト変数
static final v3 = 'abc' # final クラス変数
public static final v4 = 0z10 # final クラス変数
endclass
<final v1 = [1, 2] # final オブジェクト変数
public final v2 = {x: 1} # final オブジェクト変数
static final v3 = 'abc' # final クラス変数
public static final v4 = 0z10 # final クラス変数
endclass
final 変数はコンストラクタ関数からのみ変更できる。例: >
class A
final v1: list<number>
def new()
this.v1 = [1, 2]
enddef
endclass
var a = A.new()
echo a.v1
<final v1: list<number>
def new()
this.v1 = [1, 2]
enddef
endclass
var a = A.new()
echo a.v1
Note final 変数の値は変更できることに注意。例: >
class A
public final v1 = [1, 2]
endclass
var a = A.new()
a.v1[0] = 6 # OK
a.v1->add(3) # OK
a.v1 = [3, 4] # Error
<public final v1 = [1, 2]
endclass
var a = A.new()
a.v1[0] = 6 # OK
a.v1->add(3) # OK
a.v1 = [3, 4] # Error
E1408
final 変数はインターフェイスではサポートされていない。クラスまたはオブジェクト
のメソッドを final にすることはできない。
object-const-variable
:const キーワードを使用すると、クラス変数またはオブジェクト変数を作成し、そ
の値を定数にすることができる。例: >
class A
const v1 = [1, 2] # const オブジェクト変数
public const v2 = {x: 1} # const オブジェクト変数
static const v3 = 'abc' # const クラス変数
public static const v4 = 0z10 # const クラス変数
endclass
<const v1 = [1, 2] # const オブジェクト変数
public const v2 = {x: 1} # const オブジェクト変数
static const v3 = 'abc' # const クラス変数
public static const v4 = 0z10 # const クラス変数
endclass
const 変数はコンストラクタ関数からのみ変更できる。例: >
class A
const v1: list<number>
def new()
this.v1 = [1, 2]
enddef
endclass
var a = A.new()
echo a.v1
<const v1: list<number>
def new()
this.v1 = [1, 2]
enddef
endclass
var a = A.new()
echo a.v1
const 変数およびその値は変更できない。例: >
class A
public const v1 = [1, 2]
endclass
var a = A.new()
a.v1[0] = 6 # Error
a.v1->add(3) # Error
a.v1 = [3, 4] # Error
<public const v1 = [1, 2]
endclass
var a = A.new()
a.v1[0] = 6 # Error
a.v1->add(3) # Error
a.v1 = [3, 4] # Error
E1410
const 変数はインターフェイスではサポートされていない。クラスまたはオブジェクト
のメソッドを const にすることはできない。
==============================================================================
4. 抽象クラスを使う Vim9-abstract-class
抽象クラスは、少なくとも 1 つのサブクラスのベースを形成する。クラスモデルでは、
いくつかのクラスが共有できる同じプロパティを持っていることがよくあるが、これら
のプロパティを持つクラスにはオブジェクトを作成するための十分な状態がない。サブ
クラスは、オブジェクトの作成に使用する前に、抽象クラスを拡張し、不足している状
態やメソッドを追加する必要がある。
例えば、Shape クラスには色と太さを格納できる。Shape オブジェクトを作成すること
はできない。形状の種類に関する情報が欠落している。Shape クラスは、オブジェクト
を作成できる Square クラスと Triangle クラスのベースとして機能する。例: >
abstract class Shape
var color = Color.Black
var thickness = 10
endclass
var color = Color.Black
var thickness = 10
endclass
class Square extends Shape
var size: number
var size: number
def new(this.size)
enddef
endclass
enddef
endclass
class Triangle extends Shape
var base: number
var height: number
var base: number
var height: number
def new(this.base, this.height)
enddef
endclass
<enddef
endclass
抽象クラスは、new() メソッドがないことを除き、通常のクラスと同じ方法で定義され
る。 E1359
abstract-method E1371 E1372
抽象メソッドは、抽象クラス内でメソッド定義時に "abstract" プリフィックスを使用
することで定義できる: >
abstract class Shape
abstract def Draw()
endclass
<abstract def Draw()
endclass
抽象クラスの静的メソッドを抽象メソッドにすることはできない。
E1373
抽象クラスを拡張する非抽象クラスは、すべての抽象メソッドを実装する必要がある。
シグネチャ (引数、引数の型、戻り値の型) はまったく同じである必要がある。メソッ
ドの戻り値の型がクラスの場合、そのクラスまたはそのサブクラスの 1 つを拡張メソッ
ドで使用できる。
==============================================================================
5. インターフェイスを使う Vim9-using-interface
Shape, Square および Triangle を使用した上記の例は、オブジェクトの表面積を計算
するメソッドを追加するとさらに便利になる。そのために、数値を返す 1 つのメソッ
ド Surface() を指定する HasSurface というインターフェイスを作成する。この例は
上記の例を拡張したものである: >
abstract class Shape
var color = Color.Black
var thickness = 10
endclass
var color = Color.Black
var thickness = 10
endclass
interface HasSurface
def Surface(): number
endinterface
def Surface(): number
endinterface
class Square extends Shape implements HasSurface
var size: number
var size: number
def new(this.size)
enddef
enddef
def Surface(): number
return this.size * this.size
enddef
endclass
return this.size * this.size
enddef
endclass
class Triangle extends Shape implements HasSurface
var base: number
var height: number
var base: number
var height: number
def new(this.base, this.height)
enddef
enddef
def Surface(): number
return this.base * this.height / 2
enddef
endclass
<return this.base * this.height / 2
enddef
endclass
E1348 E1349 E1367 E1382 E1383
クラスがインターフェイスの実装を宣言した場合、インターフェイスで指定されたすべ
ての項目が同じ型でクラス内に出現する必要がある。
インターフェイス名は型として使用できる: >
var shapes: list<HasSurface> = [
Square.new(12),
Triangle.new(8, 15),
]
for shape in shapes
echo $'the surface is {shape.Surface()}'
endfor
<Square.new(12),
Triangle.new(8, 15),
]
for shape in shapes
echo $'the surface is {shape.Surface()}'
endfor
E1378 E1379 E1380 E1387
インターフェイスには、オブジェクトメソッドと読み取り専用のオブジェクト変数のみ
を含めることができる。インターフェイスには、読み書き可能または protected オブ
ジェクト変数、protected オブジェクトメソッド、クラス変数、およびクラスメソッド
を含めることはできまない。
インターフェイスは、"extends" を使用して別のインターフェイスを拡張できる。サブ
インターフェイスは、スーパーインターフェイスからすべてのインスタンス変数とメ
ソッドを継承する。
==============================================================================
6. さらなるクラスの詳細 Vim9-class Class class
クラスの定義 ~
:class :endclass :abstract
クラスは `:class` と `:endclass` の間で定義される。クラス全体は 1 つのスクリプ
トファイルで定義される。後からクラスに追加することはできない。
クラスは Vim9 script ファイル内でのみ定義できる。 E1316
関数内でクラスを定義することはできない。 E1429
スクリプトファイル内に複数のクラスを定義することは可能である。しかし、通常はメ
インクラスを 1 つだけエクスポートする方が良い。型、列挙型、ヘルパークラスを定
義すると便利である。
`:abstract` キーワードをプリフィックスとして付けたり、`:export` を使用したりで
きる。これにより、次のようなバリエーションが得られる: >
class ClassName
endclass
endclass
export class ClassName
endclass
endclass
abstract class ClassName
endclass
endclass
export abstract class ClassName
endclass
<endclass
E1314
クラス名は CamelCased にする必要がある。そして、大文字で始める必要がある。これ
により、組み込み型との衝突が回避される。
E1315
クラス名の後に、これらのオプションの項目を使用できる。それぞれ 1 回のみ出現で
きる。これらは任意の順序で出現できるが、この順序が推奨される: >
extends ClassName
implements InterfaceName, OtherInterface
specifies SomeInterface
< E1355 E1369implements InterfaceName, OtherInterface
specifies SomeInterface
各変数とメソッドの名前は 1 回だけ使用できる。同じ名前で異なる型の引数を持つメ
ソッドを定義することはできない。パブリックと protected のメンバー変数を同じ名
前で使用することはできない。スーパークラスで使用したオブジェクト変数名を子クラ
スで再利用することはできない。
オブジェクト変数の初期化 ~
変数の型がクラスで明示的に指定されていない場合、クラス定義時に "any" に設定さ
れる。オブジェクトがクラスからインスタンス化されると、変数の型が設定される。
次の予約キーワード名は、オブジェクトまたはクラス変数名として使用できない:
"super", "this", "true", "false", "null", "null_blob", "null_dict",
"null_function", "null_list", "null_partial", "null_string", "null_channel" お
よび "null_job"。
クラスを拡張する ~
extends
クラスは他のクラスを拡張できる。 E1352 E1353 E1354
基本的な考え方は、既存のクラスの上に構築し、それにプロパティを追加することだ。
拡張されたクラスは "base class" (基底クラス) または "super class" (スーパーク
ラス) と呼ばれる。新しいクラスは "child class" (子クラス) と呼ばれる。
基底クラスのオブジェクト変数はすべて子クラスに引き継がれる。(他の言語とは異な
り) それらをオーバーライドすることはできない。
E1356 E1357 E1358
基底クラスのオブジェクトメソッドは無効にすることができる。署名 (引数、引数の
型、戻り値の型) はまったく同じである必要がある。メソッドの戻り値の型がクラスの
場合、そのクラスまたはそのサブクラスの 1 つを拡張メソッドで使用できる。基底ク
ラスのメソッドは、先頭に "super." を付けることで呼び出すことができる。
E1377
子クラスのメソッド (パブリックまたは protected) のアクセスレベルは、スーパーク
ラスと同じである必要がある。
基底クラスの他のオブジェクトメソッドは、子クラスによって引き継がれる。
"new" で始まるメソッドを含むクラスメソッドは、オブジェクトメソッドと同様に無効
にすることができる。基底クラスのメソッドは、クラス名 (クラスメソッドの場合) ま
たは "super." のプリフィックスを付けることで呼び出すことができる。
他の言語とは異なり、基底クラスのコンストラクタを呼び出す必要はない。実際に呼び
出すことはできない。基底クラスからの初期化を子クラスでも行う必要がある場合は、
それをオブジェクトメソッドに入れて、すべての constructor() からそのメソッドを
呼び出す。
基底クラスで new() メソッドが指定されていない場合は、自動的に new() メソッドが
作成される。このメソッドは子クラスには引き継がれない。子クラスは独自の new()
メソッドを定義できるが、メソッドがなければ、new() メソッドが自動的に追加され
る。
インターフェイスを実装するクラス ~
implements E1346 E1347 E1389
クラスは 1 つ以上のインターフェイスを実装できる。"implements" キーワードは 1 回
のみ使用できる E1350 。複数のインターフェイスをコンマで区切って指定できる。
各インターフェイス名は 1 回だけ出現できる。 E1351
インターフェイスを定義するクラス ~
specifies
クラスは、名前付きインターフェイスを使用して、そのインターフェイス、オブジェク
ト変数およびメソッドを宣言できる。これにより、多くの言語、特に Java で頻繁に行
われる、インターフェイスを個別に指定する必要がなくなった。
クラス内の項目 ~
E1318 E1325 E1388
クラス内では、`:class` と `:endclass` の間に、次の項目を含めることができる:
- オブジェクト変数宣言: >
var _protectedVariableName: memberType
var readonlyVariableName: memberType
public var readwriteVariableName: memberType
- クラス変数宣言: >var readonlyVariableName: memberType
public var readwriteVariableName: memberType
static var _protectedClassVariableName: memberType
static var readonlyClassVariableName: memberType
public static var readwriteClassVariableName: memberType
- コンストラクタメソッド: >static var readonlyClassVariableName: memberType
public static var readwriteClassVariableName: memberType
def new(arguments)
def newName(arguments)
- クラスメソッド: >def newName(arguments)
static def SomeMethod(arguments)
static def _ProtectedMethod(arguments)
- オブジェクトメソッド: >static def _ProtectedMethod(arguments)
def SomeMethod(arguments)
def _ProtectedMethod(arguments)
def _ProtectedMethod(arguments)
オブジェクト変数の場合は、型を指定する必要がある。最善の方法は、": {type}" を
使用してこれを明示的に行うことである。単純な型の場合、"= 123" などの初期化子を
使用することもでき、Vim はその型が数値であると認識する。より複雑な型や、型が不
完全になる場合には、これを行わないこと。
例: >
var nameList = []
これはリストを指定するが、項目タイプは不明である。より良い使い方: > var nameList: list<string>
初期化は必要ない。デフォルトではリストは空である。E1330
"void", "null" および "v:none" 等、一部の型は使用できない。
組み込みオブジェクトメソッド ~
builtin-object-methods
empty()、len() および string() などの一部の組み込み関数はオブジェクトで
使用できる。オブジェクトは、これらの組み込み関数と同じ名前のメソッドを実装し
て、オブジェクト固有の値を返すことができる。
E1412
以下の組み込みメソッドがサポートされている:
object-empty()
empty() empty() 関数によって呼び出され、オブジェクトが空かどうかを確認
する。このメソッドが存在しない場合は、true が返される。このメソッ
ドは引数を受け入れず、真偽値を返す必要がある。
object-len()
len() len() 関数によって呼び出され、オブジェクトの長さを返す。このメ
ソッドがクラスにない場合はエラーが発生し、ゼロが返される。このメ
ソッドは引数を受け入れず、数値を返す必要がある。
object-string()
string() string() 関数によって呼び出され、オブジェクトのテキスト表現を取
得する。オブジェクトに対する :echo コマンドでも使用される。この
メソッドがクラスにない場合は、組み込みのデフォルトのテキスト表現
が使用される。このメソッドは引数を受け入れず、文字列を返す必要が
ある。
E1413
クラスメソッドを組み込みメソッドとして使用することはできない。
インターフェイスの定義 ~
Interface :interface :endinterface
インターフェイスは `:interface` と `:endinterface` の間で定義される。プリフィッ
クスとして `:export` を付けることができる: >
interface InterfaceName
endinterface
endinterface
export interface InterfaceName
endinterface
< E1344endinterface
インターフェイスは、クラスと同じようにオブジェクト変数を宣言できるが、初期化子
は必要ない。
E1345
インターフェイスは、引数と戻り値の型を含む `:def` を使用してメソッドを宣言でき
るが、本体や `:enddef` は使用しない。例: >
interface HasSurface
var size: number
def Surface(): number
endinterface
var size: number
def Surface(): number
endinterface
インターフェイス名は大文字で始めなければならない。 E1343
"Has" プリフィックスを使うことで、インターフェイス名であることが推測しやすくな
り、それが何を提供するものであるかのヒントを与えることができる。
インターフェイスは Vim9 script ファイルでのみ定義できる。 E1342
インターフェイスは他のインターフェイスを "implement" することはできないが、他
のインターフェイスを "extend" することはできる。 E1381
null オブジェクト ~
変数がオブジェクトの型を持つように宣言されているが初期化されていない場合、値は
null になる。この null オブジェクトを使用しようとすると、Vim はどのクラスを使
用することになっていたのかが分からないことがよくある。この場合、Vim は変数名が
正しいかどうかをチェックできず、変数名が無効な場合でも "Using a null object"
エラーが発生する。 E1360 E1362
デフォルトコンストラクタ ~
default-constructor
new() メソッドを使用せずにクラスを定義した場合は、自動的に new() メソッドが定
義される。このデフォルトコンストラクタには、指定された順序ですべてのオブジェク
ト変数の引数が含まれる。したがって、クラスが次のような場合: >
class AutoNew
var name: string
var age: number
var gender: Gender
endclass
var name: string
var age: number
var gender: Gender
endclass
デフォルトコンストラクタは次のようになる: >
def new(this.name = v:none, this.age = v:none, this.gender = v:none)
enddef
enddef
"= v:none" のデフォルト値では、引数はオプションとなる。したがって、引数なしで
`new()` を呼び出すこともできる。割り当ては行われず、オブジェクト変数のデフォル
ト値が使用される。これは、デフォルト値を使用した、より便利な例である: >
class TextPosition
var lnum: number = 1
var col: number = 1
endclass
var lnum: number = 1
var col: number = 1
endclass
コンストラクタに必須の引数を持たせたい場合は、それを自分で記述する必要がああ
る。例えば、上記の AutoNew クラスで名前を取得する必要がある場合は、次のように
コンストラクタを定義できる: >
def new(this.name, this.age = v:none, this.gender = v:none)
enddef
<enddef
デフォルトの new() メソッドを使用する場合、クラス内のオブジェクト変数の順序を
後で変更すると、デフォルトの new() メソッドの呼び出し元もすべて変更する必要が
ある。これを回避するために、new() メソッドを引数なしで明示的に定義することがで
きる。
E1328
Note ここでは "v:none" 以外のデフォルト値を使用できないことに注意。オブジェク
ト変数を初期化したい場合は、それらが宣言されている場所で実行すること。この方法
では、デフォルト値を 1 箇所で確認するだけで済む。
すべてのオブジェクト変数は、アクセス保護されたものを含め、デフォルトのコンスト
ラクタで使用されます。
クラスが別のクラスを拡張する場合、そのクラスのオブジェクト変数が最初に来る。
複数のコンストラクタ ~
multiple-constructors
通常、クラスには new() コンストラクタが 1 つだけある。コンストラクタが同じ引数
を使用して呼び出されることが多い場合は、それらの引数を 2 番目のコンストラクタ
メソッドに入れることでコードを簡素化できる。例えば、黒という色をよく使う場合
は、次のようにする: >
def new(this.garment, this.color, this.size)
enddef
...
var pants = new(Garment.pants, Color.black, "XL")
var shirt = new(Garment.shirt, Color.black, "XL")
var shoes = new(Garment.shoes, Color.black, "45")
enddef
...
var pants = new(Garment.pants, Color.black, "XL")
var shirt = new(Garment.shirt, Color.black, "XL")
var shoes = new(Garment.shoes, Color.black, "45")
毎回色を繰り返す代わりに、それを含むコンストラクタを追加できる: >
def newBlack(this.garment, this.size)
this.color = Color.black
enddef
...
var pants = newBlack(Garment.pants, "XL")
var shirt = newBlack(Garment.shirt, "XL")
var shoes = newBlack(Garment.shoes, "9.5")
this.color = Color.black
enddef
...
var pants = newBlack(Garment.pants, "XL")
var shirt = newBlack(Garment.shirt, "XL")
var shoes = newBlack(Garment.shoes, "9.5")
Note メソッド名は "new" で始まる必要があることに注意。"new()" というメソッドが
ない場合は、他のコンストラクタメソッドがあっても、デフォルトコンストラクタが追
加される。
クラス内のメソッドをコンパイルする ~
class-compile
:defcompile コマンドを使用すると、クラスで定義されているすべてのクラスメソッ
ドおよびオブジェクトメソッドをコンパイルできる: >
defcompile MyClass # クラス "MyClass" をコンパイルする
defcompile # 現在のスクリプト内のクラス群をコンパイルする
<defcompile # 現在のスクリプト内のクラス群をコンパイルする
==============================================================================
7. 型定義 typealias Vim9-type :type
E1393 E1395 E1396 E1397 E1398
型定義は、型指定に名前をつけることである。これは "型エイリアス" とも呼ばれる。
型エイリアスは、組み込み型が使える場所であればどこでも使うことができる。例: >
type ListOfStrings = list<string>
var s: ListOfStrings = ['a', 'b']
var s: ListOfStrings = ['a', 'b']
def ProcessStr(str: ListOfStrings): ListOfStrings
return str
enddef
echo ProcessStr(s)
<return str
enddef
echo ProcessStr(s)
E1394
型エイリアスは大文字で始める必要がある。既存の型のみエイリアスできる。
E1399
型エイリアスはスクリプトレベルでのみ作成でき、関数内では作成できない。型エイリ
アスはエクスポートし、スクリプト間で使用できる。
E1400 E1401 E1402 E1403 E1407
型エイリアスを式として使用することはできない。型エイリアスは代入の左辺では使用
できない。
型エイリアスの名前について、typename() 関数はエイリアスされた型を返す: >
type ListOfStudents = list<dict<any>>
echo typename(ListOfStudents)
typealias<list<dict<any>>>
<echo typename(ListOfStudents)
typealias<list<dict<any>>>
==============================================================================
8. 列挙型 Vim9-enum :enum :endenum
enum E1418 E1419 E1420
列挙型は、値のリストの 1 つを持つことができる型である。例: >
:enum Color
White,
Red,
Green, Blue, Black
:endenum
<White,
Red,
Green, Blue, Black
:endenum
enumvalue E1422
列挙値はコンマで区切られる。複数の列挙値を 1 行にリストできる。最後の列挙値の
後にコンマを付けてはならない。
列挙値には、列挙名に続いて値の名前を使用してアクセスする: >
var a: Color = Color.Blue
<列挙型はクラスとして扱われ、各列挙値は本質的にそのクラスのインスタンスである。
new() メソッドを使用した一般的なオブジェクトのインスタンス化とは異なり、この
方法では列挙型インスタンスを作成できない。
列挙型は Vim9 script ファイルでのみ定義できる。 E1414
列挙型は関数内で定義できない。
E1415
列挙名は大文字で始めなければならない。列挙型内の列挙値の名前は、大文字または小
文字で始めることができる。
E1416
列挙型はインターフェイスを実装できるが、クラスを拡張することはできない: >
enum MyEnum implements MyIntf
Value1,
Value2
Value1,
Value2
def SomeMethod()
enddef
endenum
<enddef
endenum
enum-constructor
列挙型内の列挙値値オブジェクトは、new() メソッドを使用する他のオブジェクトと
同じように構築される。関数を呼び出すのと同じように、引数を列挙値名の後に指定す
ることで、列挙型コンストラクタに引数を渡すことができる。デフォルトのコンストラ
クタには引数がない。
E1417
列挙型には、クラス変数、クラスメソッド、オブジェクト変数、およびオブジェクトメ
ソッドを含めることができる。列挙型内のメソッドを :abstract メソッドにするこ
とはできない。
次の例は、オブジェクト変数とオブジェクトメソッドを含む列挙型を示している: >
vim9script
enum Planet
Earth(1, false),
Jupiter(95, true),
Saturn(146, true)
enum Planet
Earth(1, false),
Jupiter(95, true),
Saturn(146, true)
var moons: number
var has_rings: bool
def GetMoons(): number
return this.moons
enddef
endenum
echo Planet.Jupiter.GetMoons()
echo Planet.Earth.has_rings
<var has_rings: bool
def GetMoons(): number
return this.moons
enddef
endenum
echo Planet.Jupiter.GetMoons()
echo Planet.Earth.has_rings
E1421 E1423 E1424 E1425
列挙型とその値は不変である。数値型または文字列型として使用することはできない。
列挙値は変更可能なインスタンス変数を宣言できる。
enum-name
各列挙値オブジェクトには、列挙値の名前を含む "name" インスタンス変数がある。こ
れは読み取り専用の変数である。
enum-ordinal E1426
各列挙値には、0 から始まる序数が関連付けられている。列挙値の序数には、インスタ
ンス変数 "ordinal" を使用してアクセスできる。これは読み取り専用の変数である。
Note 列挙型内の列挙値の順序が変更されると、その序数の値も変更されることに注意。
enum-values
列挙型内のすべての値には、列挙型オブジェクトのリストである "values" クラス変数
を使用してアクセスできる。これは読み取り専用の変数である。
例: >
enum Planet
Mercury,
Venus,
Earth
endenum
Mercury,
Venus,
Earth
endenum
echo Planet.Mercury
echo Planet.Venus.name
echo Planet.Venus.ordinal
for p in Planet.values
# ...
endfor
<echo Planet.Venus.name
echo Planet.Venus.ordinal
for p in Planet.values
# ...
endfor
列挙型は、列挙値オブジェクトのクラス変数と、列挙値の名前と列挙値の序数のオブ
ジェクト変数を持つクラスである: >
enum Planet
Mercury,
Venus
endenum
<Mercury,
Venus
endenum
上記の列挙型定義は、以下のクラス定義と同等である: >
class Planet
public static final Mercury: Planet = Planet.new('Mercury', 0)
public static final Venus: Planet = Planet.new('Venus', 1)
public static final Mercury: Planet = Planet.new('Mercury', 0)
public static final Venus: Planet = Planet.new('Venus', 1)
public static const values: list<Planet> = [Planet.Mercury, Planet.Venus]
public const name: string
public const ordinal: number
endclass
<public const ordinal: number
endclass
==============================================================================
9. 論理的根拠
Vim9 クラスのほとんどの選択肢は、Java, TypeScript, Dart 等の人気のある最近開
発された言語から来ている。構文は、クラス全体を波括弧で囲む代わりに `endclass`
を使用する等、Vim script の動作に合わせて作成されている。
オブジェクト指向言語の一般的な構造の一部は、この種のプログラミングがまだ新し
かったかなり昔に選択されたもので、後に最適ではないことが判明した。この時点まで
に、これらの構造は広く使用されており、それらを変更するという選択肢はなかった。
Vim では、クラスがまったく新しいものであるため、さまざまな選択をする自由があ
る。「古い」言語が使用するものよりも、構文をより単純かつ一貫性のあるものにする
ことができる。あまり流用することなく、既存の言語から知っているものとほとんど同
じになるはずだ。
最近開発された言語の中には、Vim には必要のないあらゆる種類の派手な機能を追加し
ているものもある。しかし、中には私たちが活用したいと思うような素晴らしいアイデ
アもある。
したがって、我々は一般的な言語をベースにし、悪いアイデアに見えるものを削除し、
理解しやすいいくつかの優れた機能を追加することになる。
我々が決断を下す際に用いる主なルール:
- シンプルに。
- 奇をてらわず、他の言語がやっていることをほとんどやる。
- 過去の過ちを避ける。
- ほとんどのことを明白にし、スクリプト作成者が、物事がどのように機能するかを理
解するためにヘルプを参照する必要をなくす。
- 一貫性を保つ。
- 大規模なプロジェクトではなく、平均的な規模のプラグインを目指すこと。
コンストラクタに new() を使用する ~
多くの言語では、コンストラクタメソッドのクラス名が使用される。欠点は、多くの場
合、名前が長いことだ。また、クラス名を変更する場合は、すべてのコンストラクタメ
ソッドの名前を変更する必要がある。大したことではないが、それでもデメリットはあ
る。
TypeScript などの他の言語では、"constructor()" 等の特定の名前が使用される。そ
の方が良さそうである。しかしながら、"new" または "new()" を使用して新しいオブ
ジェクトを作成することと、"constructor()" との明らかな関係はない。
Vim9 script の場合 すべてのコンストラクタに同じメソッド名を使用するスクリプ
トは正しい選択であるように思えた。これを new() と呼び出すことで、呼び出し元と
呼び出されるメソッドの関係が明確になる。
コンストラクタのオーバーロードを行わない ~
Vim script では、旧来と Vim9 の両スクリプトとも、メソッドのオーバーロードは
ない。つまり、異なる種類の引数で同じメソッド名を使用することはできない。した
がって、new() コンストラクタも 1 つだけ存在する。
Vim9 script では引数が型付けされているため、オーバーロードをサポートできる。
ただし、これはすぐに複雑になる。new() 呼び出しを見て、いくつかの new() メソッ
ドのどれが実際に呼び出されているのかを知るためには、引数の型を調べなければなら
ない。そして、そのためにはかなりの量のコードを検査する必要がある。例えば、引数
の 1 つがメソッドの戻り値である場合、そのメソッドを見つけて、そのメソッドがど
のような型を返しているかを確認する必要がある。
代わりに、すべてのコンストラクタには、"new" で始まる異なる名前を付ける必要があ
る。こうすることで、異なる引数を持つ複数のコンストラクタが可能になり、同時にど
のコンストラクタが使用されているかを確認するのが非常に簡単になる。引数の型も適
切に確認できる。
メソッドのオーバーロードを行わない ~
コンストラクタの場合と同じ理由である。多くの場合、引数の型が明らかではないた
め、実際にどのメソッドが呼び出されているかを把握することが困難になる。メソッド
に別の名前を付けるだけで、型チェックによって意図したとおりに動作することが確認
される。これにより、実際には必要のないポリモーフィズムが排除される。
単一の継承とインターフェイス ~
一部の言語は多重継承をサポートしている。これは場合によっては便利だが、クラスの
動作規則が非常に複雑になる。
代わりに、インターフェイスを使用してサポートされる内容を宣言する方がはるかに簡
単である。非常に人気のある Java 言語はこの方法で実行しており、Vim にはこれで十
分だ。ここでは「シンプルに」ルールが適用される。
クラスがインターフェイスをサポートしていることを明示的に宣言すると、クラスの目
的が簡単に分かる。また、適切な型チェックを行うことも可能になる。インターフェイ
スが変更されると、その変更も変更されたかどうかがチェックされる。メソッドがたま
たま一致したからといって、クラスがインターフェイスを実装しているとみなすメカニ
ズムは脆弱で、分かりにくい問題を引き起こすため、やめておこう。
あらゆる場所で "this.variable" を使用する ~
さまざまなプログラミング言語のオブジェクト変数には、多くの場合、場所に応じてさ
まざまな方法でアクセスできる。曖昧さを避けるために "this." を前置しなければな
らないこともある。通常は "this." なしで宣言される。 これは非常に矛盾しており、
時に混乱を招く。
非常に一般的な問題は、コンストラクタで引数がオブジェクト変数と同じ名前を使用し
ていることである。その場合、これらの変数では "this." を本体に前置する必要があ
るが、他の変数ではその必要はなく、しばしば省略される。
このため、"this." を含む変数と含まない変数が混在することになり、一貫性が失われ
る。
Vim9 のクラスでは、宣言されたメソッドと変数には常にプリフィックス "this." が
使用される。シンプルで一貫性がある。クラス内部のコードを見る時にも、どの変数参
照がオブジェクト変数で、どれがそうでないかが直接明らかになる。
クラス変数の使用 ~
"static variable" を使ってクラス変数を宣言するのはよくあることで、特に目新しい
ことはない。Vim9 script では、クラス変数は名前によって直接アクセスできる。
スクリプトローカル変数をメソッド内で使用する方法とよく似ている。オブジェクト変
数には常に "this." を先頭につけてアクセスするので、それがどんな変数なのかもす
ぐに分かる。
TypeScript は、クラス内でもクラス変数名の前にクラス名を付ける。これには 2 つの
問題がある: クラス名はかなり長くなり、かなりのスペースが必要になることと、クラ
ス名が変更されたときに、これらの場所もすべて変更する必要があることだ。
オブジェクト変数とクラス変数の宣言 ~
主な選択肢は、変数宣言のように "var" を使うかどうかである。TypeScript では使用
していない: >
class Point {
x: number;
y = 0;
}
x: number;
y = 0;
}
Vim のオブジェクト変数は以下のように宣言できる: >
class Point
this.x: number
this.y = 0
endclass
this.x: number
this.y = 0
endclass
一部のユーザーは、これは宣言というよりも代入のように見えると指摘した。"var" を
追加し、"this." を省略すると、次のように変更される: >
class Point
var x: number
var y = 0
endclass
var x: number
var y = 0
endclass
また、"static" キーワードを使ってクラス変数を宣言できるようにする必要もある。
ここでは "var" を省略することもできる: >
class Point
var x: number
static count = 0
endclass
var x: number
static count = 0
endclass
または、"static" の前にそれを使う: >
class Point
var x: number
var static count = 0
endclass
var x: number
var static count = 0
endclass
あるいは、"static" の後: >
class Point
var x: number
static var count = 0
endclass
var x: number
static var count = 0
endclass
この方が "static def Func()" に近い。
"var" を使うか使わないかの明確な好みはない。省略する主な理由は2つある:
1. TypeScript およびその他の一般的な言語では使用されていない。
2. ごちゃごちゃしない。
しかしながら、クラスやオブジェクトの変数やメソッドには、一般的な変数や関数の宣
言構文を再利用するのが一般的である。Vim9 でもメソッドには一般的な関数宣言構文
を再利用している。そのため、一貫性を保つために、これらの宣言では "var" を必要
とする。
"ClassName.new()" を使用してオブジェクトを構築する ~
多くの言語では、オブジェクトの作成に "new" 演算子を使用するが、コンストラクタ
はコマンドではなく引数を持つメソッドとして定義されているため、実際には少し奇妙
である。TypeScript にも "new" キーワードはあるが、メソッドは "constructor()"
と呼ばれており、両者の関係が分かりにくい。
Vim9 script では、コンストラクタメソッドは new() と呼ばれ、new() として呼び
出される。シンプルで簡単だ。他の言語では "new ClassName()" が使用されるが、
ClassName() メソッドはなく、ClassName というクラス内の別名のメソッドである。か
なり混乱する。
Vim9class のアクセスモード ~
vim9-access-modes
Vim9class でサポートされる変数アクセスモードとその意味は、
public-variable どこからでも読み書きできる
read-only-variable どこからでも読み取れ、クラスおよびサブクラス内
から書き込みできる
protected-variable クラスおよびサブクラス内から読み書きできる
メソッドのアクセスモードは似ているが、読み取り専用モードがない。
オブジェクト変数へのデフォルトの読み取りアクセス ~
ユーザーの中には、オブジェクト変数のアクセスルールが非対称であると指摘する人も
いる。まあ、それは意図的だ。値の変更は、値の読み取りとは大きく異なるアクション
である。読み取り操作には副作用はなく、オブジェクトに影響を与えることなく何度で
も実行できる。値を変更すると、多くの副作用が発生する可能性があり、他のオブジェ
クトに影響を与える波及効果も生じる可能性がある。
オブジェクト変数を追加する時、通常はこれについてあまり考えず、ただ型を正しくす
るだけである。通常、値は new() メソッドで設定される。したがって、ほとんどの場
合、デフォルトで読み取りアクセスのみが「機能」する。また、直接書き込むとエラー
が発生するため、本当にそれを許可する必要があるのか疑問に感じる。これは、間違い
の少ないコードを作成するのに役立つ。
アンダースコアを使用してオブジェクト変数を protected にする ~
オブジェクト変数がプライベートの場合、クラス内 (およびサブクラス内) でのみ読み
取りおよび変更でき、クラス外では使用できない。
オブジェクト変数が保護されている場合、その変数はクラス内 (およびサブクラス内)
でのみ読み取りおよび変更でき、クラス外では使用できない。アンダースコアを先頭に
付けることは、それを可視化する簡単な方法である。さまざまなプログラミング言語が
これを推奨している。
もし気が変わって、クラス外からオブジェクト変数にアクセスできるようにしたい場合
は、すべてのアンダースコアを削除する必要がある。
名前はクラス (およびサブクラス) の中にしか現れないため、見つけて変更するのは簡
単である。
逆の方法ははるかに困難である: クラス内のオブジェクト変数の先頭にアンダースコア
を追加して protected にすることは簡単にできるが、他の場所で使用されている場合
は追跡して変更する必要がある。"set" メソッド呼び出しにする必要がある場合があ
る。これは、アクセスを奪うには、そのアクセスが存在するすべての場所に対して作業
を行う必要があるという現実の問題を反映している。
別の方法としては、"public" がアクセスを別の方向に変更するのと同様に、
"protected" キーワードを使用することもできる。まぁ、これはキーワードの数を減ら
すためなのだが。
オブジェクト変数に private はない ~
一部の言語では、オブジェクト変数へのアクセスを制御するいくつかの方法が提供され
ている。
最もよく知られているのは "protected" だが、その意味は言語によって異なる。他に
は "shared", "private", "package" さらには "friend" もある。
これらのルールは人生をより困難にする。多くの人が同じ複雑なコードに取り組むプロ
ジェクトでは、ミスを犯しやすいので、これは当然のことだ。特に、クラスモデルに対
するリファクタリングやその他の変更の場合。
Vim script は、1 人または小規模なチームが作業するプラグインで使用されることが
想定されている。複雑なルールは事態をさらに複雑にするだけであり、ルールによって
提供される追加の安全性は実際には必要ない。アクセスの詳細は指定せず、単純なまま
にしておく。
==============================================================================
10. 後でやる
newSomething() コンストラクタは別のコンストラクタを呼び出すことができるか? 可
能な場合、制限は何?
考えていること:
- クラスのジェネリクス: `class <Tkey, Tentry>`
- 関数のジェネリクス: `def <Tkey> GetLast(key: Tkey)`
- ミックスイン: 役立つかどうかは分からない。簡単にするために省略する。
追加として良さそうなものをいくつか:
- テスト用: モック機構
提供すべき重要なクラスは "Promise" だ。Vim はシングルスレッドであるため、非同
期操作を接続することは、ユーザーをブロックすることなくプラグインが作業を実行で
きるようにする自然な方法である。これは、コールバックを呼び出し、タイムアウトや
エラーを処理するための統一された方法である。
vim:tw=78:ts=8:noet:ft=help:norl: