runtime(doc): Vim9: Consistenly use class/object variable and class/object method in help (#13149)

Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Yegappan Lakshmanan
2023-09-24 14:36:17 -07:00
committed by GitHub
parent abc808112e
commit c3b315f496

View File

@ -37,7 +37,7 @@ The basic item is an object:
functions are invoked "on the object", which is what sets it apart from the functions are invoked "on the object", which is what sets it apart from the
traditional separation of data and code that manipulates the data. traditional separation of data and code that manipulates the data.
- An object has a well defined interface, with typed member variables and - An object has a well defined interface, with typed member variables and
member functions. methods.
- Objects are created from a class and all objects have the same interface. - Objects are created from a class and all objects have the same interface.
This does not change at runtime, it is not dynamic. This does not change at runtime, it is not dynamic.
@ -103,7 +103,7 @@ You can create an object from this class with the new() method: >
var pos = TextPosition.new(1, 1) var pos = TextPosition.new(1, 1)
The object members "lnum" and "col" can be accessed directly: > The object variables "lnum" and "col" can be accessed directly: >
echo $'The text position is ({pos.lnum}, {pos.col})' echo $'The text position is ({pos.lnum}, {pos.col})'
< *E1317* *E1327* < *E1317* *E1327*
@ -111,28 +111,30 @@ If you have been using other object-oriented languages you will notice that
in Vim the object members are consistently referred to with the "this." in Vim the object members are consistently referred to with the "this."
prefix. This is different from languages like Java and TypeScript. The prefix. This is different from languages like Java and TypeScript. The
naming convention makes the object members easy to spot. Also, when a naming convention makes the object members easy to spot. Also, when a
variable does not have the "this." prefix you know it is not an object member. variable does not have the "this." prefix you know it is not an object
variable.
Member write access ~ Object variable write access ~
Now try to change an object member directly: > Now try to change an object variable directly: >
pos.lnum = 9 pos.lnum = 9
< *E1335* < *E1335*
This will give you an error! That is because by default object members can be This will give you an error! That is because by default object variables can
read but not set. That's why the TextPosition class provides a method for it: > be read but not set. That's why the TextPosition class provides a method for
it: >
pos.SetLnum(9) pos.SetLnum(9)
Allowing to read but not set an object member is the most common and safest Allowing to read but not set an object variable is the most common and safest
way. Most often there is no problem using a value, while setting a value may way. Most often there is no problem using a value, while setting a value may
have side effects that need to be taken care of. In this case, the SetLnum() have side effects that need to be taken care of. In this case, the SetLnum()
method could check if the line number is valid and either give an error or use method could check if the line number is valid and either give an error or use
the closest valid value. the closest valid value.
*:public* *E1331* *:public* *E1331*
If you don't care about side effects and want to allow the object member to be If you don't care about side effects and want to allow the object variable to
changed at any time, you can make it public: > be changed at any time, you can make it public: >
public this.lnum: number public this.lnum: number
public this.col: number public this.col: number
@ -140,23 +142,23 @@ changed at any time, you can make it public: >
Now you don't need the SetLnum(), SetCol() and SetPosition() methods, setting Now you don't need the SetLnum(), SetCol() and SetPosition() methods, setting
"pos.lnum" directly above will no longer give an error. "pos.lnum" directly above will no longer give an error.
*E1326* *E1326*
If you try to set an object member that doesn't exist you get an error: > If you try to set an object variable that doesn't exist you get an error: >
pos.other = 9 pos.other = 9
< E1326: Member not found on object "TextPosition": other ~ < E1326: Member not found on object "TextPosition": other ~
*E1376* *E1376*
A object member cannot be accessed using the class name. A object variable cannot be accessed using the class name.
Private members ~ Private variables ~
*E1332* *E1333* *E1332* *E1333*
On the other hand, if you do not want the object members to be read directly, On the other hand, if you do not want the object variables to be read directly,
you can make them private. This is done by prefixing an underscore to the you can make them private. This is done by prefixing an underscore to the
name: > name: >
this._lnum: number this._lnum: number
this._col number this._col number
Now you need to provide methods to get the value of the private members. Now you need to provide methods to get the value of the private variables.
These are commonly called getters. We recommend using a name that starts with These are commonly called getters. We recommend using a name that starts with
"Get": > "Get": >
@ -168,7 +170,7 @@ These are commonly called getters. We recommend using a name that starts with
return this._col return this._col
enddef enddef
This example isn't very useful, the members might as well have been public. This example isn't very useful, the variables might as well have been public.
It does become useful if you check the value. For example, restrict the line It does become useful if you check the value. For example, restrict the line
number to the total number of lines: > number to the total number of lines: >
@ -202,8 +204,8 @@ the above class): >
< <
Simplifying the new() method ~ Simplifying the new() method ~
Many constructors take values for the object members. Thus you very often see Many constructors take values for the object variables. Thus you very often
this pattern: > see this pattern: >
class SomeClass class SomeClass
this.lnum: number this.lnum: number
@ -215,19 +217,20 @@ this pattern: >
enddef enddef
endclass endclass
Not only is this text you need to write, it also has the type of each member Not only is this text you need to write, it also has the type of each
twice. Since this is so common a shorter way to write new() is provided: > variables twice. Since this is so common a shorter way to write new() is
provided: >
def new(this.lnum, this.col) def new(this.lnum, this.col)
enddef enddef
The semantics are easy to understand: Providing the object member name, The semantics are easy to understand: Providing the object variable name,
including "this.", as the argument to new() means the value provided in the including "this.", as the argument to new() means the value provided in the
new() call is assigned to that object member. This mechanism comes from the new() call is assigned to that object variable. This mechanism comes from the
Dart language. Dart language.
Putting together this way of using new() and making the members public results Putting together this way of using new() and making the variables public
in a much shorter class definition than what we started with: > results in a much shorter class definition than what we started with: >
class TextPosition class TextPosition
public this.lnum: number public this.lnum: number
@ -244,15 +247,15 @@ in a much shorter class definition than what we started with: >
The sequence of constructing a new object is: The sequence of constructing a new object is:
1. Memory is allocated and cleared. All values are zero/false/empty. 1. Memory is allocated and cleared. All values are zero/false/empty.
2. For each declared member that has an initializer, the expression is 2. For each declared object variable that has an initializer, the expression
evaluated and assigned to the member. This happens in the sequence the is evaluated and assigned to the variable. This happens in the sequence
members are declared in the class. the variables are declared in the class.
3. Arguments in the new() method in the "this.name" form are assigned. 3. Arguments in the new() method in the "this.name" form are assigned.
4. The body of the new() method is executed. 4. The body of the new() method is executed.
If the class extends a parent class, the same thing happens. In the second If the class extends a parent class, the same thing happens. In the second
step the members of the parent class are done first. There is no need to call step the object variables of the parent class are initialized first. There is
"super()" or "new()" on the parent. no need to call "super()" or "new()" on the parent.
*E1365* *E1365*
When defining the new() method the return type should not be specified. It When defining the new() method the return type should not be specified. It
@ -275,7 +278,7 @@ prefix in the class where they are defined: >
enddef enddef
endclass endclass
< *E1340* *E1341* < *E1340* *E1341*
Since the name is used as-is, shadowing the name by a function argument name Since the name is used as-is, shadowing the name by a method argument name
or local variable name is not allowed. or local variable name is not allowed.
*E1374* *E1375* *E1374* *E1375*
@ -394,7 +397,7 @@ does not have any new() method. *E1359*
*abstract-method* *E1371* *E1372* *abstract-method* *E1371* *E1372*
An abstract method can be defined in an abstract class by using the "abstract" An abstract method can be defined in an abstract class by using the "abstract"
prefix when defining the function: > prefix when defining the method: >
abstract class Shape abstract class Shape
abstract def Draw() abstract def Draw()
@ -510,17 +513,16 @@ once. They can appear in any order, although this order is recommended: >
implements InterfaceName, OtherInterface implements InterfaceName, OtherInterface
specifies SomeInterface specifies SomeInterface
< *E1355* *E1369* < *E1355* *E1369*
Each member and function name can be used only once. It is not possible to Each variable and method name can be used only once. It is not possible to
define a function with the same name and different type of arguments. It is define a method with the same name and different type of arguments. It is not
not possible to use a public and private member variable with the same name. possible to use a public and private member variable with the same name. A
A object variable name used in a super class cannot be reused in a child object variable name used in a super class cannot be reused in a child class.
class.
Member Initialization ~ Object Variable Initialization ~
If the type of a member is not explicitly specified in a class, then it is set If the type of a variable is not explicitly specified in a class, then it is
to "any" during class definition. When an object is instantiated from the set to "any" during class definition. When an object is instantiated from the
class, then the type of the member is set. class, then the type of the variable is set.
Extending a class ~ Extending a class ~
@ -531,7 +533,7 @@ The basic idea is to build on top of an existing class, add properties to it.
The extended class is called the "base class" or "super class". The new class The extended class is called the "base class" or "super class". The new class
is called the "child class". is called the "child class".
Object members from the base class are all taken over by the child class. It Object variables from the base class are all taken over by the child class. It
is not possible to override them (unlike some other languages). is not possible to override them (unlike some other languages).
*E1356* *E1357* *E1358* *E1356* *E1357* *E1358*
@ -545,19 +547,19 @@ the same as the super class.
Other object methods of the base class are taken over by the child class. Other object methods of the base class are taken over by the child class.
Class functions, including functions starting with "new", can be overruled, Class methods, including methods starting with "new", can be overruled, like
like with object methods. The function on the base class can be called by with object methods. The method on the base class can be called by prefixing
prefixing the name of the class (for class functions) or "super.". the name of the class (for class methods) or "super.".
Unlike other languages, the constructor of the base class does not need to be Unlike other languages, the constructor of the base class does not need to be
invoked. In fact, it cannot be invoked. If some initialization from the base invoked. In fact, it cannot be invoked. If some initialization from the base
class also needs to be done in a child class, put it in an object method and class also needs to be done in a child class, put it in an object method and
call that method from every constructor(). call that method from every constructor().
If the base class did not specify a new() function then one was automatically If the base class did not specify a new() method then one was automatically
created. This function will not be taken over by the child class. The child created. This method will not be taken over by the child class. The child
class can define its own new() function, or, if there isn't one, a new() class can define its own new() method, or, if there isn't one, a new() method
function will be added automatically. will be added automatically.
A class implementing an interface ~ A class implementing an interface ~
@ -569,7 +571,7 @@ commas. Each interface name can appear only once. *E1351*
A class defining an interface ~ A class defining an interface ~
*specifies* *specifies*
A class can declare its interface, the object members and methods, with a A class can declare its interface, the object variables and methods, with a
named interface. This avoids the need for separately specifying the named interface. This avoids the need for separately specifying the
interface, which is often done in many languages, especially Java. interface, which is often done in many languages, especially Java.
@ -577,14 +579,14 @@ interface, which is often done in many languages, especially Java.
Items in a class ~ Items in a class ~
*E1318* *E1325* *E1318* *E1325*
Inside a class, in between `:class` and `:endclass`, these items can appear: Inside a class, in between `:class` and `:endclass`, these items can appear:
- An object member declaration: > - An object variable declaration: >
this._privateMemberName: memberType this._privateVariableName: memberType
this.readonlyMemberName: memberType this.readonlyVariableName: memberType
public this.readwriteMemberName: memberType public this.readwriteVariableName: memberType
- A class member declaration: > - A class variable declaration: >
static this._privateMemberName: memberType static _privateClassVariableName: memberType
static this.readonlyMemberName: memberType static readonlyClassVariableName: memberType
static public this.readwriteMemberName: memberType static public readwriteClassVariableName: memberType
- A constructor method: > - A constructor method: >
def new(arguments) def new(arguments)
def newName(arguments) def newName(arguments)
@ -595,10 +597,11 @@ Inside a class, in between `:class` and `:endclass`, these items can appear:
def SomeMethod(arguments) def SomeMethod(arguments)
def _PrivateMethod(arguments) def _PrivateMethod(arguments)
For the object member the type must be specified. The best way is to do this For the object variable the type must be specified. The best way is to do
explicitly with ": {type}". For simple types you can also use an initializer, this explicitly with ": {type}". For simple types you can also use an
such as "= 123", and Vim will see that the type is a number. Avoid doing this initializer, such as "= 123", and Vim will see that the type is a number.
for more complex types and when the type will be incomplete. For example: > Avoid doing this for more complex types and when the type will be incomplete.
For example: >
this.nameList = [] this.nameList = []
This specifies a list, but the item type is unknown. Better use: > This specifies a list, but the item type is unknown. Better use: >
this.nameList: list<string> this.nameList: list<string>
@ -618,8 +621,8 @@ prefixed with `:export`: >
export interface InterfaceName export interface InterfaceName
endinterface endinterface
< *E1344* < *E1344*
An interface can declare object members, just like in a class but without any An interface can declare object variables, just like in a class but without
initializer. any initializer.
*E1345* *E1345*
An interface can declare methods with `:def`, including the arguments and An interface can declare methods with `:def`, including the arguments and
return type, but without the body and without `:enddef`. Example: > return type, but without the body and without `:enddef`. Example: >
@ -642,15 +645,15 @@ null object ~
When a variable is declared to have the type of an object, but it is not When a variable is declared to have the type of an object, but it is not
initialized, the value is null. When trying to use this null object Vim often initialized, the value is null. When trying to use this null object Vim often
does not know what class was supposed to be used. Vim then cannot check if does not know what class was supposed to be used. Vim then cannot check if
a member name is correct and you will get an "Using a null object" error, a variable name is correct and you will get an "Using a null object" error,
even when the member name is invalid. *E1360* *E1362* *E1363* even when the variable name is invalid. *E1360* *E1362* *E1363*
Default constructor ~ Default constructor ~
In case you define a class without a new() method, one will be automatically In case you define a class without a new() method, one will be automatically
defined. This default constructor will have arguments for all the object defined. This default constructor will have arguments for all the object
members, in the order they were specified. Thus if your class looks like: > variables, in the order they were specified. Thus if your class looks like: >
class AutoNew class AutoNew
this.name: string this.name: string
@ -658,14 +661,14 @@ members, in the order they were specified. Thus if your class looks like: >
this.gender: Gender this.gender: Gender
endclass endclass
Then The default constructor will be: > Then the default constructor will be: >
def new(this.name = v:none, this.age = v:none, this.gender = v:none) def new(this.name = v:none, this.age = v:none, this.gender = v:none)
enddef enddef
The "= v:none" default values make the arguments optional. Thus you can also The "= v:none" default values make the arguments optional. Thus you can also
call `new()` without any arguments. No assignment will happen and the default call `new()` without any arguments. No assignment will happen and the default
value for the object members will be used. This is a more useful example, value for the object variables will be used. This is a more useful example,
with default values: > with default values: >
class TextPosition class TextPosition
@ -681,13 +684,13 @@ the name, you can define the constructor like this: >
enddef enddef
< *E1328* < *E1328*
Note that you cannot use another default value than "v:none" here. If you Note that you cannot use another default value than "v:none" here. If you
want to initialize the object members, do it where they are declared. This want to initialize the object variables, do it where they are declared. This
way you only need to look in one place for the default values. way you only need to look in one place for the default values.
All object members will be used in the default constructor, also private All object variables will be used in the default constructor, also private
access ones. access ones.
If the class extends another one, the object members of that class will come If the class extends another one, the object variables of that class will come
first. first.
@ -801,7 +804,7 @@ the method being called is obvious.
No overloading of the constructor ~ No overloading of the constructor ~
In Vim script, both legacy and |Vim9| script, there is no overloading of In Vim script, both legacy and |Vim9| script, there is no overloading of
functions. That means it is not possible to use the same function name with methods. That means it is not possible to use the same method name with
different types of arguments. Therefore there also is only one new() different types of arguments. Therefore there also is only one new()
constructor. constructor.
@ -844,39 +847,40 @@ class implements an interface just because the methods happen to match is
brittle and leads to obscure problems, let's not do that. brittle and leads to obscure problems, let's not do that.
Using "this.member" everywhere ~ Using "this.variable" everywhere ~
The object members in various programming languages can often be accessed in The object variables in various programming languages can often be accessed in
different ways, depending on the location. Sometimes "this." has to be different ways, depending on the location. Sometimes "this." has to be
prepended to avoid ambiguity. They are usually declared without "this.". prepended to avoid ambiguity. They are usually declared without "this.".
That is quite inconsistent and sometimes confusing. That is quite inconsistent and sometimes confusing.
A very common issue is that in the constructor the arguments use the same name A very common issue is that in the constructor the arguments use the same name
as the object member. Then for these members "this." needs to be prefixed in as the object variable. Then for these variables "this." needs to be prefixed
the body, while for other members this is not needed and often omitted. This in the body, while for other variables this is not needed and often omitted.
leads to a mix of members with and without "this.", which is inconsistent. This leads to a mix of variables with and without "this.", which is
inconsistent.
For |Vim9| classes the "this." prefix is always used. Also for declaring the For |Vim9| classes the "this." prefix is always used. Also for declaring the
members. Simple and consistent. When looking at the code inside a class it's variables. Simple and consistent. When looking at the code inside a class
also directly clear which variable references are object members and which it's also directly clear which variable references are object variables and
aren't. which aren't.
Using class members ~ Using class variables ~
Using "static member" to declare a class member is very common, nothing new Using "static variable" to declare a class variable is very common, nothing
here. In |Vim9| script these can be accessed directly by their name. Very new here. In |Vim9| script these can be accessed directly by their name.
much like how a script-local variable can be used in a function. Since object Very much like how a script-local variable can be used in a method. Since
members are always accessed with "this." prepended, it's also quickly clear object variables are always accessed with "this." prepended, it's also quickly
what kind of member it is. clear what kind of variable it is.
TypeScript prepends the class name before the class member, also inside the TypeScript prepends the class name before the class variable name, also inside
class. This has two problems: The class name can be rather long, taking up the class. This has two problems: The class name can be rather long, taking
quite a bit of space, and when the class is renamed all these places need to up quite a bit of space, and when the class is renamed all these places need
be changed too. to be changed too.
Declaring object and class members ~ Declaring object and class variables ~
The main choice is whether to use "var" as with variable declarations. The main choice is whether to use "var" as with variable declarations.
TypeScript does not use it: > TypeScript does not use it: >
@ -885,7 +889,7 @@ TypeScript does not use it: >
y = 0; y = 0;
} }
Following that Vim object members could be declared like this: > Following that Vim object variables could be declared like this: >
class Point class Point
this.x: number this.x: number
this.y = 0 this.y = 0
@ -898,7 +902,7 @@ declaration. Adding "var" changes that: >
var this.y = 0 var this.y = 0
endclass endclass
We also need to be able to declare class members using the "static" keyword. We also need to be able to declare class variables using the "static" keyword.
There we can also choose to leave out "var": > There we can also choose to leave out "var": >
class Point class Point
var this.x: number var this.x: number
@ -938,50 +942,50 @@ while there is no ClassName() method, it's a method by another name in the
class called ClassName. Quite confusing. class called ClassName. Quite confusing.
Default read access to object members ~ Default read access to object variables ~
Some users will remark that the access rules for object members are Some users will remark that the access rules for object variables are
asymmetric. Well, that is intentional. Changing a value is a very different asymmetric. Well, that is intentional. Changing a value is a very different
action than reading a value. The read operation has no side effects, it can action than reading a value. The read operation has no side effects, it can
be done any number of times without affecting the object. Changing the value be done any number of times without affecting the object. Changing the value
can have many side effects, and even have a ripple effect, affecting other can have many side effects, and even have a ripple effect, affecting other
objects. objects.
When adding object members one usually doesn't think much about this, just get When adding object variables one usually doesn't think much about this, just
the type right. And normally the values are set in the new() method. get the type right. And normally the values are set in the new() method.
Therefore defaulting to read access only "just works" in most cases. And when Therefore defaulting to read access only "just works" in most cases. And when
directly writing you get an error, which makes you wonder if you actually want directly writing you get an error, which makes you wonder if you actually want
to allow that. This helps writing code with fewer mistakes. to allow that. This helps writing code with fewer mistakes.
Making object members private with an underscore ~ Making object variables private with an underscore ~
When an object member is private, it can only be read and changed inside the When an object variable is private, it can only be read and changed inside the
class (and in sub-classes), then it cannot be used outside of the class. class (and in sub-classes), then it cannot be used outside of the class.
Prepending an underscore is a simple way to make that visible. Various Prepending an underscore is a simple way to make that visible. Various
programming languages have this as a recommendation. programming languages have this as a recommendation.
In case you change your mind and want to make the object member accessible In case you change your mind and want to make the object variable accessible
outside of the class, you will have to remove the underscore everywhere. outside of the class, you will have to remove the underscore everywhere.
Since the name only appears in the class (and sub-classes) they will be easy Since the name only appears in the class (and sub-classes) they will be easy
to find and change. to find and change.
The other way around is much harder: you can easily prepend an underscore to The other way around is much harder: you can easily prepend an underscore to
the object member inside the class to make it private, but any usage elsewhere the object variable inside the class to make it private, but any usage
you will have to track down and change. You may have to make it a "set" elsewhere you will have to track down and change. You may have to make it a
method call. This reflects the real world problem that taking away access "set" method call. This reflects the real world problem that taking away
requires work to be done for all places where that access exists. access requires work to be done for all places where that access exists.
An alternative would have been using the "private" keyword, just like "public" An alternative would have been using the "private" keyword, just like "public"
changes the access in the other direction. Well, that's just to reduce the changes the access in the other direction. Well, that's just to reduce the
number of keywords. number of keywords.
No protected object members ~ No protected object variables ~
Some languages provide several ways to control access to object members. The Some languages provide several ways to control access to object variables.
most known is "protected", and the meaning varies from language to language. The most known is "protected", and the meaning varies from language to
Others are "shared", "private" and even "friend". language. Others are "shared", "private" and even "friend".
These rules make life more difficult. That can be justified in projects where These rules make life more difficult. That can be justified in projects where
many people work on the same, complex code where it is easy to make mistakes. many people work on the same, complex code where it is easy to make mistakes.