Object-oriented programming Reference

Getting started

Select the object-oriented programming profile

To undertake object-oriented programming (OOP) with Elan, you should first be reasonably familiar with procedural programming with Elan. Start by switching to the object-oriented profile from the menu at the top of the IDE. This will still allow you to do procedural programming, but changes two things:

In the following section we will describe 10 principles that define the essence of OOP, illustrated with example code - where possible from the demo programs.

Principles of object-oriented programming

Important. Every textbook on OOP differs slightly in the definition of core principles, and every language that supports OOP differs in the rules that it enforces. The following 10 principles are based on widely recognised best practices in OOP. When coding with Elan, in whichever of the supported languages - Python, VB, C#, or Java (or the Elan Reference Language) - these principles are, wherever possible, enforced by the Elan tooling. The resulting code is always valid syntax for the chosen language - but may depend upon common library methods defined in the Elan library. Also, if you go on to write code in any of those languages outside Elan, you will find that the same principles are not necessarily enforced. Though that might appear to offer you more freedom, sticking voluntarily to the principles that you learned in Elan will result in better code and fewer problems.

A class is a user-defined data structure type

In procedural programming with Elan, there is a fixed set of types available to you to represent data - the only exception being enums which offer a very limited form of 'user-defined' type (UDT). OOP makes it easy to define far richer forms of UDTs. The simplest and most common way to do this is to define a concrete class which is one of the new options made available at global (file) level when using Elan with the object-oriented profile selected.

