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:
committed by
GitHub
parent
abc808112e
commit
c3b315f496
@ -37,7 +37,7 @@ The basic item is an object:
|
||||
functions are invoked "on the object", which is what sets it apart from the
|
||||
traditional separation of data and code that manipulates the data.
|
||||
- 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.
|
||||
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)
|
||||
|
||||
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})'
|
||||
< *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."
|
||||
prefix. This is different from languages like Java and TypeScript. The
|
||||
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
|
||||
< *E1335*
|
||||
This will give you an error! That is because by default object members can be
|
||||
read but not set. That's why the TextPosition class provides a method for it: >
|
||||
This will give you an error! That is because by default object variables can
|
||||
be read but not set. That's why the TextPosition class provides a method for
|
||||
it: >
|
||||
|
||||
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
|
||||
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
|
||||
the closest valid value.
|
||||
*:public* *E1331*
|
||||
If you don't care about side effects and want to allow the object member to be
|
||||
changed at any time, you can make it public: >
|
||||
If you don't care about side effects and want to allow the object variable to
|
||||
be changed at any time, you can make it public: >
|
||||
|
||||
public this.lnum: 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
|
||||
"pos.lnum" directly above will no longer give an error.
|
||||
*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
|
||||
< E1326: Member not found on object "TextPosition": other ~
|
||||
|
||||
*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*
|
||||
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
|
||||
name: >
|
||||
|
||||
this._lnum: 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
|
||||
"Get": >
|
||||
|
||||
@ -168,7 +170,7 @@ These are commonly called getters. We recommend using a name that starts with
|
||||
return this._col
|
||||
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
|
||||
number to the total number of lines: >
|
||||
|
||||
@ -202,8 +204,8 @@ the above class): >
|
||||
<
|
||||
Simplifying the new() method ~
|
||||
|
||||
Many constructors take values for the object members. Thus you very often see
|
||||
this pattern: >
|
||||
Many constructors take values for the object variables. Thus you very often
|
||||
see this pattern: >
|
||||
|
||||
class SomeClass
|
||||
this.lnum: number
|
||||
@ -215,19 +217,20 @@ this pattern: >
|
||||
enddef
|
||||
endclass
|
||||
|
||||
Not only is this text you need to write, it also has the type of each member
|
||||
twice. Since this is so common a shorter way to write new() is provided: >
|
||||
Not only is this text you need to write, it also has the type of each
|
||||
variables twice. Since this is so common a shorter way to write new() is
|
||||
provided: >
|
||||
|
||||
def new(this.lnum, this.col)
|
||||
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
|
||||
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.
|
||||
|
||||
Putting together this way of using new() and making the members public results
|
||||
in a much shorter class definition than what we started with: >
|
||||
Putting together this way of using new() and making the variables public
|
||||
results in a much shorter class definition than what we started with: >
|
||||
|
||||
class TextPosition
|
||||
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:
|
||||
1. Memory is allocated and cleared. All values are zero/false/empty.
|
||||
2. For each declared member that has an initializer, the expression is
|
||||
evaluated and assigned to the member. This happens in the sequence the
|
||||
members are declared in the class.
|
||||
2. For each declared object variable that has an initializer, the expression
|
||||
is evaluated and assigned to the variable. This happens in the sequence
|
||||
the variables are declared in the class.
|
||||
3. Arguments in the new() method in the "this.name" form are assigned.
|
||||
4. The body of the new() method is executed.
|
||||
|
||||
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
|
||||
"super()" or "new()" on the parent.
|
||||
step the object variables of the parent class are initialized first. There is
|
||||
no need to call "super()" or "new()" on the parent.
|
||||
|
||||
*E1365*
|
||||
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
|
||||
endclass
|
||||
< *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.
|
||||
|
||||
*E1374* *E1375*
|
||||
@ -394,7 +397,7 @@ does not have any new() method. *E1359*
|
||||
|
||||
*abstract-method* *E1371* *E1372*
|
||||
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 def Draw()
|
||||
@ -510,17 +513,16 @@ once. They can appear in any order, although this order is recommended: >
|
||||
implements InterfaceName, OtherInterface
|
||||
specifies SomeInterface
|
||||
< *E1355* *E1369*
|
||||
Each member and function 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
|
||||
not possible to use a public and private member variable with the same name.
|
||||
A object variable name used in a super class cannot be reused in a child
|
||||
class.
|
||||
Each variable and method name can be used only once. It is not possible to
|
||||
define a method with the same name and different type of arguments. It is not
|
||||
possible to use a public and private member variable with the same name. A
|
||||
object variable name used in a super class cannot be reused in a child class.
|
||||
|
||||
|
||||
Member Initialization ~
|
||||
If the type of a member is not explicitly specified in a class, then it is set
|
||||
to "any" during class definition. When an object is instantiated from the
|
||||
class, then the type of the member is set.
|
||||
Object Variable Initialization ~
|
||||
If the type of a variable is not explicitly specified in a class, then it is
|
||||
set to "any" during class definition. When an object is instantiated from the
|
||||
class, then the type of the variable is set.
|
||||
|
||||
|
||||
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
|
||||
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).
|
||||
|
||||
*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.
|
||||
|
||||
Class functions, including functions starting with "new", can be overruled,
|
||||
like with object methods. The function on the base class can be called by
|
||||
prefixing the name of the class (for class functions) or "super.".
|
||||
Class methods, including methods starting with "new", can be overruled, like
|
||||
with object methods. The method on the base class can be called by prefixing
|
||||
the name of the class (for class methods) or "super.".
|
||||
|
||||
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
|
||||
class also needs to be done in a child class, put it in an object method and
|
||||
call that method from every constructor().
|
||||
|
||||
If the base class did not specify a new() function then one was automatically
|
||||
created. This function 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()
|
||||
function will be added automatically.
|
||||
If the base class did not specify a new() method then one was automatically
|
||||
created. This method will not be taken over by the child class. The child
|
||||
class can define its own new() method, or, if there isn't one, a new() method
|
||||
will be added automatically.
|
||||
|
||||
|
||||
A class implementing an interface ~
|
||||
@ -569,7 +571,7 @@ commas. Each interface name can appear only once. *E1351*
|
||||
|
||||
A class defining an interface ~
|
||||
*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
|
||||
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 ~
|
||||
*E1318* *E1325*
|
||||
Inside a class, in between `:class` and `:endclass`, these items can appear:
|
||||
- An object member declaration: >
|
||||
this._privateMemberName: memberType
|
||||
this.readonlyMemberName: memberType
|
||||
public this.readwriteMemberName: memberType
|
||||
- A class member declaration: >
|
||||
static this._privateMemberName: memberType
|
||||
static this.readonlyMemberName: memberType
|
||||
static public this.readwriteMemberName: memberType
|
||||
- An object variable declaration: >
|
||||
this._privateVariableName: memberType
|
||||
this.readonlyVariableName: memberType
|
||||
public this.readwriteVariableName: memberType
|
||||
- A class variable declaration: >
|
||||
static _privateClassVariableName: memberType
|
||||
static readonlyClassVariableName: memberType
|
||||
static public readwriteClassVariableName: memberType
|
||||
- A constructor method: >
|
||||
def new(arguments)
|
||||
def newName(arguments)
|
||||
@ -595,10 +597,11 @@ Inside a class, in between `:class` and `:endclass`, these items can appear:
|
||||
def SomeMethod(arguments)
|
||||
def _PrivateMethod(arguments)
|
||||
|
||||
For the object member the type must be specified. The best way is to do this
|
||||
explicitly with ": {type}". For simple types you can also use an initializer,
|
||||
such as "= 123", and Vim will see that the type is a number. Avoid doing this
|
||||
for more complex types and when the type will be incomplete. For example: >
|
||||
For the object variable the type must be specified. The best way is to do
|
||||
this explicitly with ": {type}". For simple types you can also use an
|
||||
initializer, such as "= 123", and Vim will see that the type is a number.
|
||||
Avoid doing this for more complex types and when the type will be incomplete.
|
||||
For example: >
|
||||
this.nameList = []
|
||||
This specifies a list, but the item type is unknown. Better use: >
|
||||
this.nameList: list<string>
|
||||
@ -618,8 +621,8 @@ prefixed with `:export`: >
|
||||
export interface InterfaceName
|
||||
endinterface
|
||||
< *E1344*
|
||||
An interface can declare object members, just like in a class but without any
|
||||
initializer.
|
||||
An interface can declare object variables, just like in a class but without
|
||||
any initializer.
|
||||
*E1345*
|
||||
An interface can declare methods with `:def`, including the arguments and
|
||||
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
|
||||
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
|
||||
a member name is correct and you will get an "Using a null object" error,
|
||||
even when the member name is invalid. *E1360* *E1362* *E1363*
|
||||
a variable name is correct and you will get an "Using a null object" error,
|
||||
even when the variable name is invalid. *E1360* *E1362* *E1363*
|
||||
|
||||
|
||||
Default constructor ~
|
||||
|
||||
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
|
||||
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
|
||||
this.name: string
|
||||
@ -658,14 +661,14 @@ members, in the order they were specified. Thus if your class looks like: >
|
||||
this.gender: Gender
|
||||
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)
|
||||
enddef
|
||||
|
||||
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
|
||||
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: >
|
||||
|
||||
class TextPosition
|
||||
@ -681,13 +684,13 @@ the name, you can define the constructor like this: >
|
||||
enddef
|
||||
< *E1328*
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@ -801,7 +804,7 @@ the method being called is obvious.
|
||||
No overloading of the constructor ~
|
||||
|
||||
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()
|
||||
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.
|
||||
|
||||
|
||||
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
|
||||
prepended to avoid ambiguity. They are usually declared without "this.".
|
||||
That is quite inconsistent and sometimes confusing.
|
||||
|
||||
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
|
||||
the body, while for other members this is not needed and often omitted. This
|
||||
leads to a mix of members with and without "this.", which is inconsistent.
|
||||
as the object variable. Then for these variables "this." needs to be prefixed
|
||||
in the body, while for other variables this is not needed and often omitted.
|
||||
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
|
||||
members. Simple and consistent. When looking at the code inside a class it's
|
||||
also directly clear which variable references are object members and which
|
||||
aren't.
|
||||
variables. Simple and consistent. When looking at the code inside a class
|
||||
it's also directly clear which variable references are object variables and
|
||||
which aren't.
|
||||
|
||||
|
||||
Using class members ~
|
||||
Using class variables ~
|
||||
|
||||
Using "static member" to declare a class member is very common, nothing new
|
||||
here. In |Vim9| script these can be accessed directly by their name. Very
|
||||
much like how a script-local variable can be used in a function. Since object
|
||||
members are always accessed with "this." prepended, it's also quickly clear
|
||||
what kind of member it is.
|
||||
Using "static variable" to declare a class variable is very common, nothing
|
||||
new here. In |Vim9| script these can be accessed directly by their name.
|
||||
Very much like how a script-local variable can be used in a method. Since
|
||||
object variables are always accessed with "this." prepended, it's also quickly
|
||||
clear what kind of variable it is.
|
||||
|
||||
TypeScript prepends the class name before the class member, also inside the
|
||||
class. This has two problems: The class name can be rather long, taking up
|
||||
quite a bit of space, and when the class is renamed all these places need to
|
||||
be changed too.
|
||||
TypeScript prepends the class name before the class variable name, also inside
|
||||
the class. This has two problems: The class name can be rather long, taking
|
||||
up quite a bit of space, and when the class is renamed all these places need
|
||||
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.
|
||||
TypeScript does not use it: >
|
||||
@ -885,7 +889,7 @@ TypeScript does not use it: >
|
||||
y = 0;
|
||||
}
|
||||
|
||||
Following that Vim object members could be declared like this: >
|
||||
Following that Vim object variables could be declared like this: >
|
||||
class Point
|
||||
this.x: number
|
||||
this.y = 0
|
||||
@ -898,7 +902,7 @@ declaration. Adding "var" changes that: >
|
||||
var this.y = 0
|
||||
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": >
|
||||
class Point
|
||||
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.
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
can have many side effects, and even have a ripple effect, affecting other
|
||||
objects.
|
||||
|
||||
When adding object members one usually doesn't think much about this, just get
|
||||
the type right. And normally the values are set in the new() method.
|
||||
When adding object variables one usually doesn't think much about this, just
|
||||
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
|
||||
directly writing you get an error, which makes you wonder if you actually want
|
||||
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.
|
||||
Prepending an underscore is a simple way to make that visible. Various
|
||||
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.
|
||||
Since the name only appears in the class (and sub-classes) they will be easy
|
||||
to find and change.
|
||||
|
||||
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
|
||||
you will have to track down and change. You may have to make it a "set"
|
||||
method call. This reflects the real world problem that taking away access
|
||||
requires work to be done for all places where that access exists.
|
||||
the object variable inside the class to make it private, but any usage
|
||||
elsewhere you will have to track down and change. You may have to make it a
|
||||
"set" method call. This reflects the real world problem that taking away
|
||||
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"
|
||||
changes the access in the other direction. Well, that's just to reduce the
|
||||
number of keywords.
|
||||
|
||||
|
||||
No protected object members ~
|
||||
No protected object variables ~
|
||||
|
||||
Some languages provide several ways to control access to object members. The
|
||||
most known is "protected", and the meaning varies from language to language.
|
||||
Others are "shared", "private" and even "friend".
|
||||
Some languages provide several ways to control access to object variables.
|
||||
The most known is "protected", and the meaning varies from language to
|
||||
language. Others are "shared", "private" and even "friend".
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user