mirror of
				https://github.com/vim/vim.git
				synced 2025-10-31 09:57:14 -04:00 
			
		
		
		
	Problem:    Classes are not documented or implemented yet.
Solution:   Make the first steps at documenting Vim9 objects, classes and
            interfaces.  Make initial choices for the syntax.  Add a skeleton
            implementation.  Add "public" and "this" in the command table.
		
	
		
			
				
	
	
		
			698 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			698 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| *vim9class.txt*	For Vim version 9.0.  Last change: 2022 Dec 04
 | |
| 
 | |
| 
 | |
| 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
 | |
| 
 | |
| 
 | |
| NOTE - This is under development, 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.  Using an abstract class	|Vim9-abstract-class|
 | |
| 4.  Using an interface		|Vim9-using-interface|
 | |
| 5.  More class details		|Vim9-class|
 | |
| 6.  Type definition		|Vim9-type|
 | |
| 7.  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 about 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.
 | |
| 
 | |
| The basic item is an object:
 | |
| - An object stores state.  It contains one or more variables that can each
 | |
|   have a value.
 | |
| - An object usually provides functions that 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
 | |
|   member functions.
 | |
| - Objects are created by a class and all objects have the same interface.
 | |
|   This never changes, 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 and constants.
 | |
| - 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.
 | |
| 
 | |
| You will soon find that composition is often better than inheritance.  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: >
 | |
| 
 | |
| 	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
 | |
| 
 | |
| 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: >
 | |
| 
 | |
| 	echo $'The text position is ({pos.lnum}, {pos.col})'
 | |
| 
 | |
| 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.  This
 | |
| 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.
 | |
| 
 | |
| 
 | |
| Member write access ~
 | |
| 
 | |
| Now try to change an object member directly: >
 | |
| 
 | |
| 	pos.lnum = 9
 | |
| 
 | |
| This will give you an error!  That is because by default object members can be
 | |
| read but not set.  That's why the class provides a method for it: >
 | |
| 
 | |
| 	pos.SetLnum(9)
 | |
| 
 | |
| Allowing to read but not set an object member 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.
 | |
| 
 | |
| 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: >
 | |
| 
 | |
| 	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.
 | |
| 
 | |
| 
 | |
| Private members ~
 | |
| 
 | |
| On the other hand, if you do not want the object members 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.
 | |
| These are commonly call 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 members 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
 | |
| 
 | |
| 
 | |
| Simplifying the new() method ~
 | |
| 
 | |
| Many constructors take values for the object members.  Thus you very often see
 | |
| this pattern: >
 | |
| 
 | |
| 	   this.lnum: number
 | |
| 	   this.col: number
 | |
| 
 | |
| 	   def new(lnum: number, col: number)
 | |
| 	      this.lnum = lnum
 | |
| 	      this.col = col
 | |
| 	   enddef
 | |
| 
 | |
| 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: >
 | |
| 
 | |
| 	   def new(this.lnum, this.col)
 | |
| 	   enddef
 | |
| 
 | |
| The semantics are easy to understand: Providing the object member name,
 | |
| including "this.", as the argument to new() means the value provided in the
 | |
| new() call is assigned to that object member.  This mechanism is coming from
 | |
| the Dart language.
 | |
| 
 | |
| 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.
 | |
| 3. Arguments in the new() method in the "this.name" form are assigned.
 | |
| 4. The body of the new() method is executed.
 | |
| 
 | |
| TODO: for a sub-class the constructor of the parent class will be invoked
 | |
| somewhere.
 | |
| 
 | |
| 
 | |
| ==============================================================================
 | |
| 
 | |
| 3.  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 those 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.
 | |
| 
 | |
| An abstract class does not have a new() method.
 | |
| 
 | |
| 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
 | |
| <
 | |
| 						*class-member* *:static*
 | |
| Class members are declared with "static".  They are used by the name without a
 | |
| prefix: >
 | |
| 
 | |
| 	class OtherThing
 | |
| 	   this.size: number
 | |
| 	   static totalSize: number
 | |
| 
 | |
| 	   def new(this.size)
 | |
| 	      totalSize += this.size
 | |
| 	   enddef
 | |
| 	endclass
 | |
| <
 | |
| 						*class-method*
 | |
| Class methods are also declared with "static".  They have no access to object
 | |
| members, 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
 | |
| 
 | |
| 
 | |
| ==============================================================================
 | |
| 
 | |
| 4.  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
 | |
| 
 | |
| 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
 | |
| 
 | |
| 
 | |
| ==============================================================================
 | |
| 
 | |
| 5.  More class details					*Vim9-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.
 | |
| 
 | |
| 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.
 | |
| 
 | |
| 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
 | |
| <							*extends*
 | |
| A class can extend one other class.
 | |
| 							*implements*
 | |
| A class can implement one or more interfaces.
 | |
| 							*specifies*
 | |
| A class can declare it's interface, the object members and methods, with a
 | |
| named interface.  This avoids the need for separately specifying the
 | |
| interface, which is often done an many languages, especially Java.
 | |
| 
 | |
| 
 | |
| 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
 | |
| 
 | |
| An interface can declare object members, just like in a class but without any
 | |
| initializer.
 | |
| 
 | |
| 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
 | |
| 
 | |
| The "Has" prefix can be used to make it easier to guess this is an interface
 | |
| name, with a hint about what it provides.
 | |
| 
 | |
| 
 | |
| 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: >
 | |
| 
 | |
| 	class AutoNew
 | |
| 	   this.name: string
 | |
| 	   this.age: number
 | |
| 	   this.gender: Gender
 | |
| 	endclass
 | |
| 
 | |
| Then The default constructor will be: >
 | |
| 
 | |
| 	def new(this.name, this.age, this.gender)
 | |
| 	enddef
 | |
| 
 | |
| All object members will be used, also private access ones.
 | |
| 
 | |
| 
 | |
| 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.
 | |
| 
 | |
| 
 | |
| ==============================================================================
 | |
| 
 | |
| 6.  Type definition					*Vim9-type* *:type*
 | |
| 
 | |
| A type definition is giving a name to a type specification.  For Example: >
 | |
| 
 | |
| 	:type ListOfStrings list<string>
 | |
| 
 | |
| TODO: more explanation
 | |
| 
 | |
| 
 | |
| ==============================================================================
 | |
| 
 | |
| 7.  Enum					*Vim9-enum* *:enum* *:endenum*
 | |
| 
 | |
| An enum is a type that can have one of a list of values.  Example: >
 | |
| 
 | |
| 	:enum Color
 | |
| 		White
 | |
| 		Red
 | |
| 		Green
 | |
| 		Blue
 | |
| 		Black
 | |
| 	:endenum
 | |
| 
 | |
| TODO: more explanation
 | |
| 
 | |
| 
 | |
| ==============================================================================
 | |
| 
 | |
| 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
 | |
| functions.  That means it is not possible to use the same function 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.
 | |
| 
 | |
| 
 | |
| Using "this.member" everywhere ~
 | |
| 
 | |
| The object members 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.
 | |
| 
 | |
| 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.
 | |
| 
 | |
| 
 | |
| 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 class members ~
 | |
| 
 | |
| 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.
 | |
| 
 | |
| 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.
 | |
| 
 | |
| 
 | |
| 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 members ~
 | |
| 
 | |
| Some users will remark that the access rules for object members 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.
 | |
| 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 membes private with an underscore ~
 | |
| 
 | |
| When an object member 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
 | |
| 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.
 | |
| 
 | |
| 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 ~
 | |
| 
 | |
| 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".
 | |
| 
 | |
| 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 provide 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:
 |