In the Snake object-oriented demo you can find three examples of concrete classes. Only the 'signature' of each class is shown here - not the instructions contained within the classes:
+class SnakeName? inheritance?1 +constructor(parameter definitions?) 2 new code end constructor +function toStringname?(parameter definitions?) returns StringType? 3 return "undefined"value or expression?4 end function end class +class AppleName? inheritance?5 +constructor(parameter definitions?) 6 new code end constructor +function toStringname?(parameter definitions?) returns StringType? 7 return "undefined"value or expression?8 end function end class +class SquareName? inheritance?9 +constructor(parameter definitions?) 10 new code end constructor +function toStringname?(parameter definitions?) returns StringType? 11 return "undefined"value or expression?12 end function end class
+class SnakeName? inheritance? # concrete class1 +def __init__(self: Snake) -> None: 2 new code +def toStringname?(self: Snake) -> strType?: # function method3 return "undefined"value or expression?4 +class AppleName? inheritance? # concrete class5 +def __init__(self: Apple) -> None: 6 new code +def toStringname?(self: Apple) -> strType?: # function method7 return "undefined"value or expression?8 +class SquareName? inheritance? # concrete class9 +def __init__(self: Square) -> None: 10 new code +def toStringname?(self: Square) -> strType?: # function method11 return "undefined"value or expression?12
+class SnakeName? inheritance? {1 +public Snake(parameter definitions?) { 2 new code } // constructor +public stringType? toStringname?(parameter definitions?) { // function method3 return "undefined"value or expression?;4 } // function method } // class +class AppleName? inheritance? {5 +public Apple(parameter definitions?) { 6 new code } // constructor +public stringType? toStringname?(parameter definitions?) { // function method7 return "undefined"value or expression?;8 } // function method } // class +class SquareName? inheritance? {9 +public Square(parameter definitions?) { 10 new code } // constructor +public stringType? toStringname?(parameter definitions?) { // function method11 return "undefined"value or expression?;12 } // function method } // class
+Class SnakeName? inheritance?1 +Sub New(parameter definitions?) 2 new code End Sub +Function toStringname?(parameter definitions?) As StringType? 3 Return "undefined"value or expression?4 End Function End Class +Class AppleName? inheritance?5 +Sub New(parameter definitions?) 6 new code End Sub +Function toStringname?(parameter definitions?) As StringType? 7 Return "undefined"value or expression?8 End Function End Class +Class SquareName? inheritance?9 +Sub New(parameter definitions?) 10 new code End Sub +Function toStringname?(parameter definitions?) As StringType? 11 Return "undefined"value or expression?12 End Function End Class
public class Global {

+class SnakeName? inheritance? {1 +public Snake(parameter definitions?) { 2 new code } // constructor +public StringType? toStringname?(parameter definitions?) { // function method3 return "undefined"value or expression?;4 } // function method } // class +class AppleName? inheritance? {5 +public Apple(parameter definitions?) { 6 new code } // constructor +public StringType? toStringname?(parameter definitions?) { // function method7 return "undefined"value or expression?;8 } // function method } // class +class SquareName? inheritance? {9 +public Square(parameter definitions?) { 10 new code } // constructor +public StringType? toStringname?(parameter definitions?) { // function method11 return "undefined"value or expression?;12 } // function method } // class
}

Within the definition of each class you will find several property instructions. Each property has unique name (within the class). A class may defined multiple properties of the same type, for example, the SquareSquareSquareSquareSquare class in Snake defines:

+constructor(parameter definitions?) 0 new code end constructor
property x as Int Code does not parse as Elan.
property x as Int Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property x as Int Code does not parse as Elan.
+constructor(parameter definitions?) 0 new code end constructor
property y as Int Code does not parse as Elan.
property y as Int Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property y as Int Code does not parse as Elan.
or properties may have different types, for example the TuringMachineTuringMachineTuringMachineTuringMachineTuringMachine class in the Roman Numerals Turing Machine demo has these:- the class may have multiple properties of the same type but with different names, or define
+constructor(parameter definitions?) 0 new code end constructor
property initialState as String Code does not parse as Elan.
property initialState as String Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property initialState as String Code does not parse as Elan.
+constructor(parameter definitions?) 0 new code end constructor
property currentState as String Code does not parse as Elan.
property currentState as String Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property currentState as String Code does not parse as Elan.
+constructor(parameter definitions?) 0 new code end constructor
property headPosition as Int Code does not parse as Elan.
property headPosition as Int Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property headPosition as Int Code does not parse as Elan.
+constructor(parameter definitions?) 0 new code end constructor
property haltState as String Code does not parse as Elan.
property haltState as String Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property haltState as String Code does not parse as Elan.
+constructor(parameter definitions?) 0 new code end constructor
property rules as List Code does not parse as Elan.
property rules as List Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property rules as List Code does not parse as Elan.
+constructor(parameter definitions?) 0 new code end constructor
property tape as String Code does not parse as Elan.
property tape as String Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property tape as String Code does not parse as Elan.

Defining a class

Selecting concrete class from the new code menu inserts a template like this:

+class NameName? inheritance?1 +constructor(parameter definitionsparameter definitions?) 2 new code end constructor +function toStringname?(parameter definitionsparameter definitions?) returns StringType? 3 return "undefined"value or expression?4 end function end class
+class NameName? inheritance? # concrete class1 +def __init__(self: Name) -> None: 2 new code +def toStringname?(self: Name) -> strType?: # function method3 return "undefined"value or expression?4
+class NameName? inheritance? {1 +public Name(parameter definitionsparameter definitions?) { 2 new code } // constructor +public stringType? toStringname?(parameter definitionsparameter definitions?) { // function method3 return "undefined"value or expression?;4 } // function method } // class
+Class NameName? inheritance?1 +Sub New(parameter definitionsparameter definitions?) 2 new code End Sub +Function toStringname?(parameter definitionsparameter definitions?) As StringType? 3 Return "undefined"value or expression?4 End Function End Class
public class Global {

+class NameName? inheritance? {1 +public Name(parameter definitionsparameter definitions?) { 2 new code } // constructor +public StringType? toStringname?(parameter definitionsparameter definitions?) { // function method3 return "undefined"value or expression?;4 } // function method } // class
}
where the only thing you must provide is a unique name for the class, which must start with a capital letter, followed by other letters, digits or underscores. (We will look at the roles of the constructor, and the toString function later on.)

An object is an instance of a class

Every object in code is an instance of a specific class. The class may be thought of as a 'template' for defining instances. Each instance holds its own specific value for each of the properties defined in the class.

An new object may be created and assigned to a variable definition, for example:

variable applename? set to new Apple()value or expression?0
applename? = Apple()value or expression? # variable definition0
var applename? = new Apple()value or expression?;0
Dim applename? = New Apple()value or expression? ' variable definition0
var applename? = new Apple()value or expression?;0
variable snakename? set to new Snake()value or expression?0
snakename? = Snake()value or expression? # variable definition0
var snakename? = new Snake()value or expression?;0
Dim snakename? = New Snake()value or expression? ' variable definition0
var snakename? = new Snake()value or expression?;0

Note that the type of each variable is determined by the class of the instance so that the appleappleappleappleapple variable may subsequently be reassigned to a different instance of AppleAppleAppleAppleApple but may not be reassigned to an instance of a different class (nor any other type).

A new instance may also be created as part of an expression without having to be assigned to a variable, for example:

return new Square(newX, newY) Code does not parse as Elan.
return new Square(newX, newY) Code does not parse as Elan.
return new Square(newX, newY) Code does not parse as Elan.
return new Square(newX, newY) Code does not parse as Elan.
return new Square(newX, newY) Code does not parse as Elan.

Reading properties

Wherever you have a reference to an object, your code may read and make use of the publicly-visible properties of that object using 'dot syntax', for example (from the Pathfinder demo):

+if distViaCurrent < neighbour.distFromStartcondition? then 0 new code end if
+if distViaCurrent < neighbour.distFromStartcondition?: 0 new code
+if (distViaCurrent < neighbour.distFromStartcondition?) { 0 new code } // if
+If distViaCurrent < neighbour.distFromStartcondition? Then 0 new code End If
+if (distViaCurrent < neighbour.distFromStartcondition?) { 0 new code } // if
is comparing the value of the variable distViaCurrentdistViaCurrentdistViaCurrentdistViaCurrentdistViaCurrent to the property named distFromStartdistFromStartdistFromStartdistFromStartdistFromStart of the object named neighbourneighbourneighbourneighbourneighbour.

The constructor initialises the properties

When coding with Elan every class must define a constructor, the template for which is created when you define a new class, and which may be edited, though not deleted. The role of the constructor is to initialise the properties whenever an instance of that class is created.

The only properties that don't need to be initialised are properties of type IntintintIntegerint, FloatfloatdoubleDoubledouble, or BooleanboolboolBooleanbool - which are automatically initialised to the default value of each type - 00000, 0.00.00.00.00.0, or falseFalsefalseFalsefalse respectively. All other properties must be explicitly initialised within the constructor. Here is simple example of properties being initialised within the constructor for SquareSquareSquareSquareSquare in the Snake demo:

constructor(x as Int, y as Int) reassign this.x to x reassign this.y to y end constructor Code does not parse as Elan.
constructor(x as Int, y as Int) reassign this.x to x reassign this.y to y end constructor Code does not parse as Elan.
constructor(x as Int, y as Int) reassign this.x to x reassign this.y to y end constructor Code does not parse as Elan.
constructor(x as Int, y as Int) reassign this.x to x reassign this.y to y end constructor Code does not parse as Elan.
constructor(x as Int, y as Int) reassign this.x to x reassign this.y to y end constructor Code does not parse as Elan.

Note:

Objects may have associations to other objects

So far we have looked at properties where the type is one of the standard library types. However, a class may define one or more properties where the type is a class - either of the same type as the class in which the property is defined, or - more commonly - of a different class. Each such property is referred to as an 'association' or 'associated object'. If the type of a property is a list, or other data structure, where the member type is a class, then it is often referred to as a 'multiple association' - with the ordinary kind of association referred to as 'single association'. The SnakeSnakeSnakeSnakeSnake class shows both kinds of association (only relevant parts of the class are shown only here):

+class SnakeName? inheritance?1 +constructor(parameter definitionsparameter definitions?) 2 variable tailname? set to new Square(20, 15)value or expression?3 reassign this.currentDirvariableName? to Direction.rightvalue or expression?4 reassign this.bodyvariableName? to [tail]value or expression?5 reassign this.headvariableName? to tail.getAdjacentSquare(this.currentDir)value or expression?6 reassign this.priorTailvariableName? to tailvalue or expression?7 end constructor private property currentDirname? as DirectionType?8 private property headname? as SquareType?9 private property bodyname? as List<of Square>Type?10 private property priorTailname? as SquareType?11 +function toStringname?(parameter definitions?) returns StringType? 12 return "undefined"value or expression?13 end function end class
+class SnakeName? inheritance? # concrete class1 +def __init__(self: Snake) -> None: 2 tailname? = Square(20, 15)value or expression? # variable definition3 self.currentDirvariableName? = Direction.rightvalue or expression? # reassign variable4 self.bodyvariableName? = [tail]value or expression? # reassign variable5 self.headvariableName? = tail.getAdjacentSquare(self.currentDir)value or expression? # reassign variable6 self.priorTailvariableName? = tailvalue or expression? # reassign variable7 currentDirname?: DirectionType? # private property8 headname?: SquareType? # private property9 bodyname?: list[Square]Type? # private property10 priorTailname?: SquareType? # private property11 +def toStringname?(self: Snake) -> strType?: # function method12 return "undefined"value or expression?13
+class SnakeName? inheritance? {1 +public Snake(parameter definitionsparameter definitions?) { 2 var tailname? = new Square(20, 15)value or expression?;3 this.currentDirvariableName? = Direction.rightvalue or expression?; // reassign variable4 this.bodyvariableName? = [tail]value or expression?; // reassign variable5 this.headvariableName? = tail.getAdjacentSquare(this.currentDir)value or expression?; // reassign variable6 this.priorTailvariableName? = tailvalue or expression?; // reassign variable7 } // constructor private DirectionType? currentDirname? {get; private set;} // private property8 private SquareType? headname? {get; private set;} // private property9 private List<Square>Type? bodyname? {get; private set;} // private property10 private SquareType? priorTailname? {get; private set;} // private property11 +public stringType? toStringname?(parameter definitions?) { // function method12 return "undefined"value or expression?;13 } // function method } // class
+Class SnakeName? inheritance?1 +Sub New(parameter definitionsparameter definitions?) 2 Dim tailname? = New Square(20, 15)value or expression? ' variable definition3 Me.currentDirvariableName? = Direction.rightvalue or expression? ' reassign variable4 Me.bodyvariableName? = {tail}value or expression? ' reassign variable5 Me.headvariableName? = tail.getAdjacentSquare(Me.currentDir)value or expression? ' reassign variable6 Me.priorTailvariableName? = tailvalue or expression? ' reassign variable7 End Sub Private Property currentDirname? As DirectionType?8 Private Property headname? As SquareType?9 Private Property bodyname? As List(Of Square)Type?10 Private Property priorTailname? As SquareType?11 +Function toStringname?(parameter definitions?) As StringType? 12 Return "undefined"value or expression?13 End Function End Class
public class Global {

+class SnakeName? inheritance? {1 +public Snake(parameter definitionsparameter definitions?) { 2 var tailname? = new Square(20, 15)value or expression?;3 this.currentDirvariableName? = Direction.rightvalue or expression?; // reassign variable4 this.bodyvariableName? = [tail]value or expression?; // reassign variable5 this.headvariableName? = tail.getAdjacentSquare(this.currentDir)value or expression?; // reassign variable6 this.priorTailvariableName? = tailvalue or expression?; // reassign variable7 } // constructor private DirectionType? currentDirname?; // private property8 private SquareType? headname?; // private property9 private List<Square>Type? bodyname?; // private property10 private SquareType? priorTailname?; // private property11 +public StringType? toStringname?(parameter definitions?) { // function method12 return "undefined"value or expression?;13 } // function method } // class
}

Note:

Classes provide behaviour through methods

In the last code example tail.getAdjacentSquare(this.currentDir)tail.getAdjacentSquare(self.currentDir)tail.getAdjacentSquare(this.currentDir)tail.getAdjacentSquare(Me.currentDir)tail.getAdjacentSquare(this.currentDir) calls a function method, using 'dot syntax' on the the variable tailtailtailtailtail, which is an instance of class SquareSquareSquareSquareSquare. Defining a function method, or a procedure method, on a class has these advantages:

  • they are invoked on an instance using 'dot-syntax' as we shown in the previous example
  • they have access to all the properties belonging to that same instance, and can also invoke other methods on the same object, in both cases prefixed by thisselfMethisMe dot.

Properties on a class define the data (or 'state') for each instance of the class; methods define the behaviour for the object.

A function method provides information based on the parameters specified in combination with the object's properties

Function methods defined on a class, are similar to global functions in that they act like 'queries', returning information that is determined by the values of the parameters. The difference is that a function method defined on a class, may additionally make use of information obtained from the properties of the object (directly or indirectly) - to determine the information to be returned. But like a global function, a function method defined on a class may not cause any side effects, which means that it may not update any property. The body of the getAdjacentSquare accesses the objects xxxxx and yyyyy properties and uses this, together with the DirectionDirectionDirectionDirectionDirection parameter to create and return a new instance of SquareSquareSquareSquareSquare with different xxxxx and yyyyy properties:

+function getAdjacentSquarename?(d as Directionparameter definitions?) returns SquareType? 1 variable newXname? set to this.xvalue or expression?2 variable newYname? set to this.yvalue or expression?3 +if d is Direction.leftcondition? then 4 reassign newXvariableName? to this.x - 1value or expression?5 elif d is Direction.rightcondition? then6 reassign newXvariableName? to this.x + 1value or expression?7 elif d is Direction.upcondition? then8 reassign newYvariableName? to this.y - 1value or expression?9 elif d is Direction.downcondition? then10 reassign newYvariableName? to this.y + 1value or expression?11 end if return new Square(newX, newY)value or expression?12 end function
+def getAdjacentSquarename?(d: Directionparameter definitions?) -> SquareType?: # function1 newXname? = self.xvalue or expression? # variable definition2 newYname? = self.yvalue or expression? # variable definition3 +if d == Direction.leftcondition?: 4 newXvariableName? = self.x - 1value or expression? # reassign variable5 elif d == Direction.rightcondition?: # else if6 newXvariableName? = self.x + 1value or expression? # reassign variable7 elif d == Direction.upcondition?: # else if8 newYvariableName? = self.y - 1value or expression? # reassign variable9 elif d == Direction.downcondition?: # else if10 newYvariableName? = self.y + 1value or expression? # reassign variable11 return Square(newX, newY)value or expression?12
+static SquareType? getAdjacentSquarename?(Direction dparameter definitions?) { // function1 var newXname? = this.xvalue or expression?;2 var newYname? = this.yvalue or expression?;3 +if (d == Direction.leftcondition?) { 4 newXvariableName? = this.x - 1value or expression?; // reassign variable5 } else if (d == Direction.rightcondition?) {6 newXvariableName? = this.x + 1value or expression?; // reassign variable7 } else if (d == Direction.upcondition?) {8 newYvariableName? = this.y - 1value or expression?; // reassign variable9 } else if (d == Direction.downcondition?) {10 newYvariableName? = this.y + 1value or expression?; // reassign variable11 } // if return new Square(newX, newY)value or expression?;12 } // function
+Function getAdjacentSquarename?(d As Directionparameter definitions?) As SquareType? 1 Dim newXname? = Me.xvalue or expression? ' variable definition2 Dim newYname? = Me.yvalue or expression? ' variable definition3 +If d = Direction.leftcondition? Then 4 newXvariableName? = Me.x - 1value or expression? ' reassign variable5 ElseIf d = Direction.rightcondition? Then6 newXvariableName? = Me.x + 1value or expression? ' reassign variable7 ElseIf d = Direction.upcondition? Then8 newYvariableName? = Me.y - 1value or expression? ' reassign variable9 ElseIf d = Direction.downcondition? Then10 newYvariableName? = Me.y + 1value or expression? ' reassign variable11 End If Return New Square(newX, newY)value or expression?12 End Function
public class Global {

+static SquareType? getAdjacentSquarename?(Direction dparameter definitions?) { // function1 var newXname? = this.xvalue or expression?;2 var newYname? = this.yvalue or expression?;3 +if (d == Direction.leftcondition?) { 4 newXvariableName? = this.x - 1value or expression?; // reassign variable5 } else if (d == Direction.rightcondition?) {6 newXvariableName? = this.x + 1value or expression?; // reassign variable7 } else if (d == Direction.upcondition?) {8 newYvariableName? = this.y - 1value or expression?; // reassign variable9 } else if (d == Direction.downcondition?) {10 newYvariableName? = this.y + 1value or expression?; // reassign variable11 } // if return new Square(newX, newY)value or expression?;12 } // function
}

The toString function method returns a string that represents the object in some way. It is called automatically whenever you call print with a variable that holds an instance of the class. If you don't need to print an object in text form on the display, it is safe to leave the default implementation toString - that was created when you added the class, and which returns an empty string """""""""". If you do intend to print an object in text form then you can define the string to be returned, incorporating one or more of the object's properties to help identify the particular object. For example, you might edit the method defined on the SnakeSnakeSnakeSnakeSnake class to:

+function toStringname?(parameter definitionsparameter definitions?) returns StringType? 1 return $"A snake with {this.body.length() + 1} segments"value or expression?2 end function
+def toStringname?(parameter definitionsparameter definitions?) -> strType?: # function1 return f"A snake with {self.body.length() + 1} segments"value or expression?2
+static stringType? toStringname?(parameter definitionsparameter definitions?) { // function1 return $"A snake with {this.body.length() + 1} segments"value or expression?;2 } // function
+Function toStringname?(parameter definitionsparameter definitions?) As StringType? 1 Return $"A snake with {Me.body.length() + 1} segments"value or expression?2 End Function
public class Global {

+static StringType? toStringname?(parameter definitionsparameter definitions?) { // function1 return String.format("A snake with % segments", this.body.length() + 1)value or expression?;2 } // function
}
(You might like to try this out, temporarilyadding the instruction print(snakearguments?)0print(snakearguments?)0Console.WriteLine(snakearguments?); // print0Console.WriteLine(snakearguments?) ' print0System.out.println(snakearguments?); // print0 into the main routine.)

A procedure method changes the state of the object, and/or interacts with the system

Procedure methods, like function methods, may define parameters and may also read the values of the object's properties. Like global procedures they may also generate side effects, and this includes changing the values of the object's properties, and may call other procedures, on the same object, or associated objects. For example, the clockTick procedure method defined in the SnakeSnakeSnakeSnakeSnake class, changes the position of various segments, and, if the headheadheadheadhead now covers the same space as the appleappleappleappleapple changes the position of the apple (passed into the method as a parameter) by calling the newRandomPosition procedure method on appleappleappleappleapple:

+procedure clockTickname?(key as String, apple as Appleparameter definitions?) 1 call this.setDirectionprocedureName?(keyarguments?)2 reassign this.priorTailvariableName? to this.body[0]value or expression?3 variable bodyname? set to this.bodyvalue or expression?4 call body.appendprocedureName?(this.headarguments?)5 reassign this.headvariableName? to this.head.getAdjacentSquare(this.currentDir)value or expression?6 +if this.head.equals(apple.location)condition? then 7 call apple.newRandomPositionprocedureName?(thisarguments?)8 else9 reassign this.bodyvariableName? to this.body.subList(1, this.body.length())value or expression?10 end if end procedure
+def clockTickname?(key: str, apple: Appleparameter definitions?) -> None: # procedure1 self.setDirectionprocedureName?(keyarguments?) # call procedure2 self.priorTailvariableName? = self.body[0]value or expression? # reassign variable3 bodyname? = self.bodyvalue or expression? # variable definition4 body.appendprocedureName?(self.headarguments?) # call procedure5 self.headvariableName? = self.head.getAdjacentSquare(self.currentDir)value or expression? # reassign variable6 +if self.head.equals(apple.location)condition?: 7 apple.newRandomPositionprocedureName?(selfarguments?) # call procedure8 else:9 self.bodyvariableName? = self.body.subList(1, self.body.length())value or expression? # reassign variable10
+static void clockTickname?(string key, Apple appleparameter definitions?) { // procedure1 this.setDirectionprocedureName?(keyarguments?); // call procedure2 this.priorTailvariableName? = this.body[0]value or expression?; // reassign variable3 var bodyname? = this.bodyvalue or expression?;4 body.appendprocedureName?(this.headarguments?); // call procedure5 this.headvariableName? = this.head.getAdjacentSquare(this.currentDir)value or expression?; // reassign variable6 +if (this.head.equals(apple.location)condition?) { 7 apple.newRandomPositionprocedureName?(thisarguments?); // call procedure8 } else {9 this.bodyvariableName? = this.body.subList(1, this.body.length())value or expression?; // reassign variable10 } // if } // procedure
+Sub clockTickname?(key As String, apple As Appleparameter definitions?) ' procedure1 Me.setDirectionprocedureName?(keyarguments?) ' call procedure2 Me.priorTailvariableName? = Me.body[0]value or expression? ' reassign variable3 Dim bodyname? = Me.bodyvalue or expression? ' variable definition4 body.appendprocedureName?(Me.headarguments?) ' call procedure5 Me.headvariableName? = Me.head.getAdjacentSquare(Me.currentDir)value or expression? ' reassign variable6 +If Me.head.equals(apple.location)condition? Then 7 apple.newRandomPositionprocedureName?(Mearguments?) ' call procedure8 Else9 Me.bodyvariableName? = Me.body.subList(1, Me.body.length())value or expression? ' reassign variable10 End If End Sub
public class Global {

+static void clockTickname?(String key, Apple appleparameter definitions?) { // procedure1 this.setDirectionprocedureName?(keyarguments?); // call procedure2 this.priorTailvariableName? = this.body[0]value or expression?; // reassign variable3 var bodyname? = this.bodyvalue or expression?;4 body.appendprocedureName?(this.headarguments?); // call procedure5 this.headvariableName? = this.head.getAdjacentSquare(this.currentDir)value or expression?; // reassign variable6 +if (this.head.equals(apple.location)condition?) { 7 apple.newRandomPositionprocedureName?(thisarguments?); // call procedure8 } else {9 this.bodyvariableName? = this.body.subList(1, this.body.length())value or expression?; // reassign variable10 } // if } // procedure
}

Objects should represent the principal nouns in the application domain

The word 'encapsulation' is often used in descriptions of object-oriented programming. But that word has two rather different meanings in plain English. Fortunately, both of the meanings of 'encapsulation' apply.

The first meaning of encapsulation is similar to 'represents' - as in 'Our school motto encapsulates the ethos and culture of the school'. (The second meaning will be addressed in the next principle.) Thus, objects should represent, or encapsulate, the principal concepts in the application domain. A rule of thumb is that the classes defined in a program should correspond to the main nouns used when describing the purpose and operation of the program in plain English. You can see this in the Snake demo, where the code defines three classes, named SnakeSnakeSnakeSnakeSnake, AppleAppleAppleAppleApple, and SquareSquareSquareSquareSquare - all nouns that would be recognised by anyone who understands how to play the game Snake, but who does not necessarily know anything about programming.

One of the claimed benefits of this is that it makes programs easier to read, and to update in response to new requirements, because the structure of the code corresponds more closely to the language of the application domain. If the user, or the business customer that owns the game, says that they want the snake to be a different colour, or for there to be two apples at once (in different positions) - it will be easier for the programmer to identity where within the program the code must be changed that in a procedural implementation. You can try this for yourself, by asking a user to specify similar small changes to the presentation and/or game rules, and testing how long it takes you to update the procedural, and object-oriented, versions of the Snake program as required.

Importantly, the class does not just represent all the information associated with the noun - it also encapsulates the behaviour needed from that thing, in the form of methods. This is sometimes stated as representing the know-whats for the thing (as properties), and the know how-to's as methods.

Objects should hide state behind methods, where possible

This principle corresponds to the second meaning of 'encapsulation': sealing. Think of the related word: 'capsule'. You can only access the capabilities of an object through its public members. All the 'members' defined on a class (that is: its properties and methods) are public - although properties are public only for reading, not for modifying. However, it is possible to make any property or method private - such that it can be accessed from code within the class, but not from outside. To do this, you bring up the context menu on the instruction (by right-mouse-click or Ctrl-m) and select make private (a change that can be reversed with make public).A method might be made private because it just provides help to one or more public methods, but is not expected to be useful in its own right. However, a property is often made private to implement the principle of 'information' hiding.

Some textbooks suggest that every property - for example dateOfBirthdateOfBirthdateOfBirthdateOfBirthdateOfBirth - should be private, and that access should be via a 'getter' - a function method typically named getDateOfBirth, and a 'setter' - a procedure method typically named setDateOfBirth. But that idea is not really hiding the information. Real information hiding is to prevent external code from accessing the data and instead provide them with public methods that provide more useful services. For example, in the Snake demo, all the headheadheadheadhead and bodybodybodybodybody properties are private, but the function method bodyCovers allows external code to test whether the snake (body or head) covers a given square, passed in as a parameter. This method is called from within the newRandomPosition defined by the AppleAppleAppleAppleApple class, to ensure that the next apple is not created directly underneath the snake.

A concrete class may optionally inherit from an abstract class

In many applications two or more of the classes will be seen as having quite a lot in common. When this situation identified, there might be a case for making them inherit from a common 'abstract' class. For example, in a school administration system, you might define classes named Pupil, Teacher, and Parent, but these might all inherit from a common abstract class named Individual. The Person class can then be described as the 'superclass' of the other three, and they as the 'subclasses' of Individual.

An abstract class is created, at global level, with an abstract class instruction, and the template requires a name (beginning with a capital letter) to be specified.

The main differences between an abstract and a concrete super-class are:

  • an abstract class cannot be instantiated. If, using the example above, you were to write: new Individual()Individual()new Individual()New Individual()new Individual() you will get a compile-error message: Individual must be concrete to create instance.
  • an abstract class has no constructor (it also does not need toString because every concrete class must define one.
  • the new code menus, as well as offering the property, procedure method, and function method options as in a concrete class, also offer abstract procedure method, and abstract function method. A concrete class must provide a concrete (normal) implementation of each abstract member defined in its superclass, or any abstract class above it in the hierarchy.

Earlier we learned about association between objects. Association may be characterised by the 'has a' relationship, as in a 'Student has a personal tutor' (meaning that the Student class defines a property named 'tutor' - which is of type Teacher. Bt contrast inheritance may be characterised as an 'is a' relationship, as in 'a Student is an Individual'.

There are two principal motivations from making two or more classes inherit from a common abstract class:

  • To reduce repetition of common properties and methods - which may be defined on the abstract superclass and then not repeated in the subclasses. For example it might be that Individual define a (string) property named 'emailAddress' and define a common procedure method named 'sendMessage'.
  • To allow instances of any of the subclasses to be handled by external code without having to know the individual subclass of each instance. This principle is known as 'type substitutability' and is a form of a more general principle know as 'polymorphism' - meaning 'of multiple forms'. For example, the user might define a special interest group that included some pupils, parents, and teachers, and so would be defined as a list of type Individual. This then makes it easy to send a message to all the members of the group, by invoking the method 'sendMessage' on each member in turn, without having to identify the type each member.

Some textbooks emphasise the first of those two motivations, but it is the second one that is far more important. Indeed, inheritance is the most over-used, and the most frequently abused, concepts in OOP. For that reason, irrespective of the programming language being used, Elan deliberately enforces principles of good OOP from the outset. For example:

  • Each class may inherit from one superclass only
  • The superclass must be an abstract class - no class may inherit from another concrete class.
  • A abstract class may inherit from another abstract class - to form a 'hierarchy' of abstraction, if desired.
  • A concrete class may inherit concrete (ordinary) members from its superclass, but it may not override them.

The last point is controversial - since all the supported programming languages allow overriding. But overriding (of concrete methods) breaks the 'O' of the widely-quoted 'SOLID' principles of oop: that classes should be 'Open for extension, but closed for modification'.

Application logic should be well-distributed across classes

In Elan, an object-oriented program still needs a procedural main method to execute.

The Java language is unusual in that even the main routine must defined within a class - Elan handles this by including a class named Global Code does not parse as Elan.Global Code does not parse as Elan.Global Code does not parse as Elan.Global Code does not parse as Elan.Global Code does not parse as Elan. as part of the file boilerplate, but treats the instructions directly within this class as being 'global' in nature. Any user-defined classes are added within this, and Elan does not allow properties to be added directly within that Global Code does not parse as Elan.Global Code does not parse as Elan.Global Code does not parse as Elan.Global Code does not parse as Elan.Global Code does not parse as Elan. class since these would be, effectively, just global variables in disguise.

In general good OOP design means aiming to keep the functionality well-distributed among the user-defined classes. Inevitably, some classes are going to define more methods, and/or more-complex methods, than others. But you should be wary of a situation where one class is doing most of the work, and the other classes act as little more than data structures without encapsulated behaviour. All too often that one big class is instantiated only once (this pattern is known as a 'singleton'), by the main routine. While singletons are not always a bad idea, a singleton that does most of the work of the program - sometimes irreverently referred to as a 'god class' - is really just a procedural program in disguise.

A useful check on your design is to look at the distribution of instructions in the program (not counting instructions within tests, which are not part of the executable program) as in the the following summary of the Snake - object-oriented demo:

622
ClassPropertiesMethods (excl. constructor & toString)No. of instructionsNotes
SnakeSnakeSnakeSnakeSnake457singleton - but an extended version of the game might have multiple instances.
SnakeSnakeSnakeSnakeSnake116ditto
SquareSquareSquareSquareSquare222Many instances created in the game. Both Snake and Apple have associations to one or more Squares
Instructions not in a user-defined class1213e.g. in main method, enums, or global procedures/functions, excluding tests
Total instructions96excluding tests

Global Instructions

Coding with Elan using the object-oriented profile offers all the global instructions uses in the procedural profile plus two new ones: concrete class and abstract class

Concrete class

Examples of concrete classes

From the Snake - object-oriented demo:

+class AppleName? inheritance?1 +constructor(parameter definitionsparameter definitions?) 2 reassign this.locationvariableName? to new Square(0, 0)value or expression?3 end constructor property locationname? as SquareType?4 +procedure newRandomPositionname?(snake as Snakeparameter definitions?) 5 variable changePositionname? set to truevalue or expression?6 +while changePositioncondition? 7 variable ranXname? set to randint(0, 39)value or expression?8 variable ranYname? set to randint(0, 29)value or expression?9 reassign this.locationvariableName? to new Square(ranX, ranY)value or expression?10 +if not snake.bodyCovers(this.location)condition? then 11 reassign changePositionvariableName? to falsevalue or expression?12 end if end while end procedure +procedure updateBlocksname?(blocks as List<of List<of Int>>parameter definitions?) 13 reassign blocks[this.location.x][this.location.y]variableName? to redvalue or expression?14 end procedure +function toStringname?(parameter definitionsparameter definitions?) returns StringType? 15 return $"an Apple at {this.location}"value or expression?16 end function end class
+class AppleName? inheritance? # concrete class1 +def __init__(self: Apple) -> None: 2 self.locationvariableName? = Square(0, 0)value or expression? # reassign variable3 locationname?: SquareType? # property4 +def newRandomPositionname?(self: Apple, snake: Snakeparameter definitions?) -> None: # procedure method5 changePositionname? = Truevalue or expression? # variable definition6 +while changePositioncondition?: 7 ranXname? = randint(0, 39)value or expression? # variable definition8 ranYname? = randint(0, 29)value or expression? # variable definition9 self.locationvariableName? = Square(ranX, ranY)value or expression? # reassign variable10 +if not snake.bodyCovers(self.location)condition?: 11 changePositionvariableName? = Falsevalue or expression? # reassign variable12 +def updateBlocksname?(self: Apple, blocks: list[list[int]]parameter definitions?) -> None: # procedure method13 blocks[self.location.x][self.location.y]variableName? = redvalue or expression? # reassign variable14 +def toStringname?(self: Apple) -> strType?: # function method15 return f"an Apple at {self.location}"value or expression?16
+class AppleName? inheritance? {1 +public Apple(parameter definitionsparameter definitions?) { 2 this.locationvariableName? = new Square(0, 0)value or expression?; // reassign variable3 } // constructor public SquareType? locationname? {get; private set;} // property4 +public void newRandomPositionname?(Snake snakeparameter definitions?) { // procedure method5 var changePositionname? = truevalue or expression?;6 +while (changePositioncondition?) { 7 var ranXname? = randint(0, 39)value or expression?;8 var ranYname? = randint(0, 29)value or expression?;9 this.locationvariableName? = new Square(ranX, ranY)value or expression?; // reassign variable10 +if (!snake.bodyCovers(this.location)condition?) { 11 changePositionvariableName? = falsevalue or expression?; // reassign variable12 } // if } // while } // procedure method +public void updateBlocksname?(List<List<int>> blocksparameter definitions?) { // procedure method13 blocks[this.location.x][this.location.y]variableName? = redvalue or expression?; // reassign variable14 } // procedure method +public stringType? toStringname?(parameter definitionsparameter definitions?) { // function method15 return $"an Apple at {this.location}"value or expression?;16 } // function method } // class
+Class AppleName? inheritance?1 +Sub New(parameter definitionsparameter definitions?) 2 Me.locationvariableName? = New Square(0, 0)value or expression? ' reassign variable3 End Sub Property locationname? As SquareType?4 +Sub newRandomPositionname?(snake As Snakeparameter definitions?) ' procedure method5 Dim changePositionname? = Truevalue or expression? ' variable definition6 +While changePositioncondition? 7 Dim ranXname? = randint(0, 39)value or expression? ' variable definition8 Dim ranYname? = randint(0, 29)value or expression? ' variable definition9 Me.locationvariableName? = New Square(ranX, ranY)value or expression? ' reassign variable10 +If Not snake.bodyCovers(Me.location)condition? Then 11 changePositionvariableName? = Falsevalue or expression? ' reassign variable12 End If End While End Sub +Sub updateBlocksname?(blocks As List(Of List(Of Integer))parameter definitions?) ' procedure method13 blocks[Me.location.x][Me.location.y]variableName? = redvalue or expression? ' reassign variable14 End Sub +Function toStringname?(parameter definitionsparameter definitions?) As StringType? 15 Return $"an Apple at {Me.location}"value or expression?16 End Function End Class
public class Global {

+class AppleName? inheritance? {1 +public Apple(parameter definitionsparameter definitions?) { 2 this.locationvariableName? = new Square(0, 0)value or expression?; // reassign variable3 } // constructor public SquareType? locationname?; // property4 +public void newRandomPositionname?(Snake snakeparameter definitions?) { // procedure method5 var changePositionname? = truevalue or expression?;6 +while (changePositioncondition?) { 7 var ranXname? = randint(0, 39)value or expression?;8 var ranYname? = randint(0, 29)value or expression?;9 this.locationvariableName? = new Square(ranX, ranY)value or expression?; // reassign variable10 +if (!snake.bodyCovers(this.location)condition?) { 11 changePositionvariableName? = falsevalue or expression?; // reassign variable12 } // if } // while } // procedure method +public void updateBlocksname?(List<List<int>> blocksparameter definitions?) { // procedure method13 blocks[this.location.x][this.location.y]variableName? = redvalue or expression?; // reassign variable14 } // procedure method +public StringType? toStringname?(parameter definitionsparameter definitions?) { // function method15 return String.format("an Apple at %", this.location)value or expression?;16 } // function method } // class
}

From the Blackjack demo:

+class CardName? inheritance?1 property suitname? as SuitType?2 property rankname? as StringType?3 property faceDownname? as BooleanType?4 +constructor(rank as String, suit as Suit, facedown as Booleanparameter definitions?) 5 reassign this.rankvariableName? to rankvalue or expression?6 reassign this.suitvariableName? to suitvalue or expression?7 reassign this.faceDownvariableName? to facedownvalue or expression?8 end constructor +procedure turnFaceUpname?(parameter definitionsparameter definitions?) 9 reassign this.faceDownvariableName? to falsevalue or expression?10 end procedure +procedure turnFaceDownname?(parameter definitionsparameter definitions?) 11 reassign this.faceDownvariableName? to truevalue or expression?12 end procedure +function toStringname?(parameter definitionsparameter definitions?) returns StringType? 13 return $"{this.rank}{symbolForSuit(this.suit)}"value or expression?14 end function end class
+class CardName? inheritance? # concrete class1 suitname?: SuitType? # property2 rankname?: strType? # property3 faceDownname?: boolType? # property4 +def __init__(self: Card, rank: str, suit: Suit, facedown: boolparameter definitions?) -> None: 5 self.rankvariableName? = rankvalue or expression? # reassign variable6 self.suitvariableName? = suitvalue or expression? # reassign variable7 self.faceDownvariableName? = facedownvalue or expression? # reassign variable8 +def turnFaceUpname?(self: Card) -> None: # procedure method9 self.faceDownvariableName? = Falsevalue or expression? # reassign variable10 +def turnFaceDownname?(self: Card) -> None: # procedure method11 self.faceDownvariableName? = Truevalue or expression? # reassign variable12 +def toStringname?(self: Card) -> strType?: # function method13 return f"{self.rank}{symbolForSuit(self.suit)}"value or expression?14
+class CardName? inheritance? {1 public SuitType? suitname? {get; private set;} // property2 public stringType? rankname? {get; private set;} // property3 public boolType? faceDownname? {get; private set;} // property4 +public Card(string rank, Suit suit, bool facedownparameter definitions?) { 5 this.rankvariableName? = rankvalue or expression?; // reassign variable6 this.suitvariableName? = suitvalue or expression?; // reassign variable7 this.faceDownvariableName? = facedownvalue or expression?; // reassign variable8 } // constructor +public void turnFaceUpname?(parameter definitionsparameter definitions?) { // procedure method9 this.faceDownvariableName? = falsevalue or expression?; // reassign variable10 } // procedure method +public void turnFaceDownname?(parameter definitionsparameter definitions?) { // procedure method11 this.faceDownvariableName? = truevalue or expression?; // reassign variable12 } // procedure method +public stringType? toStringname?(parameter definitionsparameter definitions?) { // function method13 return $"{this.rank}{symbolForSuit(this.suit)}"value or expression?;14 } // function method } // class
+Class CardName? inheritance?1 Property suitname? As SuitType?2 Property rankname? As StringType?3 Property faceDownname? As BooleanType?4 +Sub New(rank As String, suit As Suit, facedown As Booleanparameter definitions?) 5 Me.rankvariableName? = rankvalue or expression? ' reassign variable6 Me.suitvariableName? = suitvalue or expression? ' reassign variable7 Me.faceDownvariableName? = facedownvalue or expression? ' reassign variable8 End Sub +Sub turnFaceUpname?(parameter definitionsparameter definitions?) ' procedure method9 Me.faceDownvariableName? = Falsevalue or expression? ' reassign variable10 End Sub +Sub turnFaceDownname?(parameter definitionsparameter definitions?) ' procedure method11 Me.faceDownvariableName? = Truevalue or expression? ' reassign variable12 End Sub +Function toStringname?(parameter definitionsparameter definitions?) As StringType? 13 Return $"{Me.rank}{symbolForSuit(Me.suit)}"value or expression?14 End Function End Class
public class Global {

+class CardName? inheritance? {1 public SuitType? suitname?; // property2 public StringType? rankname?; // property3 public boolType? faceDownname?; // property4 +public Card(String rank, Suit suit, bool facedownparameter definitions?) { 5 this.rankvariableName? = rankvalue or expression?; // reassign variable6 this.suitvariableName? = suitvalue or expression?; // reassign variable7 this.faceDownvariableName? = facedownvalue or expression?; // reassign variable8 } // constructor +public void turnFaceUpname?(parameter definitionsparameter definitions?) { // procedure method9 this.faceDownvariableName? = falsevalue or expression?; // reassign variable10 } // procedure method +public void turnFaceDownname?(parameter definitionsparameter definitions?) { // procedure method11 this.faceDownvariableName? = truevalue or expression?; // reassign variable12 } // procedure method +public StringType? toStringname?(parameter definitionsparameter definitions?) { // function method13 return String.format("%%", this.rank, symbolForSuit(this.suit))value or expression?;14 } // function method } // class
}

From the Pathfinder demo:

+class PointName? inheritance?1 property xname? as IntType?2 property yname? as IntType?3 property isEmptyname? as BooleanType?4 +constructor(x as Int, y as Intparameter definitions?) 5 +if (x < 0) or (y < 0)condition? then 6 reassign this.isEmptyvariableName? to truevalue or expression?7 else8 reassign this.xvariableName? to xvalue or expression?9 reassign this.yvariableName? to yvalue or expression?10 end if end constructor +function minDistToname?(p as Pointparameter definitions?) returns FloatType? 11 return sqrt(pow((p.x - this.x), 2) + pow((p.y - this.y), 2))value or expression?12 end function +function isAdjacentToname?(p as Pointparameter definitions?) returns BooleanType? 13 return (this.minDistTo(p) is 1) or (this.minDistTo(p).round(4) is sqrt(2).round(4))value or expression?14 end function # Returns the 8 theoretically-neighbouring points, whether or not within boundscomment? +function neighbouringPointsname?(parameter definitionsparameter definitions?) returns List<of Point>Type? 15 return [new Point(this.x - 1, this.y - 1), new Point(this.x, this.y - 1), new Point(this.x + 1, this.y - 1), new Point(this.x - 1, this.y), new Point(this.x + 1, this.y), new Point(this.x - 1, this.y + 1), new Point(this.x, this.y + 1), new Point(this.x + 1, this.y + 1)]value or expression?16 end function +function toStringname?(parameter definitionsparameter definitions?) returns StringType? 17 return $"{this.x},{this.y}"value or expression?18 end function end class
+class PointName? inheritance? # concrete class1 xname?: intType? # property2 yname?: intType? # property3 isEmptyname?: boolType? # property4 +def __init__(self: Point, x: int, y: intparameter definitions?) -> None: 5 +if (x < 0) or (y < 0)condition?: 6 self.isEmptyvariableName? = Truevalue or expression? # reassign variable7 else:8 self.xvariableName? = xvalue or expression? # reassign variable9 self.yvariableName? = yvalue or expression? # reassign variable10 +def minDistToname?(self: Point, p: Pointparameter definitions?) -> floatType?: # function method11 return sqrt(pow((p.x - self.x), 2) + pow((p.y - self.y), 2))value or expression?12 +def isAdjacentToname?(self: Point, p: Pointparameter definitions?) -> boolType?: # function method13 return (self.minDistTo(p) == 1) or (self.minDistTo(p).round(4) == sqrt(2).round(4))value or expression?14 # Returns the 8 theoretically-neighbouring points, whether or not within boundscomment? +def neighbouringPointsname?(self: Point) -> list[Point]Type?: # function method15 return [Point(self.x - 1, self.y - 1), Point(self.x, self.y - 1), Point(self.x + 1, self.y - 1), Point(self.x - 1, self.y), Point(self.x + 1, self.y), Point(self.x - 1, self.y + 1), Point(self.x, self.y + 1), Point(self.x + 1, self.y + 1)]value or expression?16 +def toStringname?(self: Point) -> strType?: # function method17 return f"{self.x},{self.y}"value or expression?18
+class PointName? inheritance? {1 public intType? xname? {get; private set;} // property2 public intType? yname? {get; private set;} // property3 public boolType? isEmptyname? {get; private set;} // property4 +public Point(int x, int yparameter definitions?) { 5 +if ((x < 0) || (y < 0)condition?) { 6 this.isEmptyvariableName? = truevalue or expression?; // reassign variable7 } else {8 this.xvariableName? = xvalue or expression?; // reassign variable9 this.yvariableName? = yvalue or expression?; // reassign variable10 } // if } // constructor +public doubleType? minDistToname?(Point pparameter definitions?) { // function method11 return sqrt(pow((p.x - this.x), 2) + pow((p.y - this.y), 2))value or expression?;12 } // function method +public boolType? isAdjacentToname?(Point pparameter definitions?) { // function method13 return (this.minDistTo(p) == 1) || (this.minDistTo(p).round(4) == sqrt(2).round(4))value or expression?;14 } // function method // Returns the 8 theoretically-neighbouring points, whether or not within boundscomment? +public List<Point>Type? neighbouringPointsname?(parameter definitionsparameter definitions?) { // function method15 return [new Point(this.x - 1, this.y - 1), new Point(this.x, this.y - 1), new Point(this.x + 1, this.y - 1), new Point(this.x - 1, this.y), new Point(this.x + 1, this.y), new Point(this.x - 1, this.y + 1), new Point(this.x, this.y + 1), new Point(this.x + 1, this.y + 1)]value or expression?;16 } // function method +public stringType? toStringname?(parameter definitionsparameter definitions?) { // function method17 return $"{this.x},{this.y}"value or expression?;18 } // function method } // class
+Class PointName? inheritance?1 Property xname? As IntegerType?2 Property yname? As IntegerType?3 Property isEmptyname? As BooleanType?4 +Sub New(x As Integer, y As Integerparameter definitions?) 5 +If (x < 0) Or (y < 0)condition? Then 6 Me.isEmptyvariableName? = Truevalue or expression? ' reassign variable7 Else8 Me.xvariableName? = xvalue or expression? ' reassign variable9 Me.yvariableName? = yvalue or expression? ' reassign variable10 End If End Sub +Function minDistToname?(p As Pointparameter definitions?) As DoubleType? 11 Return sqrt(pow((p.x - Me.x), 2) + pow((p.y - Me.y), 2))value or expression?12 End Function +Function isAdjacentToname?(p As Pointparameter definitions?) As BooleanType? 13 Return (Me.minDistTo(p) = 1) Or (Me.minDistTo(p).round(4) = sqrt(2).round(4))value or expression?14 End Function ' Returns the 8 theoretically-neighbouring points, whether or not within boundscomment? +Function neighbouringPointsname?(parameter definitionsparameter definitions?) As List(Of Point)Type? 15 Return {New Point(Me.x - 1, Me.y - 1), New Point(Me.x, Me.y - 1), New Point(Me.x + 1, Me.y - 1), New Point(Me.x - 1, Me.y), New Point(Me.x + 1, Me.y), New Point(Me.x - 1, Me.y + 1), New Point(Me.x, Me.y + 1), New Point(Me.x + 1, Me.y + 1)}value or expression?16 End Function +Function toStringname?(parameter definitionsparameter definitions?) As StringType? 17 Return $"{Me.x},{Me.y}"value or expression?18 End Function End Class
public class Global {

+class PointName? inheritance? {1 public intType? xname?; // property2 public intType? yname?; // property3 public boolType? isEmptyname?; // property4 +public Point(int x, int yparameter definitions?) { 5 +if ((x < 0) || (y < 0)condition?) { 6 this.isEmptyvariableName? = truevalue or expression?; // reassign variable7 } else {8 this.xvariableName? = xvalue or expression?; // reassign variable9 this.yvariableName? = yvalue or expression?; // reassign variable10 } // if } // constructor +public doubleType? minDistToname?(Point pparameter definitions?) { // function method11 return sqrt(pow((p.x - this.x), 2) + pow((p.y - this.y), 2))value or expression?;12 } // function method +public boolType? isAdjacentToname?(Point pparameter definitions?) { // function method13 return (this.minDistTo(p) == 1) || (this.minDistTo(p).round(4) == sqrt(2).round(4))value or expression?;14 } // function method // Returns the 8 theoretically-neighbouring points, whether or not within boundscomment? +public List<Point>Type? neighbouringPointsname?(parameter definitionsparameter definitions?) { // function method15 return [new Point(this.x - 1, this.y - 1), new Point(this.x, this.y - 1), new Point(this.x + 1, this.y - 1), new Point(this.x - 1, this.y), new Point(this.x + 1, this.y), new Point(this.x - 1, this.y + 1), new Point(this.x, this.y + 1), new Point(this.x + 1, this.y + 1)]value or expression?;16 } // function method +public StringType? toStringname?(parameter definitionsparameter definitions?) { // function method17 return String.format("%,%", this.x, this.y)value or expression?;18 } // function method } // class
}

What you need to know about a concrete class

  • A concrete class is a type of user-defined data-structure that holds data in properties, as well as methods to provide behaviour.
  • A new class is defined by adding a concrete class instruction at global level, which requires a name (starting with an initial capital) to be specified
  • All concrete classes must define a constructor, and a toString method, but the template for the class provides minimal valid implementations of these which you may expand or modify.
  • You create an instance of the class using the class name and providing any require arguments (as specified by the class' constructor) for example: variable applename? set to new Apple()value or expression?0applename? = Apple()value or expression? # variable definition0var applename? = new Apple()value or expression?;0Dim applename? = New Apple()value or expression? ' variable definition0var applename? = new Apple()value or expression?;0 or variable startname? set to new Point(5, 5)value or expression?0startname? = Point(5, 5)value or expression? # variable definition0var startname? = new Point(5, 5)value or expression?;0Dim startname? = New Point(5, 5)value or expression? ' variable definition0var startname? = new Point(5, 5)value or expression?;0.
  • All properties must be initialised within the constructor (except for numeric or Boolean properties which have the default value for their type until changed)
  • Function methods provide information derived from the properties of the instance, and or from parameters provided to it.

  • toString is an ordinary function method but with a specific name; it is called if an instance of the class is printed, or inserted into an interpolated string; typically toString will include one or more of the object's properties.
  • Procedure methods make changes to the properties of the instance and/or depend on the system in some way.

  • Any property or method may be made private (using an action on the context menu for the instruction), such that it may ony be accessed by code within the same class.
  • Once you have an instance in a variable you can access its public methods using dot-syntax; however, properties may be only be read by external code - to modify properties from outside you need to go through a procedure method defined on that class.
  • A class may optionally inherit from a single abstract class.

Abstract class

Example of an abstract class

From the Blackjack demo, the abstract class PlayerPlayerPlayerPlayerPlayer - which is inherited by the concrete classes HumanPlayerHumanPlayerHumanPlayerHumanPlayerHumanPlayer and Dealer - defines public concrete properties, public concrete methods, plus three abstract methods, two of which are accompanied by private 'helper' methods to which subclasses may delegate part or all of their implementations.

abstract class Player property name as String property points as Int property cards as List property handTotal as Int property softAce as Boolean property status as Status property hasTurn as Boolean procedure startTurn() if this.status is Status.active then reassign this.hasTurn to true end if end procedure procedure determineOutcomeAndUpdatePoints(dealer as Dealer) variable playerOutcome set to determinePlayerOutcome(dealer, this) if playerOutcome is Outcome.winDouble then call this.changePointsBy(2) call dealer.changePointsBy(-2) elif playerOutcome is Outcome.win then call this.changePointsBy(1) call dealer.changePointsBy(-1) elif playerOutcome is Outcome.lose then call this.changePointsBy(-1) call dealer.changePointsBy(1) end if end procedure procedure evaluateStatus(newCard as Card) if (this.cardCount() is 2) and (this.handTotal is 21) then reassign this.status to Status.blackjack elif (this.handTotal > 21) and (this.softAce) then reassign this.handTotal to this.handTotal - 10 reassign this.softAce to false elif this.handTotal > 21 then reassign this.status to Status.bust elif this.handTotal is 21 then reassign this.status to Status.standing end if if this.status isnt Status.active then reassign this.hasTurn to false end if end procedure procedure stand() reassign this.status to Status.standing reassign this.hasTurn to false end procedure procedure draw() variable newCard set to dealCard(random()) variable cards set to this.cards call cards.append(newCard) if newCard.rank.equals("A") then call this.addAce() else reassign this.handTotal to this.handTotal + rankValue()[newCard.rank] end if call this.evaluateStatus(newCard) end procedure procedure addAce() if this.softAce then reassign this.handTotal to this.handTotal + 1 else reassign this.handTotal to this.handTotal + 11 reassign this.softAce to true end if end procedure function cardCount() returns Int return this.cards.length() end function procedure changePointsBy(amount as Int) reassign this.points to this.points + amount end procedure abstract procedure newHand() private procedure newHandHelper() reassign this.hasTurn to false reassign this.softAce to false reassign this.cards to new List
abstract class Player property name as String property points as Int property cards as List property handTotal as Int property softAce as Boolean property status as Status property hasTurn as Boolean procedure startTurn() if this.status is Status.active then reassign this.hasTurn to true end if end procedure procedure determineOutcomeAndUpdatePoints(dealer as Dealer) variable playerOutcome set to determinePlayerOutcome(dealer, this) if playerOutcome is Outcome.winDouble then call this.changePointsBy(2) call dealer.changePointsBy(-2) elif playerOutcome is Outcome.win then call this.changePointsBy(1) call dealer.changePointsBy(-1) elif playerOutcome is Outcome.lose then call this.changePointsBy(-1) call dealer.changePointsBy(1) end if end procedure procedure evaluateStatus(newCard as Card) if (this.cardCount() is 2) and (this.handTotal is 21) then reassign this.status to Status.blackjack elif (this.handTotal > 21) and (this.softAce) then reassign this.handTotal to this.handTotal - 10 reassign this.softAce to false elif this.handTotal > 21 then reassign this.status to Status.bust elif this.handTotal is 21 then reassign this.status to Status.standing end if if this.status isnt Status.active then reassign this.hasTurn to false end if end procedure procedure stand() reassign this.status to Status.standing reassign this.hasTurn to false end procedure procedure draw() variable newCard set to dealCard(random()) variable cards set to this.cards call cards.append(newCard) if newCard.rank.equals("A") then call this.addAce() else reassign this.handTotal to this.handTotal + rankValue()[newCard.rank] end if call this.evaluateStatus(newCard) end procedure procedure addAce() if this.softAce then reassign this.handTotal to this.handTotal + 1 else reassign this.handTotal to this.handTotal + 11 reassign this.softAce to true end if end procedure function cardCount() returns Int return this.cards.length() end function procedure changePointsBy(amount as Int) reassign this.points to this.points + amount end procedure abstract procedure newHand() private procedure newHandHelper() reassign this.hasTurn to false reassign this.softAce to false reassign this.cards to new List
abstract class Player property name as String property points as Int property cards as List property handTotal as Int property softAce as Boolean property status as Status property hasTurn as Boolean procedure startTurn() if this.status is Status.active then reassign this.hasTurn to true end if end procedure procedure determineOutcomeAndUpdatePoints(dealer as Dealer) variable playerOutcome set to determinePlayerOutcome(dealer, this) if playerOutcome is Outcome.winDouble then call this.changePointsBy(2) call dealer.changePointsBy(-2) elif playerOutcome is Outcome.win then call this.changePointsBy(1) call dealer.changePointsBy(-1) elif playerOutcome is Outcome.lose then call this.changePointsBy(-1) call dealer.changePointsBy(1) end if end procedure procedure evaluateStatus(newCard as Card) if (this.cardCount() is 2) and (this.handTotal is 21) then reassign this.status to Status.blackjack elif (this.handTotal > 21) and (this.softAce) then reassign this.handTotal to this.handTotal - 10 reassign this.softAce to false elif this.handTotal > 21 then reassign this.status to Status.bust elif this.handTotal is 21 then reassign this.status to Status.standing end if if this.status isnt Status.active then reassign this.hasTurn to false end if end procedure procedure stand() reassign this.status to Status.standing reassign this.hasTurn to false end procedure procedure draw() variable newCard set to dealCard(random()) variable cards set to this.cards call cards.append(newCard) if newCard.rank.equals("A") then call this.addAce() else reassign this.handTotal to this.handTotal + rankValue()[newCard.rank] end if call this.evaluateStatus(newCard) end procedure procedure addAce() if this.softAce then reassign this.handTotal to this.handTotal + 1 else reassign this.handTotal to this.handTotal + 11 reassign this.softAce to true end if end procedure function cardCount() returns Int return this.cards.length() end function procedure changePointsBy(amount as Int) reassign this.points to this.points + amount end procedure abstract procedure newHand() private procedure newHandHelper() reassign this.hasTurn to false reassign this.softAce to false reassign this.cards to new List
abstract class Player property name as String property points as Int property cards as List property handTotal as Int property softAce as Boolean property status as Status property hasTurn as Boolean procedure startTurn() if this.status is Status.active then reassign this.hasTurn to true end if end procedure procedure determineOutcomeAndUpdatePoints(dealer as Dealer) variable playerOutcome set to determinePlayerOutcome(dealer, this) if playerOutcome is Outcome.winDouble then call this.changePointsBy(2) call dealer.changePointsBy(-2) elif playerOutcome is Outcome.win then call this.changePointsBy(1) call dealer.changePointsBy(-1) elif playerOutcome is Outcome.lose then call this.changePointsBy(-1) call dealer.changePointsBy(1) end if end procedure procedure evaluateStatus(newCard as Card) if (this.cardCount() is 2) and (this.handTotal is 21) then reassign this.status to Status.blackjack elif (this.handTotal > 21) and (this.softAce) then reassign this.handTotal to this.handTotal - 10 reassign this.softAce to false elif this.handTotal > 21 then reassign this.status to Status.bust elif this.handTotal is 21 then reassign this.status to Status.standing end if if this.status isnt Status.active then reassign this.hasTurn to false end if end procedure procedure stand() reassign this.status to Status.standing reassign this.hasTurn to false end procedure procedure draw() variable newCard set to dealCard(random()) variable cards set to this.cards call cards.append(newCard) if newCard.rank.equals("A") then call this.addAce() else reassign this.handTotal to this.handTotal + rankValue()[newCard.rank] end if call this.evaluateStatus(newCard) end procedure procedure addAce() if this.softAce then reassign this.handTotal to this.handTotal + 1 else reassign this.handTotal to this.handTotal + 11 reassign this.softAce to true end if end procedure function cardCount() returns Int return this.cards.length() end function procedure changePointsBy(amount as Int) reassign this.points to this.points + amount end procedure abstract procedure newHand() private procedure newHandHelper() reassign this.hasTurn to false reassign this.softAce to false reassign this.cards to new List
abstract class Player property name as String property points as Int property cards as List property handTotal as Int property softAce as Boolean property status as Status property hasTurn as Boolean procedure startTurn() if this.status is Status.active then reassign this.hasTurn to true end if end procedure procedure determineOutcomeAndUpdatePoints(dealer as Dealer) variable playerOutcome set to determinePlayerOutcome(dealer, this) if playerOutcome is Outcome.winDouble then call this.changePointsBy(2) call dealer.changePointsBy(-2) elif playerOutcome is Outcome.win then call this.changePointsBy(1) call dealer.changePointsBy(-1) elif playerOutcome is Outcome.lose then call this.changePointsBy(-1) call dealer.changePointsBy(1) end if end procedure procedure evaluateStatus(newCard as Card) if (this.cardCount() is 2) and (this.handTotal is 21) then reassign this.status to Status.blackjack elif (this.handTotal > 21) and (this.softAce) then reassign this.handTotal to this.handTotal - 10 reassign this.softAce to false elif this.handTotal > 21 then reassign this.status to Status.bust elif this.handTotal is 21 then reassign this.status to Status.standing end if if this.status isnt Status.active then reassign this.hasTurn to false end if end procedure procedure stand() reassign this.status to Status.standing reassign this.hasTurn to false end procedure procedure draw() variable newCard set to dealCard(random()) variable cards set to this.cards call cards.append(newCard) if newCard.rank.equals("A") then call this.addAce() else reassign this.handTotal to this.handTotal + rankValue()[newCard.rank] end if call this.evaluateStatus(newCard) end procedure procedure addAce() if this.softAce then reassign this.handTotal to this.handTotal + 1 else reassign this.handTotal to this.handTotal + 11 reassign this.softAce to true end if end procedure function cardCount() returns Int return this.cards.length() end function procedure changePointsBy(amount as Int) reassign this.points to this.points + amount end procedure abstract procedure newHand() private procedure newHandHelper() reassign this.hasTurn to false reassign this.softAce to false reassign this.cards to new List

What you need to know about as abstract class

  • An abstract class is like a concrete class except that:
    • It may not be instantiated directly, and hence has no constructor.
    • A concrete class may inherit from it; this is specified in the concrete class, by entering the name of the desired abstract 'superclass' in the the field labelled inheritance. Note that when this has been entered the editor will add the syntax for inheritance appropriate to the language being used.
    • It may define concrete members (as found on concrete classes) and/or abstract members.
    • Any concrete class that inherits from an abstract class, must provide concrete implementations of all abstract members defined on its immediate superclass, and on any abstract classes defined in its hierarchy.
  • An abstract class must be declared in the code above any class that inherits from it.
  • Inheritance hierarchies must form a tree, that is you must avoid creating a circular dependency where, for example, type A inherits from type B, which inherits from type CCCCC, which inherits from type AAAAA.

More advanced techniques

Delegation

If you want sub-classes to be able to define their own implementation of a common method, then that method should be made abstract. If it turns out that several of the sub-classes need the same, or very similar, implementations (and duplication of code is always to be avoided, according to the DRY principle - Don't Repeat Yourself) then you use the pattern of 'delegation'. This means defining the method as abstract, but also providing a private implementation (or part-implementation) with a slightly different name, which sub-classes may call from their own implementations. You can see an example of this in the Blackjack demo:

Statement Instructions

Coding with Elan using the object-oriented profile offers the same statement instructions used in the procedural profile.

Member instructions

Member instructions (also referred to simply as 'members') may be added only with a concrete class or abstract class. Hence, these method instructions may be created only when the profile is set to object-oriented (or functional - which includes all OOP capabilities).

Constructor

Examples of constructors

From the Snake - object-oriented demo, where the SquareSquareSquareSquareSquare class defines this constructor:

+class SquareName? inheritance?1 +constructor(x as Int, y as Intparameter definitions?) 2 reassign this.xvariableName? to xvalue or expression?3 reassign this.yvariableName? to yvalue or expression?4 end constructor +function toStringname?(parameter definitions?) returns StringType? 5 return "undefined"value or expression?6 end function end class
+class SquareName? inheritance? # concrete class1 +def __init__(self: Square, x: int, y: intparameter definitions?) -> None: 2 self.xvariableName? = xvalue or expression? # reassign variable3 self.yvariableName? = yvalue or expression? # reassign variable4 +def toStringname?(self: Square) -> strType?: # function method5 return "undefined"value or expression?6
+class SquareName? inheritance? {1 +public Square(int x, int yparameter definitions?) { 2 this.xvariableName? = xvalue or expression?; // reassign variable3 this.yvariableName? = yvalue or expression?; // reassign variable4 } // constructor +public stringType? toStringname?(parameter definitions?) { // function method5 return "undefined"value or expression?;6 } // function method } // class
+Class SquareName? inheritance?1 +Sub New(x As Integer, y As Integerparameter definitions?) 2 Me.xvariableName? = xvalue or expression? ' reassign variable3 Me.yvariableName? = yvalue or expression? ' reassign variable4 End Sub +Function toStringname?(parameter definitions?) As StringType? 5 Return "undefined"value or expression?6 End Function End Class
public class Global {

+class SquareName? inheritance? {1 +public Square(int x, int yparameter definitions?) { 2 this.xvariableName? = xvalue or expression?; // reassign variable3 this.yvariableName? = yvalue or expression?; // reassign variable4 } // constructor +public StringType? toStringname?(parameter definitions?) { // function method5 return "undefined"value or expression?;6 } // function method } // class
}

which requires the programmer to provide values for parameters xxxxx and yyyyy when instantiating a SquareSquareSquareSquareSquare, and assigns these parameters to the corresponding properties of the object.

From the Blackjack demo, where the GameGameGameGameGame class defines this constructor:

constructor(dealerStartPoints as Int) reassign this.dealer to new Dealer(dealerStartPoints) reassign this.players to new List() reassign this.message to "" end constructor Code does not parse as Elan.
constructor(dealerStartPoints as Int) reassign this.dealer to new Dealer(dealerStartPoints) reassign this.players to new List() reassign this.message to "" end constructor Code does not parse as Elan.
constructor(dealerStartPoints as Int) reassign this.dealer to new Dealer(dealerStartPoints) reassign this.players to new List() reassign this.message to "" end constructor Code does not parse as Elan.
constructor(dealerStartPoints as Int) reassign this.dealer to new Dealer(dealerStartPoints) reassign this.players to new List() reassign this.message to "" end constructor Code does not parse as Elan.
constructor(dealerStartPoints as Int) reassign this.dealer to new Dealer(dealerStartPoints) reassign this.players to new List() reassign this.message to "" end constructor Code does not parse as Elan.

Note that the single parameter defined in the instructor does not correspond directly to the any property defined on GameGameGameGameGame, but is used to create a new instance of DealerDealerDealerDealerDealer for its own dealerdealerdealerdealerdealer (association) property. Its playerplayerplayerplayerplayer property is a 'multiple association' and is initialised to a new (empty) list of type PlayerPlayerPlayerPlayerPlayer.

What you need to know about a constructors

  • A concrete class must have a constructor - so when a new concrete class is created, it includes a minimal default implementation.
  • The purpose of the constructor is to initialise all properties (except, optionally, for properties of numeric or Boolean types - if you are happy for them to take the default values for those types).
  • <
  • A property is initialised using a reassign instruction, prefixing the name of the propery as in this example: reassign this.xvariableName? to xvalue or expression?0self.xvariableName? = xvalue or expression? # reassign variable0this.xvariableName? = xvalue or expression?; // reassign variable0Me.xvariableName? = xvalue or expression? ' reassign variable0this.xvariableName? = xvalue or expression?; // reassign variable0 - the prefix means that you can have a parameter with the same name, with no risk of ambiguity.
  • The constructor may make use of the object's function methods and/or global methods to calculate values to be assigned to its properties.
  • However, a constructor may not call any procedure - internal or external - because constructing a new instance should have no side-effects.
  • Property

    Examples of properties

    From the Snake - object-oriented demo, where the GameGameGameGameGame class defines these properties:

    +constructor(parameter definitions?) 0 new code end constructor
    property dealer as Dealer Code does not parse as Elan.
    property dealer as Dealer Code does not parse as Elan.
    +Sub New(parameter definitions?) 0 new code End Sub
    property dealer as Dealer Code does not parse as Elan.
    property players as List&let;of Player> Code does not parse as Elan.
    property players as List&let;of Player> Code does not parse as Elan.
    property players as List&let;of Player> Code does not parse as Elan.
    property players as List&let;of Player> Code does not parse as Elan.
    property players as List&let;of Player> Code does not parse as Elan.
    +constructor(parameter definitions?) 0 new code end constructor
    property message as String Code does not parse as Elan.
    property message as String Code does not parse as Elan.
    +Sub New(parameter definitions?) 0 new code End Sub
    property message as String Code does not parse as Elan.

    What you need to know about properties

    • A property is a named value defined on a concrete or abstract class with a name conforming to the standard rules for an identifier.
    • The type of a property, unlike a variable, is explicitly defined, as shown in the examples above. It may be a standard library type (including data structures), or the name of another used-defined class, or a data structure of another class type (see examples above).
    • Every property must be initialsed within a constructor unless it is of a numeric or Boolean type - which have default values.
    • A property is public by default, but may be specified as private (visible only to code inside the class) though the make private action on the context menu (or reverted to public with make public.
    • The public properties of an instance may be read by code outside the class using dot-syntax, but may only be modified (from outside) via an appropriate procedure method.

    More advanced techniques

    Optional ('Maybe') associations

    When a class property is optional, it is defined with the type MaybeMaybeMaybeMaybeMaybe.

    Before reading the property's value, you must test if it has a value with function hasValue which returns a BooleanboolboolBooleanbool. If trueTruetrueTruetrue is returned, then you retrieve the value with function getValue; if falseFalsefalseFalsefalse is returned, then it has no current value, and using getValue will give a runtime error.

    To update the property's value, use function setValue. To restore it to having no value, use function clearValue.

    Here a pupil (of class Pupil) that optionally has a tutor (of class Teacher):

    +main 1 variable tTomname? set to new Teacher()value or expression?2 variable pPetename? set to new Pupil()value or expression?3 +if pPete.tutor.hasValue()condition? then 4 print(pPete.tutorarguments?)5 end if print(parguments?)6 end main +class TeacherName? inheritance?7 +constructor(parameter definitionsparameter definitions?) 8 new code end constructor +function toStringname?(parameter definitionsparameter definitions?) returns StringType? 9 return "a Teacher"value or expression?10 end function end class +class PupilName? inheritance?11 +constructor(parameter definitionsparameter definitions?) 12 reassign this.tutorvariableName? to new Maybe<of Teacher>()value or expression?13 end constructor property tutorname? as Maybe<of Teacher>Type?14 +function toStringname?(parameter definitionsparameter definitions?) returns StringType? 15 return "a Pupil"value or expression?16 end function end class
    +def main() -> None: 1 tTomname? = Teacher()value or expression? # variable definition2 pPetename? = Pupil()value or expression? # variable definition3 +if pPete.tutor.hasValue()condition?: 4 print(pPete.tutorarguments?)5 print(parguments?)6 +class TeacherName? inheritance? # concrete class7 +def __init__(self: Teacher) -> None: 8 new code +def toStringname?(self: Teacher) -> strType?: # function method9 return "a Teacher"value or expression?10 +class PupilName? inheritance? # concrete class11 +def __init__(self: Pupil) -> None: 12 self.tutorvariableName? = Maybe[Teacher]()value or expression? # reassign variable13 tutorname?: Maybe[Teacher]Type? # property14 +def toStringname?(self: Pupil) -> strType?: # function method15 return "a Pupil"value or expression?16 main()
    +static void main() { 1 var tTomname? = new Teacher()value or expression?;2 var pPetename? = new Pupil()value or expression?;3 +if (pPete.tutor.hasValue()condition?) { 4 Console.WriteLine(pPete.tutorarguments?); // print5 } // if Console.WriteLine(parguments?); // print6 } // main +class TeacherName? inheritance? {7 +public Teacher(parameter definitionsparameter definitions?) { 8 new code } // constructor +public stringType? toStringname?(parameter definitionsparameter definitions?) { // function method9 return "a Teacher"value or expression?;10 } // function method } // class +class PupilName? inheritance? {11 +public Pupil(parameter definitionsparameter definitions?) { 12 this.tutorvariableName? = new Maybe<Teacher>()value or expression?; // reassign variable13 } // constructor public Maybe<Teacher>Type? tutorname? {get; private set;} // property14 +public stringType? toStringname?(parameter definitionsparameter definitions?) { // function method15 return "a Pupil"value or expression?;16 } // function method } // class
    +Sub main() 1 Dim tTomname? = New Teacher()value or expression? ' variable definition2 Dim pPetename? = New Pupil()value or expression? ' variable definition3 +If pPete.tutor.hasValue()condition? Then 4 Console.WriteLine(pPete.tutorarguments?) ' print5 End If Console.WriteLine(parguments?) ' print6 End Sub +Class TeacherName? inheritance?7 +Sub New(parameter definitionsparameter definitions?) 8 new code End Sub +Function toStringname?(parameter definitionsparameter definitions?) As StringType? 9 Return "a Teacher"value or expression?10 End Function End Class +Class PupilName? inheritance?11 +Sub New(parameter definitionsparameter definitions?) 12 Me.tutorvariableName? = New Maybe(Of Teacher)()value or expression? ' reassign variable13 End Sub Property tutorname? As Maybe(Of Teacher)Type?14 +Function toStringname?(parameter definitionsparameter definitions?) As StringType? 15 Return "a Pupil"value or expression?16 End Function End Class
    public class Global {

    +static void main() { 1 var tTomname? = new Teacher()value or expression?;2 var pPetename? = new Pupil()value or expression?;3 +if (pPete.tutor.hasValue()condition?) { 4 System.out.println(pPete.tutorarguments?); // print5 } // if System.out.println(parguments?); // print6 } // main +class TeacherName? inheritance? {7 +public Teacher(parameter definitionsparameter definitions?) { 8 new code } // constructor +public StringType? toStringname?(parameter definitionsparameter definitions?) { // function method9 return "a Teacher"value or expression?;10 } // function method } // class +class PupilName? inheritance? {11 +public Pupil(parameter definitionsparameter definitions?) { 12 this.tutorvariableName? = new Maybe<Teacher>()value or expression?; // reassign variable13 } // constructor public Maybe<Teacher>Type? tutorname?; // property14 +public StringType? toStringname?(parameter definitionsparameter definitions?) { // function method15 return "a Pupil"value or expression?;16 } // function method } // class
    }

    Function method

    Examples of function methods

    From the Roman Numerals Turing Machine demo, where the Turing Machine Code does not parse as Elan.Turing Machine Code does not parse as Elan.Turing Machine Code does not parse as Elan.Turing Machine Code does not parse as Elan.Turing Machine Code does not parse as Elan. class defines these two function methods:

    +function isHaltedname?(parameter definitionsparameter definitions?) returns BooleanType? 1 return this.currentState.equals(this.haltState)value or expression?2 end function +function findMatchingRulename?(parameter definitionsparameter definitions?) returns RuleType? 3 variable matchesname? set to this.rules.filter(lambda r as Rule => (r.currentState.equals(this.currentState)) and (r.currentSymbol.equals(this.tape[this.headPosition])))value or expression?4 +if matches.length() is 0condition? then 5 throw ElanRuntimeErrorexception type? $"No rule matching state {this.currentState} and symbol {this.tape[this.headPosition]}"message?6 end if return matches.head()value or expression?7 end function
    +def isHaltedname?(parameter definitionsparameter definitions?) -> boolType?: # function1 return self.currentState.equals(self.haltState)value or expression?2 +def findMatchingRulename?(parameter definitionsparameter definitions?) -> RuleType?: # function3 matchesname? = self.rules.filter(lambda r: Rule: (r.currentState.equals(self.currentState)) and (r.currentSymbol.equals(self.tape[self.headPosition])))value or expression? # variable definition4 +if matches.length() == 0condition?: 5 raise ElanRuntimeErrorexception type?(f"No rule matching state {self.currentState} and symbol {self.tape[self.headPosition]}"message?)6 return matches.head()value or expression?7
    +static boolType? isHaltedname?(parameter definitionsparameter definitions?) { // function1 return this.currentState.equals(this.haltState)value or expression?;2 } // function +static RuleType? findMatchingRulename?(parameter definitionsparameter definitions?) { // function3 var matchesname? = this.rules.filter(Rule r => (r.currentState.equals(this.currentState)) && (r.currentSymbol.equals(this.tape[this.headPosition])))value or expression?;4 +if (matches.length() == 0condition?) { 5 throw new ElanRuntimeErrorexception type?($"No rule matching state {this.currentState} and symbol {this.tape[this.headPosition]}"message?);6 } // if return matches.head()value or expression?;7 } // function
    +Function isHaltedname?(parameter definitionsparameter definitions?) As BooleanType? 1 Return Me.currentState.equals(Me.haltState)value or expression?2 End Function +Function findMatchingRulename?(parameter definitionsparameter definitions?) As RuleType? 3 Dim matchesname? = Me.rules.filter(Function (r As Rule) (r.currentState.equals(Me.currentState)) And (r.currentSymbol.equals(Me.tape[Me.headPosition])))value or expression? ' variable definition4 +If matches.length() = 0condition? Then 5 Throw New ElanRuntimeErrorexception type?($"No rule matching state {Me.currentState} and symbol {Me.tape[Me.headPosition]}"message?)6 End If Return matches.head()value or expression?7 End Function
    public class Global {

    +static boolType? isHaltedname?(parameter definitionsparameter definitions?) { // function1 return this.currentState.equals(this.haltState)value or expression?;2 } // function +static RuleType? findMatchingRulename?(parameter definitionsparameter definitions?) { // function3 var matchesname? = this.rules.filter((Rule r) -> (r.currentState.equals(this.currentState)) && (r.currentSymbol.equals(this.tape[this.headPosition])))value or expression?;4 +if (matches.length() == 0condition?) { 5 throw new ElanRuntimeErrorexception type?(String.format("No rule matching state % and symbol %", this.currentState, this.tape[this.headPosition])message?);6 } // if return matches.head()value or expression?;7 } // function
    }

    What you need to know about function methods

    • A function method provides information based on the parameters specified in combination with the object's properties.
    • A function method broadly follows the same rules as an ordinary (global) function - so it may not create any side effects nor.
    • However, a function method may read, and hence depend upon, the properties of the object on which it is defined.
    • A function method is public by default, but may be specified as private (visible only to code inside the class) though the make private action on the context menu (or reverted to public with make public).
    • A function method may be called (within an expression) on an object in code outside the class using dot-syntax on the variable holding the instance.
    • A function method may be invoked from within another method inside the class using, for example this.methodName()self.methodName()this.methodName()Me.methodName()this.methodName().

    Procedure method

    Examples of procedure methods

    From the Roman Numerals Turing Machine demo, where the Turing Machine Code does not parse as Elan.Turing Machine Code does not parse as Elan.Turing Machine Code does not parse as Elan.Turing Machine Code does not parse as Elan.Turing Machine Code does not parse as Elan. defines these procedure methods (among others):

    +procedure setTapename?(tape as Stringparameter definitions?) 1 reassign this.tapevariableName? to tapevalue or expression?2 end procedure
    +def setTapename?(tape: strparameter definitions?) -> None: # procedure1 self.tapevariableName? = tapevalue or expression? # reassign variable2
    +static void setTapename?(string tapeparameter definitions?) { // procedure1 this.tapevariableName? = tapevalue or expression?; // reassign variable2 } // procedure
    +Sub setTapename?(tape As Stringparameter definitions?) ' procedure1 Me.tapevariableName? = tapevalue or expression? ' reassign variable2 End Sub
    public class Global {

    +static void setTapename?(String tapeparameter definitions?) { // procedure1 this.tapevariableName? = tapevalue or expression?; // reassign variable2 } // procedure
    }

    This is the simplest form of a procedure method, which allows the tapetapetapetapetape property (a string) to be set to a new value by code outside the class.

    +procedure singleStepname?(parameter definitionsparameter definitions?) 1 variable rulename? set to this.findMatchingRule()value or expression?2 call this.executeprocedureName?(rulearguments?)3 end procedure
    +def singleStepname?(parameter definitionsparameter definitions?) -> None: # procedure1 rulename? = self.findMatchingRule()value or expression? # variable definition2 self.executeprocedureName?(rulearguments?) # call procedure3
    +static void singleStepname?(parameter definitionsparameter definitions?) { // procedure1 var rulename? = this.findMatchingRule()value or expression?;2 this.executeprocedureName?(rulearguments?); // call procedure3 } // procedure
    +Sub singleStepname?(parameter definitionsparameter definitions?) ' procedure1 Dim rulename? = Me.findMatchingRule()value or expression? ' variable definition2 Me.executeprocedureName?(rulearguments?) ' call procedure3 End Sub
    public class Global {

    +static void singleStepname?(parameter definitionsparameter definitions?) { // procedure1 var rulename? = this.findMatchingRule()value or expression?;2 this.executeprocedureName?(rulearguments?); // call procedure3 } // procedure
    }

    This method made use of a function method on the same class - findMatchingRule() - and then calls another procedure on the same instance - execute - passing it the found rulerulerulerulerule.

    What you need to know about procedure methods

    • A procedure method changes the state of the object, and/or interacts with the system
    • A procedure method broadly follows the same rules as an ordinary (global) function - so it may not create any side effects nor.
    • However, a procedure method may read, and hence depend upon, the properties of the object on which it is defined.
    • A procedure method is public by default, but may be specified as private (visible only to code inside the class) though the make private action on the context menu (or reverted to public with make public).
    • A procedure method may be called (using a call procedure instruction) on an object in code outside the class using dot-syntax on the variable holding the instance.
    • A procedure method may be called from within another method inside the class using, for example call this.methodNameprocedureName?(arguments?)0self.methodNameprocedureName?(arguments?) # call procedure0this.methodNameprocedureName?(arguments?); // call procedure0Me.methodNameprocedureName?(arguments?) ' call procedure0this.methodNameprocedureName?(arguments?); // call procedure0.

    Abstract function

    Examples of abstract functions

    From the Blackjack demo, where the abstract class PlayerPlayerPlayerPlayerPlayer defines this abstract function:

    abstract function getMessage() returns String Code does not parse as Elan.
    abstract function getMessage() returns String Code does not parse as Elan.
    abstract function getMessage() returns String Code does not parse as Elan.
    abstract function getMessage() returns String Code does not parse as Elan.
    abstract function getMessage() returns String Code does not parse as Elan.

    which is implemented (as a concrete function method) by the subclasses, including on DealerDealerDealerDealerDealer:

    +function getMessagename?(parameter definitionsparameter definitions?) returns StringType? 1 variable msgname? set to ""value or expression?2 +if this.hasPlayedcondition? then 3 reassign msgvariableName? to this.getMessageHelper() + $" - hand total: {this.handTotal}"value or expression?4 end if return msgvalue or expression?5 end function
    +def getMessagename?(parameter definitionsparameter definitions?) -> strType?: # function1 msgname? = ""value or expression? # variable definition2 +if self.hasPlayedcondition?: 3 msgvariableName? = self.getMessageHelper() + f" - hand total: {self.handTotal}"value or expression? # reassign variable4 return msgvalue or expression?5
    +static stringType? getMessagename?(parameter definitionsparameter definitions?) { // function1 var msgname? = ""value or expression?;2 +if (this.hasPlayedcondition?) { 3 msgvariableName? = this.getMessageHelper() + $" - hand total: {this.handTotal}"value or expression?; // reassign variable4 } // if return msgvalue or expression?;5 } // function
    +Function getMessagename?(parameter definitionsparameter definitions?) As StringType? 1 Dim msgname? = ""value or expression? ' variable definition2 +If Me.hasPlayedcondition? Then 3 msgvariableName? = Me.getMessageHelper() + $" - hand total: {Me.handTotal}"value or expression? ' reassign variable4 End If Return msgvalue or expression?5 End Function
    public class Global {

    +static StringType? getMessagename?(parameter definitionsparameter definitions?) { // function1 var msgname? = ""value or expression?;2 +if (this.hasPlayedcondition?) { 3 msgvariableName? = this.getMessageHelper() + String.format(" - hand total: %", this.handTotal)value or expression?; // reassign variable4 } // if return msgvalue or expression?;5 } // function
    }

    which makes use of a method getMessageHelper() that is defined as (private) concrete function method on the superclass (PlayerPlayerPlayerPlayerPlayer).

    What you need to know about p abstract functions

    • An abstract function method may be defined only on an abstract class .
    • Any concrete subclass must then implement a concrete (regular) function to match.

    Abstract procedure

    Examples of abstract procedure

    From the Blackjack demo, where the abstract class PlayerPlayerPlayerPlayerPlayer defines this abstract function:

    abstract procedure nextAction(dealerFaceCard as Card) Code does not parse as Elan.
    abstract procedure nextAction(dealerFaceCard as Card) Code does not parse as Elan.
    abstract procedure nextAction(dealerFaceCard as Card) Code does not parse as Elan.
    abstract procedure nextAction(dealerFaceCard as Card) Code does not parse as Elan.
    abstract procedure nextAction(dealerFaceCard as Card) Code does not parse as Elan.

    which is implemented (as a concrete function method) by the subclass DealerDealerDealerDealerDealer using simple logic to implement the standard rules that a Blackjack dealer must always follow:

    +procedure nextActionname?(faceCard as Cardparameter definitions?) 1 +if this.handTotal < 17condition? then 2 call this.drawprocedureName?(arguments?)3 else4 call this.standprocedureName?(arguments?)5 end if end procedure
    +def nextActionname?(faceCard: Cardparameter definitions?) -> None: # procedure1 +if self.handTotal < 17condition?: 2 self.drawprocedureName?(arguments?) # call procedure3 else:4 self.standprocedureName?(arguments?) # call procedure5
    +static void nextActionname?(Card faceCardparameter definitions?) { // procedure1 +if (this.handTotal < 17condition?) { 2 this.drawprocedureName?(arguments?); // call procedure3 } else {4 this.standprocedureName?(arguments?); // call procedure5 } // if } // procedure
    +Sub nextActionname?(faceCard As Cardparameter definitions?) ' procedure1 +If Me.handTotal < 17condition? Then 2 Me.drawprocedureName?(arguments?) ' call procedure3 Else4 Me.standprocedureName?(arguments?) ' call procedure5 End If End Sub
    public class Global {

    +static void nextActionname?(Card faceCardparameter definitions?) { // procedure1 +if (this.handTotal < 17condition?) { 2 this.drawprocedureName?(arguments?); // call procedure3 } else {4 this.standprocedureName?(arguments?); // call procedure5 } // if } // procedure
    }

    and on the subclass HumanPlayerHumanPlayerHumanPlayerHumanPlayerHumanPlayer by code that usesinput/output methods to obtain the next action from the user:

    +procedure nextActionname?(dealerFaceCard as Cardparameter definitions?) 1 variable keyname? set to ""value or expression?2 call clearKeyBufferprocedureName?(?)3 +while key.equals("")condition? 4 reassign keyvariableName? to waitForKey()value or expression?5 +if key.equals("d")condition? then 6 call this.drawprocedureName?(arguments?)7 elif key.equals("s")condition? then8 call this.standprocedureName?(arguments?)9 else10 reassign keyvariableName? to ""value or expression?11 end if end while end procedure
    +def nextActionname?(dealerFaceCard: Cardparameter definitions?) -> None: # procedure1 keyname? = ""value or expression? # variable definition2 clearKeyBufferprocedureName?(?) # call procedure3 +while key.equals("")condition?: 4 keyvariableName? = waitForKey()value or expression? # reassign variable5 +if key.equals("d")condition?: 6 self.drawprocedureName?(arguments?) # call procedure7 elif key.equals("s")condition?: # else if8 self.standprocedureName?(arguments?) # call procedure9 else:10 keyvariableName? = ""value or expression? # reassign variable11
    +static void nextActionname?(Card dealerFaceCardparameter definitions?) { // procedure1 var keyname? = ""value or expression?;2 clearKeyBufferprocedureName?(?); // call procedure3 +while (key.equals("")condition?) { 4 keyvariableName? = waitForKey()value or expression?; // reassign variable5 +if (key.equals("d")condition?) { 6 this.drawprocedureName?(arguments?); // call procedure7 } else if (key.equals("s")condition?) {8 this.standprocedureName?(arguments?); // call procedure9 } else {10 keyvariableName? = ""value or expression?; // reassign variable11 } // if } // while } // procedure
    +Sub nextActionname?(dealerFaceCard As Cardparameter definitions?) ' procedure1 Dim keyname? = ""value or expression? ' variable definition2 clearKeyBufferprocedureName?(?) ' call procedure3 +While key.equals("")condition? 4 keyvariableName? = waitForKey()value or expression? ' reassign variable5 +If key.equals("d")condition? Then 6 Me.drawprocedureName?(arguments?) ' call procedure7 ElseIf key.equals("s")condition? Then8 Me.standprocedureName?(arguments?) ' call procedure9 Else10 keyvariableName? = ""value or expression? ' reassign variable11 End If End While End Sub
    public class Global {

    +static void nextActionname?(Card dealerFaceCardparameter definitions?) { // procedure1 var keyname? = ""value or expression?;2 clearKeyBufferprocedureName?(?); // call procedure3 +while (key.equals("")condition?) { 4 keyvariableName? = waitForKey()value or expression?; // reassign variable5 +if (key.equals("d")condition?) { 6 this.drawprocedureName?(arguments?); // call procedure7 } else if (key.equals("s")condition?) {8 this.standprocedureName?(arguments?); // call procedure9 } else {10 keyvariableName? = ""value or expression?; // reassign variable11 } // if } // while } // procedure
    }

    What you need to know about p abstract procedures

    • An abstract abstract_procedure method may be defined only on an abstract class .
    • Any concrete subclass must then implement a concrete (regular) function to match.

    Comment

    See Comments

    Elan Language Reference go to the top