| *vim9class.txt* For Vim version 9.0. Last change: 2023 Sep 18 |
| |
| |
| VIM REFERENCE MANUAL by Bram Moolenaar |
| |
| |
| NOTE - This is not finished yet, anything can still change! - NOTE |
| |
| |
| Vim9 classes, objects, interfaces, types and enums. |
| |
| 1. Overview |Vim9-class-overview| |
| 2. A simple class |Vim9-simple-class| |
| 3. Class variables and methods |Vim9-class-member| |
| 4. Using an abstract class |Vim9-abstract-class| |
| 5. Using an interface |Vim9-using-interface| |
| 6. More class details |Vim9-class| |
| 7. Type definition |Vim9-type| |
| 8. Enum |Vim9-enum| |
| |
| 9. Rationale |
| 10. To be done later |
| |
| ============================================================================== |
| |
| 1. Overview *Vim9-class-overview* |
| |
| The fancy term is "object-oriented programming". You can find lots of study |
| material on this subject. Here we document what |Vim9| script provides, |
| assuming you know the basics already. Added are helpful hints about how to |
| use this functionality effectively. Vim9 classes and objects cannot be used |
| in legacy Vim scripts and legacy functions. |
| |
| The basic item is an object: |
| - An object stores state. It contains one or more variables that can each |
| have a value. |
| - An object provides functions that use and manipulate its state. These |
| 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 |
| methods. |
| - Objects are created from a class and all objects have the same interface. |
| This does not change at runtime, it is not dynamic. |
| |
| An object can only be created by a class. A class provides: |
| - A new() method, the constructor, which returns an object for the class. |
| This method is invoked on the class name: MyClass.new(). |
| - State shared by all objects of the class: class variables (class members). |
| - A hierarchy of classes, with super-classes and sub-classes, inheritance. |
| |
| An interface is used to specify properties of an object: |
| - An object can declare several interfaces that it implements. |
| - Different objects implementing the same interface can be used the same way. |
| |
| The class hierarchy allows for single inheritance. Otherwise interfaces are |
| to be used where needed. |
| |
| |
| Class modeling ~ |
| |
| You can model classes any way you like. Keep in mind what you are building, |
| don't try to model the real world. This can be confusing, especially because |
| teachers use real-world objects to explain class relations and you might think |
| your model should therefore reflect the real world. It doesn't! The model |
| should match your purpose. |
| |
| Keep in mind that composition (an object contains other objects) is often |
| better than inheritance (an object extends another object). Don't waste time |
| trying to find the optimal class model. Or waste time discussing whether a |
| square is a rectangle or that a rectangle is a square. It doesn't matter. |
| |
| |
| ============================================================================== |
| |
| 2. A simple class *Vim9-simple-class* |
| |
| Let's start with a simple example: a class that stores a text position (see |
| below for how to do this more efficiently): > |
| |
| class TextPosition |
| this.lnum: number |
| this.col: number |
| |
| def new(lnum: number, col: number) |
| this.lnum = lnum |
| this.col = col |
| enddef |
| |
| def SetLnum(lnum: number) |
| this.lnum = lnum |
| enddef |
| |
| def SetCol(col: number) |
| this.col = col |
| enddef |
| |
| def SetPosition(lnum: number, col: number) |
| this.lnum = lnum |
| this.col = col |
| enddef |
| endclass |
| < *object* *Object* |
| You can create an object from this class with the new() method: > |
| |
| var pos = TextPosition.new(1, 1) |
| |
| The object variables "lnum" and "col" can be accessed directly: > |
| |
| echo $'The text position is ({pos.lnum}, {pos.col})' |
| < *E1317* *E1327* |
| 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 |
| variable. |
| |
| |
| Object variable write access ~ |
| |
| Now try to change an object variable directly: > |
| |
| pos.lnum = 9 |
| < *E1335* |
| 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 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 variable to |
| be changed at any time, you can make it public: > |
| |
| public this.lnum: number |
| public this.col: number |
| |
| 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 variable that doesn't exist you get an error: > |
| pos.other = 9 |
| < E1326: Member not found on object "TextPosition": other ~ |
| |
| *E1376* |
| A object variable cannot be accessed using the class name. |
| |
| Private variables ~ |
| *private-variable* *E1332* *E1333* |
| 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 variables. |
| These are commonly called getters. We recommend using a name that starts with |
| "Get": > |
| |
| def GetLnum(): number |
| return this._lnum |
| enddef |
| |
| def GetCol() number |
| return this._col |
| enddef |
| |
| 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: > |
| |
| def GetLnum(): number |
| if this._lnum > this._lineCount |
| return this._lineCount |
| endif |
| return this._lnum |
| enddef |
| < |
| Private methods ~ |
| *private-method* *E1366* |
| If you want object methods to be accessible only from other methods of the |
| same class and not used from outside the class, then you can make them |
| private. This is done by prefixing the method name with an underscore: > |
| |
| class SomeClass |
| def _Foo(): number |
| return 10 |
| enddef |
| def Bar(): number |
| return this._Foo() |
| enddef |
| endclass |
| < |
| Accessing a private method outside the class will result in an error (using |
| the above class): > |
| |
| var a = SomeClass.new() |
| a._Foo() |
| < |
| Simplifying the new() method ~ |
| *new()* *constructor* |
| Many constructors take values for the object variables. Thus you very often |
| see this pattern: > |
| |
| class SomeClass |
| this.lnum: number |
| this.col: number |
| |
| def new(lnum: number, col: number) |
| this.lnum = lnum |
| this.col = col |
| enddef |
| endclass |
| < |
| *E1390* |
| 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 variable name, |
| including "this.", as the argument to new() means the value provided in 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 variables public |
| results in a much shorter class definition than what we started with: > |
| |
| class TextPosition |
| public this.lnum: number |
| public this.col: number |
| |
| def new(this.lnum, this.col) |
| enddef |
| |
| def SetPosition(lnum: number, col: number) |
| this.lnum = lnum |
| this.col = col |
| enddef |
| endclass |
| |
| The sequence of constructing a new object is: |
| 1. Memory is allocated and cleared. All values are zero/false/empty. |
| 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 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 |
| always returns an object of the class. |
| |
| *E1386* |
| When invoking an object method, the method name should be preceded by the |
| object variable name. A object method cannot be invoked using the class |
| name. |
| |
| ============================================================================== |
| |
| 3. Class Variables and Methods *Vim9-class-member* |
| |
| *:static* *E1337* *E1338* *E1368* |
| Class members are declared with "static". They are used by the name without a |
| prefix in the class where they are defined: > |
| |
| class OtherThing |
| this.size: number |
| static totalSize: number |
| |
| def new(this.size) |
| totalSize += this.size |
| enddef |
| endclass |
| < *E1340* *E1341* |
| Since the name is used as-is, shadowing the name by a method argument name |
| or local variable name is not allowed. |
| |
| *E1374* *E1375* *E1384* *E1385* |
| To access a class member outside of the class where it is defined, the class |
| name prefix must be used. A class member cannot be accessed using an object. |
| |
| Just like object members the access can be made private by using an underscore |
| as the first character in the name, and it can be made public by prefixing |
| "public": > |
| |
| class OtherThing |
| static total: number # anybody can read, only class can write |
| static _sum: number # only class can read and write |
| public static result: number # anybody can read and write |
| endclass |
| < |
| *class-method* |
| Class methods are also declared with "static". They can use the class |
| variables but they have no access to the object variables, they cannot use the |
| "this" keyword: |
| > |
| class OtherThing |
| this.size: number |
| static totalSize: number |
| |
| # Clear the total size and return the value it had before. |
| static def ClearTotalSize(): number |
| var prev = totalSize |
| totalSize = 0 |
| return prev |
| enddef |
| endclass |
| |
| Inside the class the class method can be called by name directly, outside the |
| class the class name must be prefixed: `OtherThing.ClearTotalSize()`. To use |
| a super class method in a child class, the class name must be prefixed. |
| |
| Just like object methods the access can be made private by using an underscore |
| as the first character in the method name: > |
| |
| class OtherThing |
| static def _Foo() |
| echo "Foo" |
| enddef |
| def Bar() |
| _Foo() |
| enddef |
| endclass |
| < |
| *E1370* |
| Note that constructors cannot be declared as "static", because they always |
| are. |
| |
| To access the class methods and class variables of a super class in an |
| extended class, the class name prefix should be used just as from anywhere |
| outside of the defining class: > |
| |
| vim9script |
| class Vehicle |
| static nextID: number = 1000 |
| static def GetID(): number |
| nextID += 1 |
| return nextID |
| enddef |
| endclass |
| class Car extends Vehicle |
| this.myID: number |
| def new() |
| this.myID = Vehicle.GetID() |
| enddef |
| endclass |
| < |
| Class variables and methods are not inherited by a child class. A child class |
| can declare a static variable or a method with the same name as the one in the |
| super class. Depending on the class where the member is used the |
| corresponding class member will be used. The type of the class member in a |
| child class can be different from that in the super class. |
| |
| ============================================================================== |
| |
| 4. Using an abstract class *Vim9-abstract-class* |
| |
| An abstract class forms the base for at least one sub-class. In the class |
| model one often finds that a few classes have the same properties that can be |
| shared, but a class with these properties does not have enough state to create |
| an object from. A sub-class must extend the abstract class and add the |
| missing state and/or methods before it can be used to create objects for. |
| |
| For example, a Shape class could store a color and thickness. You cannot |
| create a Shape object, it is missing the information about what kind of shape |
| it is. The Shape class functions as the base for a Square and a Triangle |
| class, for which objects can be created. Example: > |
| |
| abstract class Shape |
| this.color = Color.Black |
| this.thickness = 10 |
| endclass |
| |
| class Square extends Shape |
| this.size: number |
| |
| def new(this.size) |
| enddef |
| endclass |
| |
| class Triangle extends Shape |
| this.base: number |
| this.height: number |
| |
| def new(this.base, this.height) |
| enddef |
| endclass |
| < |
| An abstract class is defined the same way as a normal class, except that it |
| 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 method: > |
| |
| abstract class Shape |
| abstract def Draw() |
| abstract static def SetColor() |
| endclass |
| < |
| *E1373* |
| A class extending the abstract class must implement all the abstract methods. |
| The signature (arguments, argument types and return type) must be exactly the |
| same. If the return type of a method is a class, then that class or one of |
| its subclasses can be used in the extended method. Class methods in an |
| abstract class can also be abstract methods. |
| |
| ============================================================================== |
| |
| 5. Using an interface *Vim9-using-interface* |
| |
| The example above with Shape, Square and Triangle can be made more useful if |
| we add a method to compute the surface of the object. For that we create the |
| interface called HasSurface, which specifies one method Surface() that returns |
| a number. This example extends the one above: > |
| |
| abstract class Shape |
| this.color = Color.Black |
| this.thickness = 10 |
| endclass |
| |
| interface HasSurface |
| def Surface(): number |
| endinterface |
| |
| class Square extends Shape implements HasSurface |
| this.size: number |
| |
| def new(this.size) |
| enddef |
| |
| def Surface(): number |
| return this.size * this.size |
| enddef |
| endclass |
| |
| class Triangle extends Shape implements HasSurface |
| this.base: number |
| this.height: number |
| |
| def new(this.base, this.height) |
| enddef |
| |
| def Surface(): number |
| return this.base * this.height / 2 |
| enddef |
| endclass |
| < |
| *E1348* *E1349* *E1367* *E1382* *E1383* |
| If a class declares to implement an interface, all the items specified in the |
| interface must appear in the class, with the same types. |
| |
| The interface name can be used as a type: > |
| |
| var shapes: list<HasSurface> = [ |
| Square.new(12), |
| Triangle.new(8, 15), |
| ] |
| for shape in shapes |
| echo $'the surface is {shape.Surface()}' |
| endfor |
| < |
| *E1378* *E1379* *E1380* *E1387* |
| An interface can contain only object methods and read-only object variables. |
| An interface cannot contain read-write and private object variables, private |
| object methods, class variables and class methods. |
| |
| An interface can extend another interface using "extends". The sub-interface |
| inherits all the instance variables and methods from the super interface. |
| |
| ============================================================================== |
| |
| 6. More class details *Vim9-class* *Class* *class* |
| |
| Defining a class ~ |
| *:class* *:endclass* *:abstract* |
| A class is defined between `:class` and `:endclass`. The whole class is |
| defined in one script file. It is not possible to add to a class later. |
| |
| A class can only be defined in a |Vim9| script file. *E1316* |
| A class cannot be defined inside a function. |
| |
| It is possible to define more than one class in a script file. Although it |
| usually is better to export only one main class. It can be useful to define |
| types, enums and helper classes though. |
| |
| The `:abstract` keyword may be prefixed and `:export` may be used. That gives |
| these variants: > |
| |
| class ClassName |
| endclass |
| |
| export class ClassName |
| endclass |
| |
| abstract class ClassName |
| endclass |
| |
| export abstract class ClassName |
| endclass |
| < |
| *E1314* |
| The class name should be CamelCased. It must start with an uppercase letter. |
| That avoids clashing with builtin types. |
| *E1315* |
| After the class name these optional items can be used. Each can appear only |
| once. They can appear in any order, although this order is recommended: > |
| extends ClassName |
| implements InterfaceName, OtherInterface |
| specifies SomeInterface |
| < *E1355* *E1369* |
| 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. |
| |
| |
| 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. |
| |
| The following reserved keyword names cannot be used as an object or class |
| variable name: "super", "this", "true", "false", "null", "null_blob", |
| "null_dict", "null_function", "null_list", "null_partial", "null_string", |
| "null_channel" and "null_job". |
| |
| Extending a class ~ |
| *extends* |
| A class can extend one other class. *E1352* *E1353* *E1354* |
| 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 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* |
| Object methods of the base class can be overruled. The signature (arguments, |
| argument types and return type) must be exactly the same. If the return type |
| of a method is a class, then that class or one of its subclasses can be used |
| in the extended method. The method of the base class can be called by |
| prefixing "super.". |
| |
| *E1377* |
| The access level of a method (public or private) in a child class should be |
| the same as the super class. |
| |
| Other object methods of the base class are taken over by the child class. |
| |
| 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() 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 ~ |
| *implements* *E1346* *E1347* *E1389* |
| A class can implement one or more interfaces. The "implements" keyword can |
| only appear once *E1350* . Multiple interfaces can be specified, separated by |
| commas. Each interface name can appear only once. *E1351* |
| |
| |
| A class defining an interface ~ |
| *specifies* |
| 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. |
| |
| |
| Items in a class ~ |
| *E1318* *E1325* *E1388* |
| Inside a class, in between `:class` and `:endclass`, these items can appear: |
| - 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) |
| - A class method: > |
| static def SomeMethod(arguments) |
| static def _PrivateMethod(arguments) |
| - An object method: > |
| def SomeMethod(arguments) |
| def _PrivateMethod(arguments) |
| |
| 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> |
| The initialization isn't needed, the list is empty by default. |
| *E1330* |
| Some types cannot be used, such as "void", "null" and "v:none". |
| |
| |
| Defining an interface ~ |
| *:interface* *:endinterface* |
| An interface is defined between `:interface` and `:endinterface`. It may be |
| prefixed with `:export`: > |
| |
| interface InterfaceName |
| endinterface |
| |
| export interface InterfaceName |
| endinterface |
| < *E1344* |
| 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: > |
| |
| interface HasSurface |
| this.size: number |
| def Surface(): number |
| endinterface |
| |
| An interface name must start with an uppercase letter. *E1343* |
| The "Has" prefix can be used to make it easier to guess this is an interface |
| name, with a hint about what it provides. |
| An interface can only be defined in a |Vim9| script file. *E1342* |
| An interface cannot "implement" another interface but it can "extend" another |
| interface. *E1381* |
| |
| |
| 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 variable name is correct and you will get an "Using a null object" error, |
| even when the variable name is invalid. *E1360* *E1362* |
| |
| |
| Default constructor ~ |
| *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 |
| variables, in the order they were specified. Thus if your class looks like: > |
| |
| class AutoNew |
| this.name: string |
| this.age: number |
| this.gender: Gender |
| endclass |
| |
| 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 variables will be used. This is a more useful example, |
| with default values: > |
| |
| class TextPosition |
| this.lnum: number = 1 |
| this.col: number = 1 |
| endclass |
| |
| If you want the constructor to have mandatory arguments, you need to write it |
| yourself. For example, if for the AutoNew class above you insist on getting |
| the name, you can define the constructor like this: > |
| |
| def new(this.name, this.age = v:none, this.gender = v:none) |
| enddef |
| < *E1328* |
| Note that you cannot use another default value than "v:none" here. If you |
| 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 variables will be used in the default constructor, also private |
| access ones. |
| |
| If the class extends another one, the object variables of that class will come |
| first. |
| |
| |
| Multiple constructors ~ |
| |
| Normally a class has just one new() constructor. In case you find that the |
| constructor is often called with the same arguments you may want to simplify |
| your code by putting those arguments into a second constructor method. For |
| example, if you tend to use the color black a lot: > |
| |
| 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") |
| |
| Instead of repeating the color every time you can add a constructor that |
| includes it: > |
| |
| 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") |
| |
| Note that the method name must start with "new". If there is no method called |
| "new()" then the default constructor is added, even though there are other |
| constructor methods. |
| |
| |
| ============================================================================== |
| |
| 7. Type definition *Vim9-type* *:type* |
| |
| {not implemented yet} |
| |
| A type definition is giving a name to a type specification. For Example: > |
| |
| :type ListOfStrings list<string> |
| |
| |
| ============================================================================== |
| |
| 8. Enum *Vim9-enum* *:enum* *:endenum* |
| |
| {not implemented yet} |
| |
| An enum is a type that can have one of a list of values. Example: > |
| |
| :enum Color |
| White |
| Red |
| Green |
| Blue |
| Black |
| :endenum |
| |
| |
| ============================================================================== |
| |
| 9. Rationale |
| |
| Most of the choices for |Vim9| classes come from popular and recently |
| developed languages, such as Java, TypeScript and Dart. The syntax has been |
| made to fit with the way Vim script works, such as using `endclass` instead of |
| using curly braces around the whole class. |
| |
| Some common constructs of object-oriented languages were chosen very long ago |
| when this kind of programming was still new, and later found to be |
| sub-optimal. By this time those constructs were widely used and changing them |
| was not an option. In Vim we do have the freedom to make different choices, |
| since classes are completely new. We can make the syntax simpler and more |
| consistent than what "old" languages use. Without diverting too much, it |
| should still mostly look like what you know from existing languages. |
| |
| Some recently developed languages add all kinds of fancy features that we |
| don't need for Vim. But some have nice ideas that we do want to use. |
| Thus we end up with a base of what is common in popular languages, dropping |
| what looks like a bad idea, and adding some nice features that are easy to |
| understand. |
| |
| The main rules we use to make decisions: |
| - Keep it simple. |
| - No surprises, mostly do what other languages are doing. |
| - Avoid mistakes from the past. |
| - Avoid the need for the script writer to consult the help to understand how |
| things work, most things should be obvious. |
| - Keep it consistent. |
| - Aim at an average size plugin, not at a huge project. |
| |
| |
| Using new() for the constructor ~ |
| |
| Many languages use the class name for the constructor method. A disadvantage |
| is that quite often this is a long name. And when changing the class name all |
| constructor methods need to be renamed. Not a big deal, but still a |
| disadvantage. |
| |
| Other languages, such as TypeScript, use a specific name, such as |
| "constructor()". That seems better. However, using "new" or "new()" to |
| create a new object has no obvious relation with "constructor()". |
| |
| For |Vim9| script using the same method name for all constructors seemed like |
| the right choice, and by calling it new() the relation between the caller and |
| the method being called is obvious. |
| |
| |
| No overloading of the constructor ~ |
| |
| In Vim script, both legacy and |Vim9| script, there is no overloading of |
| 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. |
| |
| With |Vim9| script it would be possible to support overloading, since |
| arguments are typed. However, this gets complicated very quickly. Looking at |
| a new() call one has to inspect the types of the arguments to know which of |
| several new() methods is actually being called. And that can require |
| inspecting quite a bit of code. For example, if one of the arguments is the |
| return value of a method, you need to find that method to see what type it is |
| returning. |
| |
| Instead, every constructor has to have a different name, starting with "new". |
| That way multiple constructors with different arguments are possible, while it |
| is very easy to see which constructor is being used. And the type of |
| arguments can be properly checked. |
| |
| |
| No overloading of methods ~ |
| |
| Same reasoning as for the constructor: It is often not obvious what type |
| arguments have, which would make it difficult to figure out what method is |
| actually being called. Better just give the methods a different name, then |
| type checking will make sure it works as you intended. This rules out |
| polymorphism, which we don't really need anyway. |
| |
| |
| Single inheritance and interfaces ~ |
| |
| Some languages support multiple inheritance. Although that can be useful in |
| some cases, it makes the rules of how a class works quite complicated. |
| Instead, using interfaces to declare what is supported is much simpler. The |
| very popular Java language does it this way, and it should be good enough for |
| Vim. The "keep it simple" rule applies here. |
| |
| Explicitly declaring that a class supports an interface makes it easy to see |
| what a class is intended for. It also makes it possible to do proper type |
| checking. When an interface is changed any class that declares to implement |
| it will be checked if that change was also changed. The mechanism to assume a |
| 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.variable" everywhere ~ |
| |
| 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 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 |
| 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 variables ~ |
| |
| 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 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 variables ~ |
| |
| The main choice is whether to use "var" as with variable declarations. |
| TypeScript does not use it: > |
| class Point { |
| x: number; |
| y = 0; |
| } |
| |
| Following that Vim object variables could be declared like this: > |
| class Point |
| this.x: number |
| this.y = 0 |
| endclass |
| |
| Some users pointed out that this looks more like an assignment than a |
| declaration. Adding "var" changes that: > |
| class Point |
| var this.x: number |
| var this.y = 0 |
| endclass |
| |
| 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 |
| static count = 0 |
| endclass |
| |
| Or do use it, before "static": > |
| class Point |
| var this.x: number |
| var static count = 0 |
| endclass |
| |
| Or after "static": > |
| class Point |
| var this.x: number |
| static var count = 0 |
| endclass |
| |
| This is more in line with "static def Func()". |
| |
| There is no clear preference whether to use "var" or not. The two main |
| reasons to leave it out are: |
| 1. TypeScript, Java and other popular languages do not use it. |
| 2. Less clutter. |
| |
| |
| Using "ClassName.new()" to construct an object ~ |
| |
| Many languages use the "new" operator to create an object, which is actually |
| kind of strange, since the constructor is defined as a method with arguments, |
| not a command. TypeScript also has the "new" keyword, but the method is |
| called "constructor()", it is hard to see the relation between the two. |
| |
| In |Vim9| script the constructor method is called new(), and it is invoked as |
| new(), simple and straightforward. Other languages use "new ClassName()", |
| 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 variables ~ |
| |
| 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 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 variables private with an underscore ~ |
| |
| 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 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 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 variables ~ |
| |
| 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. |
| Especially when refactoring or other changes to the class model. |
| |
| The Vim scripts are expected to be used in a plugin, with just one person or a |
| small team working on it. Complex rules then only make it more complicated, |
| the extra safety provided by the rules isn't really needed. Let's just keep |
| it simple and not specify access details. |
| |
| |
| ============================================================================== |
| |
| 10. To be done later |
| |
| Can a newSomething() constructor invoke another constructor? If yes, what are |
| the restrictions? |
| |
| Thoughts: |
| - Generics for a class: `class <Tkey, Tentry>` |
| - Generics for a function: `def <Tkey> GetLast(key: Tkey)` |
| - Mixins: not sure if that is useful, leave out for simplicity. |
| |
| Some things that look like good additions: |
| - For testing: Mock mechanism |
| |
| An important class to be provided is "Promise". Since Vim is single |
| threaded, connecting asynchronous operations is a natural way of allowing |
| plugins to do their work without blocking the user. It's a uniform way to |
| invoke callbacks and handle timeouts and errors. |
| |
| |
| vim:tw=78:ts=8:noet:ft=help:norl: |