Procedural programming Reference

Getting started

Select the procedural programming profile

To undertake procedural programming (PP) in Elan, ensure that Elan is set to the Procedural profile from the menu at the top of the IDE. This will ensure that:

Why coding with Elan is different

To start programming with Elan, first familiarise yourself using the Elan IDE, and especially with entering and editing code, by watching the videos in this document [LINK REQUIRED]. You will learn, therein, that Elan supports programming in Python, VB, C#, or Java (as well as the Reference Language), and offers the option of immediate translation between all four languages - either way - if desired. However, if you have previously programmed in any of those languages you will finds that Elan enforces a few constraints on how code is written. There are three forms of these constraints:

These are described in more detail below

Enforced best practices in coding

Elan supports, and in many cases enforces a set of good programming practices. This applies across all the supported languages - even where a language, if used outside Elan, would allow you to do otherwise. In the context of procedural programming - where the procedural profile has been selected in the IDE, there are seven such best practices.

Some of these best practices are widely recognised, others less so. Some programmers will argue that there are very specific circumstances where breaking the rules should be acceptable. The problem with this line of argument is that most of the places where even professional programmers break the rules don't fit those circumstances - they are broken because the programmer just finds it more convenient. It is far better that you learn to program without breaking these rules than that you adopt bad habits from the beginning. This will result in you writing better code, and encountering fewer unexpected problems.

The seven best practices enforced by Elan - for procedural programming, this is -are:

[INSERT TOC of the h3 headings in this section]

No global variables

It is widely recognised that global variables are a bad idea - encouraging poor program structure and leading to unexpected behaviour. Yet most programming languages permit them. The Elan editor solves this by offering the variable definition instruction only within the main routine, functions, or procedures. At global level the only permitted definition of a named value is a constant, and that, too, is constrained to be of a type that cannot be mutated by any instruction in the program.

In Java, all code must be within a class. Elan provides a boilerplate 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. to adhere to this rule, and all code must be added within this, and everything added immediately within the 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. is deemed, in Elan terms, to be 'global code', so you will not be able to add a variable - even in the form of a 'property' there.

Static typing

Static typing means that the type of any variable, or parameter, or value returned by a function, is known at compile time, and that type never changes, even though the value may change. VB, C#, and Java, are inherently statically-typed. Python is inherently dynamically-typed but since Python 3.5 has offered the option to add 'type hints' which allow working in a statically typed manner. While some programmers like the idea of dynamic typing, there is almost no need for it in most forms of programming, and it is well established that dynamic typing does not work well when building large, complex systems. Moreover, in a pure educational context, using static typing gives a stronger understanding that, in all modern languages, statically- or dynamically-typed, all data elements have an associated type - and static typing helps you to understand the nature of those types. When entering/editing Python, Elan therefore enforces the use of type hints where needed.

Elan requires types to be specified for all parameters for procedures or functions, and for the return type of a function.

Elan does not, however, require the type to be specified explicitly for any variable (or constant) because the type is inferred from the initial value given to it.

No null references

All programmers have encourntered the notorious 'Null Reference ...` (or 'Null Pointer ...`) error message, and these can sometimes be very hard to track down. Tony Hoare, Turing Medal recipient and one of Britain's greatest computer scientists, magnanimously described his invention of 'null' as a Billion Dollar Mistake.

All the supported programming languages inherently permit null reference errors - but when you are coding in Elan it is impossible for one to arise because:

No breaking out of loops before they have completed

Python, C#, and Java all have a break instruction (the VB equivalent is exit) which allow you to break out of a loop. Elan does not permit any of those instructions to be used. Why?

The break instruction (or equivalent) was invented when the only possible kind of loops that programming languages supported executed a specified number of times - there were no 'conditional loops'. With the advent of 'structured programming' came proper conditional loops, of which the most common form is the 'while loop'. After structured programming it is never necessary to break out from a loop before its natural termination.

Return from a function or procedure only after the final instruction

This principle is somewhat analogous to the previous one - where programmers find it 'convenient' to include multiple returnreturnreturnreturnreturn statements within a procedure or a function. All the supported languages permit this in most other environments. Elan, however, deliberately prevents this practice. In Elan a function always has a return statement as its last instruction (which specifies the value to be returned), and there is no option to add another one earlier. And in Elan a procedure never has a return instruction - the procedure exits after the last instruction.

But where is the harm in 'early returns'? Here are four answers:

Some programmers argue that the constraint of no early returns in a function, means you have to write an extra line of code at the start to define a variable that holds the result. True - but that's a small price to pay, and it also makes your code more readable.

All functions must be pure

Many textbooks draw no distinction between procedures and functions, except that the latter return a value to the calling code. Used properly, the two are distinct in another, very important, respect.

Understanding procedures

A procedure:

By contrast, a properfunction:

Although some programming languages make a syntactic distinction between a procedure and a function (some don't), the only ones that enforce the distinctions made above are the 'pure' functional programming languages, such as Haskell, OCAML, F# ...

Elan ensures that all functions are proper functions, but places no restrictions on what you can do in a procedure (except return a value). In the long term, if you wish to progress further with programming, laying this foundation will make it far easier for you to transition to 'functional programming' than otherwise. But making this distinction from the outset offers more immediate benefits:

Every function should be unit tested

Historically, most testing of programs was done by humans, running through a large number of 'test scripts' (or 'cases') manually to check that the delivered system worked correctly, then again after every single change or extension to the system, to ensure that existing functionality had not been broken accidentally.

In recent years, the emphasis as shifted to automated testing. Although writing the test scripts/cases still takes, once written, all the tests can be run repeatedly at the touch of a button, and even triggered automatically by any change to the code.

There are many forms of automated testing, but 'unit testing' is the most popular. All modern languages offer a 'unit testing framework', but they vary considerably in how easy they are to write or run.

Unit tests are intended for testing proper functions though many do not enforce that. But running unit tests on procedures, or improper functions (that create side effects) is dangerous - running the tests might result in data being written to a file or database, or a network message being sent repeatedly, for example. There are software frameworks for automated tesing of procedures and even whole applications (known as 'end to end testing') - but these frameworks are much harder to use.

Many professional software development teams enforce the writing of unit tests for every function, but this is typically something that most programmers don't discover unless they turn professional. But it is a practice that even complete beginners would benefit from:

Elan provides unit testing, for all the supported languages, that is even simpler to use (both for writing the tests and running them) than most other tools. Although it does not enforce the rule that every function written should have automated unit tests, it does make it so easy, that it hopefully encourages learners to use the practice

Use of a common library across all the supported languages.

All programming languages have their own standard libraries for:

and more. There are many similarities between the libraries of different languages, but also significant differences. Elan defines its own library which offers the identical functions in each of the supported languages. The Elan library has been designed to find as much common ground as possible but, inevitably, it is not identical to the standard library for any of the supported languages.

One consequence of this is that even though the Python, C#, VB, or Java code generated within Elan is always syntactically valid for the language, you won't typically be able to run exported code in another environment without editing the library calls. (We hope to provide automated, or semi-automated, tools for this in future.)

Other constraints

Elan enforces a few other constraints that aren't driven by best practices, or by the common library, but because of small arbitrary differences between the target languages. Fortunately these are quite small both in number and impact. There is no need to learn them explicitly, because in all cases where you might encounter one, you will get a clear error message that gives you the solution. But here is a list of the most important ones:

Global instructions

Global instructions are placed directly in your code file. They are never indented from the left-hand edge, nor may they be located within other instructions.

Main routine

Examples of main routines

p>From the Burrow demo. For very simple applications like this, the main routine is the whole program:

+main 1 variable blocksname? set to createBlockGraphics(white)value or expression?2 variable xname? set to 20value or expression?3 variable yname? set to 15value or expression?4 +while truecondition? 5 reassign blocks[x][y]variableName? to redvalue or expression?6 call displayBlocksprocedureName?(blocksarguments?)7 reassign blocks[x][y]variableName? to blackvalue or expression?8 variable directionname? set to randint(0, 3)value or expression?9 +if direction is 0condition? then 10 reassign xvariableName? to min([x + 1, 39])value or expression?11 elif direction is 1condition? then12 reassign xvariableName? to max([x - 1, 0])value or expression?13 elif direction is 2condition? then14 reassign yvariableName? to min([y + 1, 29])value or expression?15 elif direction is 3condition? then16 reassign yvariableName? to max([y - 1, 0])value or expression?17 end if end while end main
+def main() -> None: 1 blocksname? = createBlockGraphics(white)value or expression? # variable definition2 xname? = 20value or expression? # variable definition3 yname? = 15value or expression? # variable definition4 +while Truecondition?: 5 blocks[x][y]variableName? = redvalue or expression? # reassign variable6 displayBlocksprocedureName?(blocksarguments?) # call procedure7 blocks[x][y]variableName? = blackvalue or expression? # reassign variable8 directionname? = randint(0, 3)value or expression? # variable definition9 +if direction == 0condition?: 10 xvariableName? = min([x + 1, 39])value or expression? # reassign variable11 elif direction == 1condition?: # else if12 xvariableName? = max([x - 1, 0])value or expression? # reassign variable13 elif direction == 2condition?: # else if14 yvariableName? = min([y + 1, 29])value or expression? # reassign variable15 elif direction == 3condition?: # else if16 yvariableName? = max([y - 1, 0])value or expression? # reassign variable17 main()
+static void main() { 1 var blocksname? = createBlockGraphics(white)value or expression?;2 var xname? = 20value or expression?;3 var yname? = 15value or expression?;4 +while (truecondition?) { 5 blocks[x][y]variableName? = redvalue or expression?; // reassign variable6 displayBlocksprocedureName?(blocksarguments?); // call procedure7 blocks[x][y]variableName? = blackvalue or expression?; // reassign variable8 var directionname? = randint(0, 3)value or expression?;9 +if (direction == 0condition?) { 10 xvariableName? = min([x + 1, 39])value or expression?; // reassign variable11 } else if (direction == 1condition?) {12 xvariableName? = max([x - 1, 0])value or expression?; // reassign variable13 } else if (direction == 2condition?) {14 yvariableName? = min([y + 1, 29])value or expression?; // reassign variable15 } else if (direction == 3condition?) {16 yvariableName? = max([y - 1, 0])value or expression?; // reassign variable17 } // if } // while } // main
+Sub main() 1 Dim blocksname? = createBlockGraphics(white)value or expression? ' variable definition2 Dim xname? = 20value or expression? ' variable definition3 Dim yname? = 15value or expression? ' variable definition4 +While Truecondition? 5 blocks[x][y]variableName? = redvalue or expression? ' reassign variable6 displayBlocksprocedureName?(blocksarguments?) ' call procedure7 blocks[x][y]variableName? = blackvalue or expression? ' reassign variable8 Dim directionname? = randint(0, 3)value or expression? ' variable definition9 +If direction = 0condition? Then 10 xvariableName? = min({x + 1, 39})value or expression? ' reassign variable11 ElseIf direction = 1condition? Then12 xvariableName? = max({x - 1, 0})value or expression? ' reassign variable13 ElseIf direction = 2condition? Then14 yvariableName? = min({y + 1, 29})value or expression? ' reassign variable15 ElseIf direction = 3condition? Then16 yvariableName? = max({y - 1, 0})value or expression? ' reassign variable17 End If End While End Sub
public class Global {

+static void main() { 1 var blocksname? = createBlockGraphics(white)value or expression?;2 var xname? = 20value or expression?;3 var yname? = 15value or expression?;4 +while (truecondition?) { 5 blocks[x][y]variableName? = redvalue or expression?; // reassign variable6 displayBlocksprocedureName?(blocksarguments?); // call procedure7 blocks[x][y]variableName? = blackvalue or expression?; // reassign variable8 var directionname? = randint(0, 3)value or expression?;9 +if (direction == 0condition?) { 10 xvariableName? = min([x + 1, 39])value or expression?; // reassign variable11 } else if (direction == 1condition?) {12 xvariableName? = max([x - 1, 0])value or expression?; // reassign variable13 } else if (direction == 2condition?) {14 yvariableName? = min([y + 1, 29])value or expression?; // reassign variable15 } else if (direction == 3condition?) {16 yvariableName? = max([y - 1, 0])value or expression?; // reassign variable17 } // if } // while } // main
}

From the Ripple Sort demo. The main routine defines the list to be sorted, delegates most of the work to the inPlaceRippleSort procedure (not shown here), then prints the sorted list:

+main 1 variable liname? set to [3, 6, 1, 0, 99, 4, 67]value or expression?2 call inPlaceRippleSortprocedureName?(liarguments?)3 print(liarguments?)4 end main
+def main() -> None: 1 liname? = [3, 6, 1, 0, 99, 4, 67]value or expression? # variable definition2 inPlaceRippleSortprocedureName?(liarguments?) # call procedure3 print(liarguments?)4 main()
+static void main() { 1 var liname? = [3, 6, 1, 0, 99, 4, 67]value or expression?;2 inPlaceRippleSortprocedureName?(liarguments?); // call procedure3 Console.WriteLine(liarguments?); // print4 } // main
+Sub main() 1 Dim liname? = {3, 6, 1, 0, 99, 4, 67}value or expression? ' variable definition2 inPlaceRippleSortprocedureName?(liarguments?) ' call procedure3 Console.WriteLine(liarguments?) ' print4 End Sub
public class Global {

+static void main() { 1 var liname? = [3, 6, 1, 0, 99, 4, 67]value or expression?;2 inPlaceRippleSortprocedureName?(liarguments?); // call procedure3 System.out.println(liarguments?); // print4 } // main
}

From the Life procedural demo. The whole program is 157 instructions (including tests), but the main routine contains just 9. It is good practice to keep the main routine small.

+main 1 variable gridname? set to createBlockGraphics(white)value or expression?2 call fillRandomprocedureName?(gridarguments?)3 +while truecondition? 4 call displayBlocksprocedureName?(gridarguments?)5 variable gridRefname? set to new AsRef<of List<of List<of Int>>>(grid)value or expression?6 call nextGenerationprocedureName?(gridRefarguments?)7 reassign gridvariableName? to gridRef.value()value or expression?8 call sleep_msprocedureName?(50arguments?)9 end while end main
+def main() -> None: 1 gridname? = createBlockGraphics(white)value or expression? # variable definition2 fillRandomprocedureName?(gridarguments?) # call procedure3 +while Truecondition?: 4 displayBlocksprocedureName?(gridarguments?) # call procedure5 gridRefname? = AsRef[list[list[int]]](grid)value or expression? # variable definition6 nextGenerationprocedureName?(gridRefarguments?) # call procedure7 gridvariableName? = gridRef.value()value or expression? # reassign variable8 sleep_msprocedureName?(50arguments?) # call procedure9 main()
+static void main() { 1 var gridname? = createBlockGraphics(white)value or expression?;2 fillRandomprocedureName?(gridarguments?); // call procedure3 +while (truecondition?) { 4 displayBlocksprocedureName?(gridarguments?); // call procedure5 var gridRefname? = new AsRef<List<List<int>>>(grid)value or expression?;6 nextGenerationprocedureName?(gridRefarguments?); // call procedure7 gridvariableName? = gridRef.value()value or expression?; // reassign variable8 sleep_msprocedureName?(50arguments?); // call procedure9 } // while } // main
+Sub main() 1 Dim gridname? = createBlockGraphics(white)value or expression? ' variable definition2 fillRandomprocedureName?(gridarguments?) ' call procedure3 +While Truecondition? 4 displayBlocksprocedureName?(gridarguments?) ' call procedure5 Dim gridRefname? = New AsRef(Of List(Of List(Of Integer)))(grid)value or expression? ' variable definition6 nextGenerationprocedureName?(gridRefarguments?) ' call procedure7 gridvariableName? = gridRef.value()value or expression? ' reassign variable8 sleep_msprocedureName?(50arguments?) ' call procedure9 End While End Sub
public class Global {

+static void main() { 1 var gridname? = createBlockGraphics(white)value or expression?;2 fillRandomprocedureName?(gridarguments?); // call procedure3 +while (truecondition?) { 4 displayBlocksprocedureName?(gridarguments?); // call procedure5 var gridRefname? = new AsRef<List<List<int>>>(grid)value or expression?;6 nextGenerationprocedureName?(gridRefarguments?); // call procedure7 gridvariableName? = gridRef.value()value or expression?; // reassign variable8 sleep_msprocedureName?(50arguments?); // call procedure9 } // while } // main
}

What you need to know about the main routine

  • The first instruction added within the main routine, is always the first instruction to be executed when the program is run.
  • Without a main routine, there is nothing to run. However you may write and test functions without a main.
  • The main routine does not have to be at the top of the file, but this is a good convention to follow.
  • You may not add another main routine within a program, unless you first ghost the existing one.
  • The main routine may form the entire program (as in the Burrowexample above); more commonly it will delegate to procedures and functions defined by the programmer.

Procedure

Examples of procedures

From the Bubbles demo. It changes the vertical position, and the size, of each bubble in the provided list:

+procedure moveGrowBurstname?(bubbles as List<of CircleVG>parameter definitions?) 1 +for bitem? in bubblessource? 2 +if random() < 0.05condition? then 3 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? call b.setRadiusprocedureName?(0arguments?)4 call b.setCentreYprocedureName?(75arguments?)5 else6 # bubble rises and grows slightlycomment? call b.setCentreYprocedureName?(b.centreY - 1arguments?)7 call b.setRadiusprocedureName?(b.radius + 0.2arguments?)8 end if end for call displayVectorGraphicsprocedureName?(bubblesarguments?)9 call sleep_msprocedureName?(5arguments?)10 end procedure
+def moveGrowBurstname?(bubbles: list[CircleVG]parameter definitions?) -> None: # procedure1 +for bitem? in bubblessource?: 2 +if random() < 0.05condition?: 3 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) # call procedure4 b.setCentreYprocedureName?(75arguments?) # call procedure5 else:6 # bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) # call procedure7 b.setRadiusprocedureName?(b.radius + 0.2arguments?) # call procedure8 displayVectorGraphicsprocedureName?(bubblesarguments?) # call procedure9 sleep_msprocedureName?(5arguments?) # call procedure10
+static void moveGrowBurstname?(List<CircleVG> bubblesparameter definitions?) { // procedure1 +foreach (bitem? in bubblessource?) { 2 +if (random() < 0.05condition?) { 3 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // call procedure4 b.setCentreYprocedureName?(75arguments?); // call procedure5 } else {6 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // call procedure7 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // call procedure8 } // if } // foreach displayVectorGraphicsprocedureName?(bubblesarguments?); // call procedure9 sleep_msprocedureName?(5arguments?); // call procedure10 } // procedure
+Sub moveGrowBurstname?(bubbles As List(Of CircleVG)parameter definitions?) ' procedure1 +For Each bitem? In bubblessource? 2 +If random() < 0.05condition? Then 3 ' 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) ' call procedure4 b.setCentreYprocedureName?(75arguments?) ' call procedure5 Else6 ' bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) ' call procedure7 b.setRadiusprocedureName?(b.radius + 0.2arguments?) ' call procedure8 End If Next b displayVectorGraphicsprocedureName?(bubblesarguments?) ' call procedure9 sleep_msprocedureName?(5arguments?) ' call procedure10 End Sub
public class Global {

+static void moveGrowBurstname?(List<CircleVG> bubblesparameter definitions?) { // procedure1 +foreach (bitem? in bubblessource?) { 2 +if (random() < 0.05condition?) { 3 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // call procedure4 b.setCentreYprocedureName?(75arguments?); // call procedure5 } else {6 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // call procedure7 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // call procedure8 } // if } // foreach displayVectorGraphicsprocedureName?(bubblesarguments?); // call procedure9 sleep_msprocedureName?(5arguments?); // call procedure10 } // procedure
}

It is called within the main routine by this instruction: call moveGrowBurstprocedureName?(bubblesarguments?)0moveGrowBurstprocedureName?(bubblesarguments?) # call procedure0moveGrowBurstprocedureName?(bubblesarguments?); // call procedure0moveGrowBurstprocedureName?(bubblesarguments?) ' call procedure0moveGrowBurstprocedureName?(bubblesarguments?); // call procedure0

From the Turtle Dragon demo. The procedure defines two parameters of different types. The body of this procedure consists of a straight sequence of call procedure instructions, but all the procedures are defined on the turtle (the parameter named ttttt) using 'dot-syntax':

+procedure setupTurtlename?(t as Turtle, order as Intparameter definitions?) 1 call t.turnToHeadingprocedureName?(180 + order*45arguments?)2 call t.placeAtprocedureName?(-40, 20arguments?)3 call t.penColourprocedureName?(redarguments?)4 call t.penWidthprocedureName?(10.0/orderarguments?)5 call t.penDownprocedureName?(arguments?)6 call t.showprocedureName?(arguments?)7 end procedure
+def setupTurtlename?(t: Turtle, order: intparameter definitions?) -> None: # procedure1 t.turnToHeadingprocedureName?(180 + order*45arguments?) # call procedure2 t.placeAtprocedureName?(-40, 20arguments?) # call procedure3 t.penColourprocedureName?(redarguments?) # call procedure4 t.penWidthprocedureName?(10.0/orderarguments?) # call procedure5 t.penDownprocedureName?(arguments?) # call procedure6 t.showprocedureName?(arguments?) # call procedure7
+static void setupTurtlename?(Turtle t, int orderparameter definitions?) { // procedure1 t.turnToHeadingprocedureName?(180 + order*45arguments?); // call procedure2 t.placeAtprocedureName?(-40, 20arguments?); // call procedure3 t.penColourprocedureName?(redarguments?); // call procedure4 t.penWidthprocedureName?(10.0/orderarguments?); // call procedure5 t.penDownprocedureName?(arguments?); // call procedure6 t.showprocedureName?(arguments?); // call procedure7 } // procedure
+Sub setupTurtlename?(t As Turtle, order As Integerparameter definitions?) ' procedure1 t.turnToHeadingprocedureName?(180 + order*45arguments?) ' call procedure2 t.placeAtprocedureName?(-40, 20arguments?) ' call procedure3 t.penColourprocedureName?(redarguments?) ' call procedure4 t.penWidthprocedureName?(10.0/orderarguments?) ' call procedure5 t.penDownprocedureName?(arguments?) ' call procedure6 t.showprocedureName?(arguments?) ' call procedure7 End Sub
public class Global {

+static void setupTurtlename?(Turtle t, int orderparameter definitions?) { // procedure1 t.turnToHeadingprocedureName?(180 + order*45arguments?); // call procedure2 t.placeAtprocedureName?(-40, 20arguments?); // call procedure3 t.penColourprocedureName?(redarguments?); // call procedure4 t.penWidthprocedureName?(10.0/orderarguments?); // call procedure5 t.penDownprocedureName?(arguments?); // call procedure6 t.showprocedureName?(arguments?); // call procedure7 } // procedure
}

From the Life - procedural demo. The code sets each of the cells in the 40x30 gridgridgridgridgrid

+procedure fillRandomname?(grid as List<of List<of Int>>parameter definitions?) 1 +for colitem? in range(0, 40)source? 2 +for rowitem? in range(0, 30)source? 3 reassign grid[col][row]variableName? to blackOrWhite(random())value or expression?4 end for end for end procedure
+def fillRandomname?(grid: list[list[int]]parameter definitions?) -> None: # procedure1 +for colitem? in range(0, 40)source?: 2 +for rowitem? in range(0, 30)source?: 3 grid[col][row]variableName? = blackOrWhite(random())value or expression? # reassign variable4
+static void fillRandomname?(List<List<int>> gridparameter definitions?) { // procedure1 +foreach (colitem? in range(0, 40)source?) { 2 +foreach (rowitem? in range(0, 30)source?) { 3 grid[col][row]variableName? = blackOrWhite(random())value or expression?; // reassign variable4 } // foreach } // foreach } // procedure
+Sub fillRandomname?(grid As List(Of List(Of Integer))parameter definitions?) ' procedure1 +For Each colitem? In range(0, 40)source? 2 +For Each rowitem? In range(0, 30)source? 3 grid[col][row]variableName? = blackOrWhite(random())value or expression? ' reassign variable4 Next row Next col End Sub
public class Global {

+static void fillRandomname?(List<List<int>> gridparameter definitions?) { // procedure1 +foreach (colitem? in range(0, 40)source?) { 2 +foreach (rowitem? in range(0, 30)source?) { 3 grid[col][row]variableName? = blackOrWhite(random())value or expression?; // reassign variable4 } // foreach } // foreach } // procedure
}

What you need to know about procedures

  • A procedure (also known as a 'subroutine') defines a part of the program to which work is delegated, either by the main routine, or by another procedure.
  • The Elan library provides some ready-made procedures such as print and pause.
  • The procedure instruction allows you to define your own procedures.
  • A procedure is defined with a unique name, starting lower-case (moveGrowBurst).
  • It may optionally define one or more parameters (one in the example above), comma-separated if more than one.
  • Each parameter specifies a name, starting lower-case (bubblesbubblesbubblesbubblesbubbles above) and a type (List<of CircleVG>list[CircleVG]List<CircleVG>List(Of CircleVG)List<CircleVG> above) using the syntax bubbles as List Code does not parse as Elan.bubbles as List Code does not parse as Elan.bubbles as List Code does not parse as Elan.bubbles as List Code does not parse as Elan.bubbles as List Code does not parse as Elan.
  • The procedure is called from elsewhere using a call procedure instruction. which specifies the name of the procedure to be called, and includes a value(s) or expressions(s) that match the type(s) for the parameter(s).
  • The procedure always finishes on the last instruction defined within it, at which point the program continues from where the call instruction was located.
  • Changes made to parameter values (where possible) within the procedure (such as changing the radius and centre of each bubble in the list new codenew codenew codenew code
    public class Global {

    new code
    }
    which specifies the name of the procedure to be called, and includes a value(s) or expressions(s) that match the type(s) for the parameter(s).
  • The procedure always finishes on the last instruction defined within it, at which point the program continues from where the call instruction was located.
  • Changes made to parameter values (where possible) within the procedure (such as changing the radius and centre of each bubble in the list bubblesbubblesbubblesbubblesbubbles) will be visible to the calling code when the procedure exits.
  • Functions may be called from within a procedure, but not vice versa.
  • Instructions within a procedure - unlike within a function - may call other procedures, undertake input/output, and use system methods.

More advanced techniques

Call by reference

To change the value in named value arg supplied as an argument in a procedure call, both the call argument and the procedure parameter must use a reference (or pointer) to it, defined as type AsRefAsRefAsRefAsRefAsRef. This then allows use of the dot methods value and set on it to read and change its value, as in this example where argRef is a pointer to variable arg in main, so that arg can be accessed elsewhere by reference:

+main 1 variable argname? set to "abc"value or expression?2 variable argRefname? set to new AsRef<of String>(arg)value or expression?3 call changeArgprocedureName?(argRefarguments?)4 print(argRef.value()arguments?)5 end main +procedure changeArgname?(pointer as AsRef<of String>parameter definitions?) 6 variable rname? set to pointer.value()value or expression?7 call pointer.setprocedureName?(r.upperCase()arguments?)8 end procedure
+def main() -> None: 1 argname? = "abc"value or expression? # variable definition2 argRefname? = AsRef[str](arg)value or expression? # variable definition3 changeArgprocedureName?(argRefarguments?) # call procedure4 print(argRef.value()arguments?)5 +def changeArgname?(pointer: AsRef[str]parameter definitions?) -> None: # procedure6 rname? = pointer.value()value or expression? # variable definition7 pointer.setprocedureName?(r.upperCase()arguments?) # call procedure8 main()
+static void main() { 1 var argname? = "abc"value or expression?;2 var argRefname? = new AsRef<string>(arg)value or expression?;3 changeArgprocedureName?(argRefarguments?); // call procedure4 Console.WriteLine(argRef.value()arguments?); // print5 } // main +static void changeArgname?(AsRef<string> pointerparameter definitions?) { // procedure6 var rname? = pointer.value()value or expression?;7 pointer.setprocedureName?(r.upperCase()arguments?); // call procedure8 } // procedure
+Sub main() 1 Dim argname? = "abc"value or expression? ' variable definition2 Dim argRefname? = New AsRef(Of String)(arg)value or expression? ' variable definition3 changeArgprocedureName?(argRefarguments?) ' call procedure4 Console.WriteLine(argRef.value()arguments?) ' print5 End Sub +Sub changeArgname?(pointer As AsRef(Of String)parameter definitions?) ' procedure6 Dim rname? = pointer.value()value or expression? ' variable definition7 pointer.setprocedureName?(r.upperCase()arguments?) ' call procedure8 End Sub
public class Global {

+static void main() { 1 var argname? = "abc"value or expression?;2 var argRefname? = new AsRef<String>(arg)value or expression?;3 changeArgprocedureName?(argRefarguments?); // call procedure4 System.out.println(argRef.value()arguments?); // print5 } // main +static void changeArgname?(AsRef<String> pointerparameter definitions?) { // procedure6 var rname? = pointer.value()value or expression?;7 pointer.setprocedureName?(r.upperCase()arguments?); // call procedure8 } // procedure
}

Recursive procedure calls

A procedure may call itself, directly or indirectly (calling another procedure that then calls back to the first one). The following example is from the Turtle Snowflake demo, you can see that the drawSide procedure calls itself from four separate places within its body - in order to achieve the 'fractal' effect, where a basic shape repeats itself at different levels of scale. You can also observe two very important principles of recursion here:

  • That calls back into itself are with a smaller version of the problem - in this case a thirdthirdthirdthirdthird of the lengthlengthlengthlengthlength that the procedure was given.
  • That there is a terminating condition where the recursion 'bottoms out' - in this when the side length gets down to 1.

+procedure drawSidename?(length as Float, t as Turtleparameter definitions?) 1 +if (length > 1)condition? then 2 variable thirdname? set to length/3value or expression?3 call drawSideprocedureName?(third, targuments?)4 call t.turnprocedureName?(-60arguments?)5 call drawSideprocedureName?(third, targuments?)6 call t.turnprocedureName?(120arguments?)7 call drawSideprocedureName?(third, targuments?)8 call t.turnprocedureName?(-60arguments?)9 call drawSideprocedureName?(third, targuments?)10 else11 call t.moveprocedureName?(lengtharguments?)12 end if end procedure
+def drawSidename?(length: float, t: Turtleparameter definitions?) -> None: # procedure1 +if (length > 1)condition?: 2 thirdname? = length/3value or expression? # variable definition3 drawSideprocedureName?(third, targuments?) # call procedure4 t.turnprocedureName?(-60arguments?) # call procedure5 drawSideprocedureName?(third, targuments?) # call procedure6 t.turnprocedureName?(120arguments?) # call procedure7 drawSideprocedureName?(third, targuments?) # call procedure8 t.turnprocedureName?(-60arguments?) # call procedure9 drawSideprocedureName?(third, targuments?) # call procedure10 else:11 t.moveprocedureName?(lengtharguments?) # call procedure12
+static void drawSidename?(double length, Turtle tparameter definitions?) { // procedure1 +if ((length > 1)condition?) { 2 var thirdname? = length/3value or expression?;3 drawSideprocedureName?(third, targuments?); // call procedure4 t.turnprocedureName?(-60arguments?); // call procedure5 drawSideprocedureName?(third, targuments?); // call procedure6 t.turnprocedureName?(120arguments?); // call procedure7 drawSideprocedureName?(third, targuments?); // call procedure8 t.turnprocedureName?(-60arguments?); // call procedure9 drawSideprocedureName?(third, targuments?); // call procedure10 } else {11 t.moveprocedureName?(lengtharguments?); // call procedure12 } // if } // procedure
+Sub drawSidename?(length As Double, t As Turtleparameter definitions?) ' procedure1 +If (length > 1)condition? Then 2 Dim thirdname? = length/3value or expression? ' variable definition3 drawSideprocedureName?(third, targuments?) ' call procedure4 t.turnprocedureName?(-60arguments?) ' call procedure5 drawSideprocedureName?(third, targuments?) ' call procedure6 t.turnprocedureName?(120arguments?) ' call procedure7 drawSideprocedureName?(third, targuments?) ' call procedure8 t.turnprocedureName?(-60arguments?) ' call procedure9 drawSideprocedureName?(third, targuments?) ' call procedure10 Else11 t.moveprocedureName?(lengtharguments?) ' call procedure12 End If End Sub
public class Global {

+static void drawSidename?(double length, Turtle tparameter definitions?) { // procedure1 +if ((length > 1)condition?) { 2 var thirdname? = length/3value or expression?;3 drawSideprocedureName?(third, targuments?); // call procedure4 t.turnprocedureName?(-60arguments?); // call procedure5 drawSideprocedureName?(third, targuments?); // call procedure6 t.turnprocedureName?(120arguments?); // call procedure7 drawSideprocedureName?(third, targuments?); // call procedure8 t.turnprocedureName?(-60arguments?); // call procedure9 drawSideprocedureName?(third, targuments?); // call procedure10 } else {11 t.moveprocedureName?(lengtharguments?); // call procedure12 } // if } // procedure
}

Function

Examples of functions

From the Snake - procedural demo. Given the coordinates of the snake's head, determines whether or not the head has gone outside the bounds of the 40x30 grid:

+function hasHitEdgename?(headX as Int, headY as Intparameter definitions?) returns BooleanType? 1 return (headX < 0) or (headY < 0) or (headX > 39) or (headY > 29)value or expression?2 end function
+def hasHitEdgename?(headX: int, headY: intparameter definitions?) -> boolType?: # function1 return (headX < 0) or (headY < 0) or (headX > 39) or (headY > 29)value or expression?2
+static boolType? hasHitEdgename?(int headX, int headYparameter definitions?) { // function1 return (headX < 0) || (headY < 0) || (headX > 39) || (headY > 29)value or expression?;2 } // function
+Function hasHitEdgename?(headX As Integer, headY As Integerparameter definitions?) As BooleanType? 1 Return (headX < 0) Or (headY < 0) Or (headX > 39) Or (headY > 29)value or expression?2 End Function
public class Global {

+static boolType? hasHitEdgename?(int headX, int headYparameter definitions?) { // function1 return (headX < 0) || (headY < 0) || (headX > 39) || (headY > 29)value or expression?;2 } // function
}

It is called within the main routine as part of this instruction:

reassign gameOnvariableName? to not hasHitEdge(head[0], head[1]) and not body.contains(head)value or expression?0
gameOnvariableName? = not hasHitEdge(head[0], head[1]) and not body.contains(head)value or expression? # reassign variable0
gameOnvariableName? = !hasHitEdge(head[0], head[1]) && !body.contains(head)value or expression?; // reassign variable0
gameOnvariableName? = Not hasHitEdge(head[0], head[1]) And Not body.contains(head)value or expression? ' reassign variable0
gameOnvariableName? = !hasHitEdge(head[0], head[1]) && !body.contains(head)value or expression?; // reassign variable0

From the Date Time demo. A function to determine whether a given year is a leap year or not:

+function leapname?(year as Intparameter definitions?) returns BooleanType? 1 return (((year mod 4) is 0) and ((year mod 100) isnt 0)) or ((year mod 400) is 0)value or expression?2 end function
+def leapname?(year: intparameter definitions?) -> boolType?: # function1 return (((year % 4) == 0) and ((year % 100) != 0)) or ((year % 400) == 0)value or expression?2
+static boolType? leapname?(int yearparameter definitions?) { // function1 return (((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0)value or expression?;2 } // function
+Function leapname?(year As Integerparameter definitions?) As BooleanType? 1 Return (((year Mod 4) = 0) And ((year Mod 100) <> 0)) Or ((year Mod 400) = 0)value or expression?2 End Function
public class Global {

+static boolType? leapname?(int yearparameter definitions?) { // function1 return (((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0)value or expression?;2 } // function
}

From the Life - procedural demo a function that finds the cell immediately to the north of the cell provided. Both the single parameter and return value are 2-tuples, where both elements are of type IntintintIntegerint:

+function northname?(cell as (Int, Int)parameter definitions?) returns (Int, Int)Type? 1 variable xname? set to cell.item_0value or expression?2 variable yname? set to cell.item_1value or expression?3 variable y2name? set to y - 1value or expression?4 +if y2 is -1condition? then 5 reassign y2variableName? to 29value or expression?6 end if return (x, y2)value or expression?7 end function
+def northname?(cell: tuple[int, int]parameter definitions?) -> tuple[int, int]Type?: # function1 xname? = cell.item_0value or expression? # variable definition2 yname? = cell.item_1value or expression? # variable definition3 y2name? = y - 1value or expression? # variable definition4 +if y2 == -1condition?: 5 y2variableName? = 29value or expression? # reassign variable6 return (x, y2)value or expression?7
+static (int, int)Type? northname?((int, int) cellparameter definitions?) { // function1 var xname? = cell.item_0value or expression?;2 var yname? = cell.item_1value or expression?;3 var y2name? = y - 1value or expression?;4 +if (y2 == -1condition?) { 5 y2variableName? = 29value or expression?; // reassign variable6 } // if return (x, y2)value or expression?;7 } // function
+Function northname?(cell As (Integer, Integer)parameter definitions?) As (Integer, Integer)Type? 1 Dim xname? = cell.item_0value or expression? ' variable definition2 Dim yname? = cell.item_1value or expression? ' variable definition3 Dim y2name? = y - 1value or expression? ' variable definition4 +If y2 = -1condition? Then 5 y2variableName? = 29value or expression? ' reassign variable6 End If Return (x, y2)value or expression?7 End Function
public class Global {

+static (int, int)Type? northname?((int, int) cellparameter definitions?) { // function1 var xname? = cell.item_0value or expression?;2 var yname? = cell.item_1value or expression?;3 var y2name? = y - 1value or expression?;4 +if (y2 == -1condition?) { 5 y2variableName? = 29value or expression?; // reassign variable6 } // if return (x, y2)value or expression?;7 } // function
}

A function to search a (pre-sorted) list of strings using the binary search algorithm:

+function binarySearchname?(li as List<of String>, item as Stringparameter definitions?) returns BooleanType? 1 variable resultname? set to falsevalue or expression?2 +if li.length() > 0condition? then 3 variable midname? set to divAsInt(li.length(), 2)value or expression?4 variable valuename? set to li[mid]value or expression?5 +if item.equals(value)condition? then 6 reassign resultvariableName? to truevalue or expression?7 elif item.isBefore(value)condition? then8 reassign resultvariableName? to binarySearch(li.subList(0, mid), item)value or expression?9 else10 reassign resultvariableName? to binarySearch(li.subList(mid + 1, li.length()), item)value or expression?11 end if end if return resultvalue or expression?12 end function
+def binarySearchname?(li: list[str], item: strparameter definitions?) -> boolType?: # function1 resultname? = Falsevalue or expression? # variable definition2 +if li.length() > 0condition?: 3 midname? = divAsInt(li.length(), 2)value or expression? # variable definition4 valuename? = li[mid]value or expression? # variable definition5 +if item.equals(value)condition?: 6 resultvariableName? = Truevalue or expression? # reassign variable7 elif item.isBefore(value)condition?: # else if8 resultvariableName? = binarySearch(li.subList(0, mid), item)value or expression? # reassign variable9 else:10 resultvariableName? = binarySearch(li.subList(mid + 1, li.length()), item)value or expression? # reassign variable11 return resultvalue or expression?12
+static boolType? binarySearchname?(List<string> li, string itemparameter definitions?) { // function1 var resultname? = falsevalue or expression?;2 +if (li.length() > 0condition?) { 3 var midname? = divAsInt(li.length(), 2)value or expression?;4 var valuename? = li[mid]value or expression?;5 +if (item.equals(value)condition?) { 6 resultvariableName? = truevalue or expression?; // reassign variable7 } else if (item.isBefore(value)condition?) {8 resultvariableName? = binarySearch(li.subList(0, mid), item)value or expression?; // reassign variable9 } else {10 resultvariableName? = binarySearch(li.subList(mid + 1, li.length()), item)value or expression?; // reassign variable11 } // if } // if return resultvalue or expression?;12 } // function
+Function binarySearchname?(li As List(Of String), item As Stringparameter definitions?) As BooleanType? 1 Dim resultname? = Falsevalue or expression? ' variable definition2 +If li.length() > 0condition? Then 3 Dim midname? = divAsInt(li.length(), 2)value or expression? ' variable definition4 Dim valuename? = li[mid]value or expression? ' variable definition5 +If item.equals(value)condition? Then 6 resultvariableName? = Truevalue or expression? ' reassign variable7 ElseIf item.isBefore(value)condition? Then8 resultvariableName? = binarySearch(li.subList(0, mid), item)value or expression? ' reassign variable9 Else10 resultvariableName? = binarySearch(li.subList(mid + 1, li.length()), item)value or expression? ' reassign variable11 End If End If Return resultvalue or expression?12 End Function
public class Global {

+static boolType? binarySearchname?(List<String> li, String itemparameter definitions?) { // function1 var resultname? = falsevalue or expression?;2 +if (li.length() > 0condition?) { 3 var midname? = divAsInt(li.length(), 2)value or expression?;4 var valuename? = li[mid]value or expression?;5 +if (item.equals(value)condition?) { 6 resultvariableName? = truevalue or expression?; // reassign variable7 } else if (item.isBefore(value)condition?) {8 resultvariableName? = binarySearch(li.subList(0, mid), item)value or expression?; // reassign variable9 } else {10 resultvariableName? = binarySearch(li.subList(mid + 1, li.length()), item)value or expression?; // reassign variable11 } // if } // if return resultvalue or expression?;12 } // function
}

What you need to know about functions

  • A function defines a mechanism to transform input data (passed as parameters) into a 'returned' value.
  • Because it generates a value, a function call is a kind of expression - it may define a complete expression, a part of a more complex expression (as above).
  • The Elan library provides some ready-made functions such as sqrt.
  • The function instruction allows you to define your own functions.
  • A function is defined with a unique name, starting lower-case (hasHitEdge).
  • It will usually define one or more parameters (two in the example above), comma-separated if more than one.
  • Each parameter specifies a name starting lower-case (headXheadXheadXheadXheadX above) and a type (IntintintIntegerint above) using the syntax headX as Int Code does not parse as Elan.headX as Int Code does not parse as Elan.headX as Int Code does not parse as Elan.headX as Int Code does not parse as Elan.headX as Int Code does not parse as Elan.
  • The function signature (top line) must also specify the type of value it will return (returnsreturnsreturnsreturnsreturns BooleanboolboolBooleanbool above).
  • The returned value may be a simple value such as an IntintintIntegerint, or it may be a data structure such as a List<of Float>list[float]List<double>List(Of Double)List<double>.
  • A function always has a returnreturnreturnreturnreturn statement as its last instruction, which specifies a value, or an expression to be evaluated. The value, or the result of evaluating the expression, must yield the same type as specified in the signature.
  • Even though some languages would allow you to put the returnreturnreturnreturnreturn anywhere within the function, or even have more than one of them, Elan deliberately does not permit this - in any of the supported languages - because that would result in poorly-structured code.
  • The body of a function may consist only of the return statement - with the expression following returnreturnreturnreturnreturn doing all the work needed.
  • More generally, the return statement will be preceded by other statement instructions to determine the result in several steps.
  • While many languages treat functions as a procedure that returns a value - this is unhelpful. Elan enforces - for each of the supported languages - that functions are kept 'pure', like all functions used in mathematics. Specifically:
    • a function cannot undertake any input/output, or create any other 'side effect' (change to the system that can be observed outside the function).
    • prohibited side-effects including changing any of the values passed in as parameters.
    • the returned result must depend solely, and deterministically, on the values provided as parameters - just as sqrt(2)sqrt(2)sqrt(2)sqrt(2)sqrt(2) always produces the same result, irrespective of whatever else might be occurring in the program.

Test

Examples of tests

From the Date Time demo, testing the function that determines whether a given year is a leap year. Note that the comments distinguish between the 'normal' test cases and the 'boundary' (also known as 'edge') cases, where errors are more common:

+test test_leaptest_name? 1 # normal casescomment? assert leap(2025)actual (computed) value? is falseexpected value? not run2 assert leap(2024)actual (computed) value? is trueexpected value? not run3 # boundary casescomment? assert leap(1900)actual (computed) value? is falseexpected value? not run4 assert leap(2000)actual (computed) value? is trueexpected value? not run5 end test
+def test_leaptest_name?(self) -> None: 1 # normal casescomment? self.assertEqual(leap(2025)actual (computed) value?, Falseexpected value?) not run2 self.assertEqual(leap(2024)actual (computed) value?, Trueexpected value?) not run3 # boundary casescomment? self.assertEqual(leap(1900)actual (computed) value?, Falseexpected value?) not run4 self.assertEqual(leap(2000)actual (computed) value?, Trueexpected value?) not run5
+[TestMethod] static void test_leaptest_name?() { 1 // normal casescomment? Assert.AreEqual(falseexpected value?, leap(2025)actual (computed) value?); not run2 Assert.AreEqual(trueexpected value?, leap(2024)actual (computed) value?); not run3 // boundary casescomment? Assert.AreEqual(falseexpected value?, leap(1900)actual (computed) value?); not run4 Assert.AreEqual(trueexpected value?, leap(2000)actual (computed) value?); not run5 } // test
+<TestMethod> Sub test_leaptest_name?() 1 ' normal casescomment? Assert.AreEqual(Falseexpected value?, leap(2025)actual (computed) value?) not run2 Assert.AreEqual(Trueexpected value?, leap(2024)actual (computed) value?) not run3 ' boundary casescomment? Assert.AreEqual(Falseexpected value?, leap(1900)actual (computed) value?) not run4 Assert.AreEqual(Trueexpected value?, leap(2000)actual (computed) value?) not run5 End Sub
public class Global {

+@Test static void test_leaptest_name?() { 1 // normal casescomment? assertEquals(falseexpected value?, leap(2025)actual (computed) value?); not run2 assertEquals(trueexpected value?, leap(2024)actual (computed) value?); not run3 // boundary casescomment? assertEquals(falseexpected value?, leap(1900)actual (computed) value?); not run4 assertEquals(trueexpected value?, leap(2000)actual (computed) value?); not run5 } // test
}

Example from the Life - procedural demo. It tests all valid combinations of parameter values - because the logic of this function is quite unusual:

+test test_willLivetest_name? 1 assert willLive(white, 0)actual (computed) value? is falseexpected value? not run2 assert willLive(white, 1)actual (computed) value? is falseexpected value? not run3 assert willLive(white, 2)actual (computed) value? is falseexpected value? not run4 assert willLive(white, 3)actual (computed) value? is trueexpected value? not run5 assert willLive(white, 4)actual (computed) value? is falseexpected value? not run6 assert willLive(white, 5)actual (computed) value? is falseexpected value? not run7 assert willLive(white, 6)actual (computed) value? is falseexpected value? not run8 assert willLive(white, 7)actual (computed) value? is falseexpected value? not run9 assert willLive(white, 8)actual (computed) value? is falseexpected value? not run10 assert willLive(black, 0)actual (computed) value? is falseexpected value? not run11 assert willLive(black, 1)actual (computed) value? is falseexpected value? not run12 assert willLive(black, 2)actual (computed) value? is trueexpected value? not run13 assert willLive(black, 3)actual (computed) value? is trueexpected value? not run14 assert willLive(black, 4)actual (computed) value? is falseexpected value? not run15 assert willLive(black, 5)actual (computed) value? is falseexpected value? not run16 assert willLive(black, 6)actual (computed) value? is falseexpected value? not run17 assert willLive(black, 7)actual (computed) value? is falseexpected value? not run18 assert willLive(black, 8)actual (computed) value? is falseexpected value? not run19 end test
+def test_willLivetest_name?(self) -> None: 1 self.assertEqual(willLive(white, 0)actual (computed) value?, Falseexpected value?) not run2 self.assertEqual(willLive(white, 1)actual (computed) value?, Falseexpected value?) not run3 self.assertEqual(willLive(white, 2)actual (computed) value?, Falseexpected value?) not run4 self.assertEqual(willLive(white, 3)actual (computed) value?, Trueexpected value?) not run5 self.assertEqual(willLive(white, 4)actual (computed) value?, Falseexpected value?) not run6 self.assertEqual(willLive(white, 5)actual (computed) value?, Falseexpected value?) not run7 self.assertEqual(willLive(white, 6)actual (computed) value?, Falseexpected value?) not run8 self.assertEqual(willLive(white, 7)actual (computed) value?, Falseexpected value?) not run9 self.assertEqual(willLive(white, 8)actual (computed) value?, Falseexpected value?) not run10 self.assertEqual(willLive(black, 0)actual (computed) value?, Falseexpected value?) not run11 self.assertEqual(willLive(black, 1)actual (computed) value?, Falseexpected value?) not run12 self.assertEqual(willLive(black, 2)actual (computed) value?, Trueexpected value?) not run13 self.assertEqual(willLive(black, 3)actual (computed) value?, Trueexpected value?) not run14 self.assertEqual(willLive(black, 4)actual (computed) value?, Falseexpected value?) not run15 self.assertEqual(willLive(black, 5)actual (computed) value?, Falseexpected value?) not run16 self.assertEqual(willLive(black, 6)actual (computed) value?, Falseexpected value?) not run17 self.assertEqual(willLive(black, 7)actual (computed) value?, Falseexpected value?) not run18 self.assertEqual(willLive(black, 8)actual (computed) value?, Falseexpected value?) not run19
+[TestMethod] static void test_willLivetest_name?() { 1 Assert.AreEqual(falseexpected value?, willLive(white, 0)actual (computed) value?); not run2 Assert.AreEqual(falseexpected value?, willLive(white, 1)actual (computed) value?); not run3 Assert.AreEqual(falseexpected value?, willLive(white, 2)actual (computed) value?); not run4 Assert.AreEqual(trueexpected value?, willLive(white, 3)actual (computed) value?); not run5 Assert.AreEqual(falseexpected value?, willLive(white, 4)actual (computed) value?); not run6 Assert.AreEqual(falseexpected value?, willLive(white, 5)actual (computed) value?); not run7 Assert.AreEqual(falseexpected value?, willLive(white, 6)actual (computed) value?); not run8 Assert.AreEqual(falseexpected value?, willLive(white, 7)actual (computed) value?); not run9 Assert.AreEqual(falseexpected value?, willLive(white, 8)actual (computed) value?); not run10 Assert.AreEqual(falseexpected value?, willLive(black, 0)actual (computed) value?); not run11 Assert.AreEqual(falseexpected value?, willLive(black, 1)actual (computed) value?); not run12 Assert.AreEqual(trueexpected value?, willLive(black, 2)actual (computed) value?); not run13 Assert.AreEqual(trueexpected value?, willLive(black, 3)actual (computed) value?); not run14 Assert.AreEqual(falseexpected value?, willLive(black, 4)actual (computed) value?); not run15 Assert.AreEqual(falseexpected value?, willLive(black, 5)actual (computed) value?); not run16 Assert.AreEqual(falseexpected value?, willLive(black, 6)actual (computed) value?); not run17 Assert.AreEqual(falseexpected value?, willLive(black, 7)actual (computed) value?); not run18 Assert.AreEqual(falseexpected value?, willLive(black, 8)actual (computed) value?); not run19 } // test
+<TestMethod> Sub test_willLivetest_name?() 1 Assert.AreEqual(Falseexpected value?, willLive(white, 0)actual (computed) value?) not run2 Assert.AreEqual(Falseexpected value?, willLive(white, 1)actual (computed) value?) not run3 Assert.AreEqual(Falseexpected value?, willLive(white, 2)actual (computed) value?) not run4 Assert.AreEqual(Trueexpected value?, willLive(white, 3)actual (computed) value?) not run5 Assert.AreEqual(Falseexpected value?, willLive(white, 4)actual (computed) value?) not run6 Assert.AreEqual(Falseexpected value?, willLive(white, 5)actual (computed) value?) not run7 Assert.AreEqual(Falseexpected value?, willLive(white, 6)actual (computed) value?) not run8 Assert.AreEqual(Falseexpected value?, willLive(white, 7)actual (computed) value?) not run9 Assert.AreEqual(Falseexpected value?, willLive(white, 8)actual (computed) value?) not run10 Assert.AreEqual(Falseexpected value?, willLive(black, 0)actual (computed) value?) not run11 Assert.AreEqual(Falseexpected value?, willLive(black, 1)actual (computed) value?) not run12 Assert.AreEqual(Trueexpected value?, willLive(black, 2)actual (computed) value?) not run13 Assert.AreEqual(Trueexpected value?, willLive(black, 3)actual (computed) value?) not run14 Assert.AreEqual(Falseexpected value?, willLive(black, 4)actual (computed) value?) not run15 Assert.AreEqual(Falseexpected value?, willLive(black, 5)actual (computed) value?) not run16 Assert.AreEqual(Falseexpected value?, willLive(black, 6)actual (computed) value?) not run17 Assert.AreEqual(Falseexpected value?, willLive(black, 7)actual (computed) value?) not run18 Assert.AreEqual(Falseexpected value?, willLive(black, 8)actual (computed) value?) not run19 End Sub
public class Global {

+@Test static void test_willLivetest_name?() { 1 assertEquals(falseexpected value?, willLive(white, 0)actual (computed) value?); not run2 assertEquals(falseexpected value?, willLive(white, 1)actual (computed) value?); not run3 assertEquals(falseexpected value?, willLive(white, 2)actual (computed) value?); not run4 assertEquals(trueexpected value?, willLive(white, 3)actual (computed) value?); not run5 assertEquals(falseexpected value?, willLive(white, 4)actual (computed) value?); not run6 assertEquals(falseexpected value?, willLive(white, 5)actual (computed) value?); not run7 assertEquals(falseexpected value?, willLive(white, 6)actual (computed) value?); not run8 assertEquals(falseexpected value?, willLive(white, 7)actual (computed) value?); not run9 assertEquals(falseexpected value?, willLive(white, 8)actual (computed) value?); not run10 assertEquals(falseexpected value?, willLive(black, 0)actual (computed) value?); not run11 assertEquals(falseexpected value?, willLive(black, 1)actual (computed) value?); not run12 assertEquals(trueexpected value?, willLive(black, 2)actual (computed) value?); not run13 assertEquals(trueexpected value?, willLive(black, 3)actual (computed) value?); not run14 assertEquals(falseexpected value?, willLive(black, 4)actual (computed) value?); not run15 assertEquals(falseexpected value?, willLive(black, 5)actual (computed) value?); not run16 assertEquals(falseexpected value?, willLive(black, 6)actual (computed) value?); not run17 assertEquals(falseexpected value?, willLive(black, 7)actual (computed) value?); not run18 assertEquals(falseexpected value?, willLive(black, 8)actual (computed) value?); not run19 } // test
}

Example from the Snake - procedural demo, showing the use of a variable to create data to be used within multiple test cases (asserts):

+test test_getAdjacentSquaretest_name? 1 variable sqname? set to [20, 15]value or expression?2 assert getAdjacentSquare(sq, Direction.up)actual (computed) value? is [20, 14]expected value? not run3 assert getAdjacentSquare(sq, Direction.down)actual (computed) value? is [20, 16]expected value? not run4 assert getAdjacentSquare(sq, Direction.left)actual (computed) value? is [19, 15]expected value? not run5 assert getAdjacentSquare(sq, Direction.right)actual (computed) value? is [21, 15]expected value? not run6 # boundarycomment? assert getAdjacentSquare([0, 15], Direction.left)actual (computed) value? is [-1, 15]expected value? not run7 end test
+def test_getAdjacentSquaretest_name?(self) -> None: 1 sqname? = [20, 15]value or expression? # variable definition2 self.assertEqual(getAdjacentSquare(sq, Direction.up)actual (computed) value?, [20, 14]expected value?) not run3 self.assertEqual(getAdjacentSquare(sq, Direction.down)actual (computed) value?, [20, 16]expected value?) not run4 self.assertEqual(getAdjacentSquare(sq, Direction.left)actual (computed) value?, [19, 15]expected value?) not run5 self.assertEqual(getAdjacentSquare(sq, Direction.right)actual (computed) value?, [21, 15]expected value?) not run6 # boundarycomment? self.assertEqual(getAdjacentSquare([0, 15], Direction.left)actual (computed) value?, [-1, 15]expected value?) not run7
+[TestMethod] static void test_getAdjacentSquaretest_name?() { 1 var sqname? = [20, 15]value or expression?;2 Assert.AreEqual([20, 14]expected value?, getAdjacentSquare(sq, Direction.up)actual (computed) value?); not run3 Assert.AreEqual([20, 16]expected value?, getAdjacentSquare(sq, Direction.down)actual (computed) value?); not run4 Assert.AreEqual([19, 15]expected value?, getAdjacentSquare(sq, Direction.left)actual (computed) value?); not run5 Assert.AreEqual([21, 15]expected value?, getAdjacentSquare(sq, Direction.right)actual (computed) value?); not run6 // boundarycomment? Assert.AreEqual([-1, 15]expected value?, getAdjacentSquare([0, 15], Direction.left)actual (computed) value?); not run7 } // test
+<TestMethod> Sub test_getAdjacentSquaretest_name?() 1 Dim sqname? = {20, 15}value or expression? ' variable definition2 Assert.AreEqual({20, 14}expected value?, getAdjacentSquare(sq, Direction.up)actual (computed) value?) not run3 Assert.AreEqual({20, 16}expected value?, getAdjacentSquare(sq, Direction.down)actual (computed) value?) not run4 Assert.AreEqual({19, 15}expected value?, getAdjacentSquare(sq, Direction.left)actual (computed) value?) not run5 Assert.AreEqual({21, 15}expected value?, getAdjacentSquare(sq, Direction.right)actual (computed) value?) not run6 ' boundarycomment? Assert.AreEqual({-1, 15}expected value?, getAdjacentSquare({0, 15}, Direction.left)actual (computed) value?) not run7 End Sub
public class Global {

+@Test static void test_getAdjacentSquaretest_name?() { 1 var sqname? = [20, 15]value or expression?;2 assertEquals([20, 14]expected value?, getAdjacentSquare(sq, Direction.up)actual (computed) value?); not run3 assertEquals([20, 16]expected value?, getAdjacentSquare(sq, Direction.down)actual (computed) value?); not run4 assertEquals([19, 15]expected value?, getAdjacentSquare(sq, Direction.left)actual (computed) value?); not run5 assertEquals([21, 15]expected value?, getAdjacentSquare(sq, Direction.right)actual (computed) value?); not run6 // boundarycomment? assertEquals([-1, 15]expected value?, getAdjacentSquare([0, 15], Direction.left)actual (computed) value?); not run7 } // test
}

What you need to know about tests

  • The form of automated testing provided by Elan (for all of the supported languages) are known as 'unit testing'.
  • Some languages allow unit tests to be applied to procedures, but this dangerous since running the tests could result in unwanted damage to the system (such as writing to a database); Elan guarantees that tests are safe by restrict them to functions.
  • A test method must be given a name that starts with the prefix `test_` (added automatically) and followed, typically, by the name of the function.
  • Tests are always written at global level in Elan, but they may be anywhere within the file: you may write a test adjacent to the function being tested, or group all the tests at the end of the file perhaps.
  • A test method may define variables for use in the test - for example to define a data value that will be used in several asserts.
  • A test method must include one or more assert statements - with each one typically covering a different use-case.
  • Each assert instruction has two fields that the programmer must complete: the actual (computed) result - which typically consists of a call to the function being tested, with specific arguments - and the expected result.
  • The tests do not form part of the executable program: you cannot call a test from within other code.
  • Instead, all tests in a program are run automatically, each time the code compiles.
  • The result of each assert in each test is shown alongside the assert instruction, and the overall summary is shown in the status panel at the top centre of the Elan IDE.
  • Writing tests for every function is good practice - confirming that your function implementations are correct: when you write them, every time you re-load the program from file.
  • Tests also encourage you to 're-factor', improve, or extend the capability of your functions without fear that you have inadvertently made then incorrect for certain cases.

More advanced techniques

Ghosting tests

To deliberately ensure that a test is not run, use ghostingghostingghostingghostingghosting .

Even when tests or assert equals are ghostedghostedghostedghostedghosted, all the tests will be run and their status shown, but the overall test status will show the status of only the unghosted tests (green pass, amber warning or red fail).

You can ghost an entire test:

+test test_overAppletest_name? variable g1name? set to new Game(new Random())value or expression? variable g2name? set to g1.withApple(new Square(23, 15))value or expression? assert headOverApple(g2)actual (computed) value? is falseexpected value? not run variable g3name? set to g2.withHead(new Square(23, 15))value or expression? assert headOverApple(g3)actual (computed) value? is trueexpected value? not run end test
+def test_overAppletest_name?(self) -> None: g1name? = Game(Random())value or expression? # variable definition g2name? = g1.withApple(Square(23, 15))value or expression? # variable definition self.assertEqual(headOverApple(g2)actual (computed) value?, Falseexpected value?) not run g3name? = g2.withHead(Square(23, 15))value or expression? # variable definition self.assertEqual(headOverApple(g3)actual (computed) value?, Trueexpected value?) not run
+[TestMethod] static void test_overAppletest_name?() { var g1name? = new Game(new Random())value or expression?; var g2name? = g1.withApple(new Square(23, 15))value or expression?; Assert.AreEqual(falseexpected value?, headOverApple(g2)actual (computed) value?); not run var g3name? = g2.withHead(new Square(23, 15))value or expression?; Assert.AreEqual(trueexpected value?, headOverApple(g3)actual (computed) value?); not run } // test
+<TestMethod> Sub test_overAppletest_name?() Dim g1name? = New Game(New Random())value or expression? ' variable definition Dim g2name? = g1.withApple(New Square(23, 15))value or expression? ' variable definition Assert.AreEqual(Falseexpected value?, headOverApple(g2)actual (computed) value?) not run Dim g3name? = g2.withHead(New Square(23, 15))value or expression? ' variable definition Assert.AreEqual(Trueexpected value?, headOverApple(g3)actual (computed) value?) not run End Sub
public class Global {

+@Test static void test_overAppletest_name?() { var g1name? = new Game(new Random())value or expression?; var g2name? = g1.withApple(new Square(23, 15))value or expression?; assertEquals(falseexpected value?, headOverApple(g2)actual (computed) value?); not run var g3name? = g2.withHead(new Square(23, 15))value or expression?; assertEquals(trueexpected value?, headOverApple(g3)actual (computed) value?); not run } // test
}

or you can ghost any individual assert(s):

+test test_overAppletest_name? 1 variable g1name? set to new Game(new Random())value or expression?2 variable g2name? set to g1.withApple(new Square(23, 15))value or expression?3 assert headOverApple(g2)actual (computed) value? is trueexpected value? not run variable g3name? set to g2.withHead(new Square(23, 15))value or expression?4 assert headOverApple(g3)actual (computed) value? is trueexpected value? not run5 end test
+def test_overAppletest_name?(self) -> None: 1 g1name? = Game(Random())value or expression? # variable definition2 g2name? = g1.withApple(Square(23, 15))value or expression? # variable definition3 self.assertEqual(headOverApple(g2)actual (computed) value?, Trueexpected value?) not run g3name? = g2.withHead(Square(23, 15))value or expression? # variable definition4 self.assertEqual(headOverApple(g3)actual (computed) value?, Trueexpected value?) not run5
+[TestMethod] static void test_overAppletest_name?() { 1 var g1name? = new Game(new Random())value or expression?;2 var g2name? = g1.withApple(new Square(23, 15))value or expression?;3 Assert.AreEqual(trueexpected value?, headOverApple(g2)actual (computed) value?); not run var g3name? = g2.withHead(new Square(23, 15))value or expression?;4 Assert.AreEqual(trueexpected value?, headOverApple(g3)actual (computed) value?); not run5 } // test
+<TestMethod> Sub test_overAppletest_name?() 1 Dim g1name? = New Game(New Random())value or expression? ' variable definition2 Dim g2name? = g1.withApple(New Square(23, 15))value or expression? ' variable definition3 Assert.AreEqual(Trueexpected value?, headOverApple(g2)actual (computed) value?) not run Dim g3name? = g2.withHead(New Square(23, 15))value or expression? ' variable definition4 Assert.AreEqual(Trueexpected value?, headOverApple(g3)actual (computed) value?) not run5 End Sub
public class Global {

+@Test static void test_overAppletest_name?() { 1 var g1name? = new Game(new Random())value or expression?;2 var g2name? = g1.withApple(new Square(23, 15))value or expression?;3 assertEquals(trueexpected value?, headOverApple(g2)actual (computed) value?); not run var g3name? = g2.withHead(new Square(23, 15))value or expression?;4 assertEquals(trueexpected value?, headOverApple(g3)actual (computed) value?); not run5 } // test
}

Testing FloatfloatdoubleDoubledouble values

When testing FloatfloatdoubleDoubledouble values it is recommend that you use the round method to round the computed result to a fixed number of decimal places. This avoids rounding errors and is easier to read. For example:

+test test_roundtest_name? 1 assert sqrt(2).round(3)actual (computed) value? is 1.414expected value? not run2 end test
+def test_roundtest_name?(self) -> None: 1 self.assertEqual(sqrt(2).round(3)actual (computed) value?, 1.414expected value?) not run2
+[TestMethod] static void test_roundtest_name?() { 1 Assert.AreEqual(1.414expected value?, sqrt(2).round(3)actual (computed) value?); not run2 } // test
+<TestMethod> Sub test_roundtest_name?() 1 Assert.AreEqual(1.414expected value?, sqrt(2).round(3)actual (computed) value?) not run2 End Sub
public class Global {

+@Test static void test_roundtest_name?() { 1 assertEquals(1.414expected value?, sqrt(2).round(3)actual (computed) value?); not run2 } // test
}

Testing for runtime errors

If the expression you are testing would cause a runtime error then the error will be displayed in the red fail message:

If there are failures, mark the tests that you added since the last successful test as ghostedghostedghostedghostedghosted and then remove their ghosted status one by one until the cause is identified and fixed.

+test test_listtest_name? 1 variable aname? set to [5, 1, 7]value or expression?2 assert a[0]actual (computed) value? is 5expected value? not run3 assert a[2]actual (computed) value? is 7expected value? not run4 end test
+def test_listtest_name?(self) -> None: 1 aname? = [5, 1, 7]value or expression? # variable definition2 self.assertEqual(a[0]actual (computed) value?, 5expected value?) not run3 self.assertEqual(a[2]actual (computed) value?, 7expected value?) not run4
+[TestMethod] static void test_listtest_name?() { 1 var aname? = [5, 1, 7]value or expression?;2 Assert.AreEqual(5expected value?, a[0]actual (computed) value?); not run3 Assert.AreEqual(7expected value?, a[2]actual (computed) value?); not run4 } // test
+<TestMethod> Sub test_listtest_name?() 1 Dim aname? = {5, 1, 7}value or expression? ' variable definition2 Assert.AreEqual(5expected value?, a[0]actual (computed) value?) not run3 Assert.AreEqual(7expected value?, a[2]actual (computed) value?) not run4 End Sub
public class Global {

+@Test static void test_listtest_name?() { 1 var aname? = [5, 1, 7]value or expression?;2 assertEquals(5expected value?, a[0]actual (computed) value?); not run3 assertEquals(7expected value?, a[2]actual (computed) value?); not run4 } // test
}

This assert shows how to test for an expected error message:

+test test_messagetest_name? 1 variable aname? set to [5, 1, 7]value or expression?2 assert a[4]actual (computed) value? is 0expected value? not run3 assert a[4]actual (computed) value? is "Out of range index: 4 size: 3"expected value? not run4 end test
+def test_messagetest_name?(self) -> None: 1 aname? = [5, 1, 7]value or expression? # variable definition2 self.assertEqual(a[4]actual (computed) value?, 0expected value?) not run3 self.assertEqual(a[4]actual (computed) value?, "Out of range index: 4 size: 3"expected value?) not run4
+[TestMethod] static void test_messagetest_name?() { 1 var aname? = [5, 1, 7]value or expression?;2 Assert.AreEqual(0expected value?, a[4]actual (computed) value?); not run3 Assert.AreEqual("Out of range index: 4 size: 3"expected value?, a[4]actual (computed) value?); not run4 } // test
+<TestMethod> Sub test_messagetest_name?() 1 Dim aname? = {5, 1, 7}value or expression? ' variable definition2 Assert.AreEqual(0expected value?, a[4]actual (computed) value?) not run3 Assert.AreEqual("Out of range index: 4 size: 3"expected value?, a[4]actual (computed) value?) not run4 End Sub
public class Global {

+@Test static void test_messagetest_name?() { 1 var aname? = [5, 1, 7]value or expression?;2 assertEquals(0expected value?, a[4]actual (computed) value?); not run3 assertEquals("Out of range index: 4 size: 3"expected value?, a[4]actual (computed) value?); not run4 } // test
}

Testing long strings

If you have a test that compares strings longer than 20 characters, any test failure message will be reduced to reporting the first character at which the actual (computed) and expected strings differ. In the following example, the programmer has accidentally missed out the `/` in the closing html tag:

+constant sGWname? set to "grid { display: flex; flex-direction: column; margin-top: 40px; width: 500px; } word { display: flex; flex-direction: row; margin: auto; }"literal value or data structure?1 +function setInStylename?(s as Stringparameter definitions?) returns StringType? 2 return "<style>" + s + "<style>"value or expression?3 end function +test test_setInStyletest_name? 4 assert setInStyle(sGW)actual (computed) value? is "<style>" + sGW + "<style>"expected value? not run5 # assert fail message will be "s found at [146] expected: )"comment? end test
+sGWname? = "grid { display: flex; flex-direction: column; margin-top: 40px; width: 500px; } word { display: flex; flex-direction: row; margin: auto; }"literal value or data structure? # constant1 +def setInStylename?(s: strparameter definitions?) -> strType?: # function2 return "<style>" + s + "<style>"value or expression?3 +def test_setInStyletest_name?(self) -> None: 4 self.assertEqual(setInStyle(sGW)actual (computed) value?, "<style>" + sGW + "<style>"expected value?) not run5 # assert fail message will be "s found at [146] expected: )"comment?
+const String sGWname? = "grid { display: flex; flex-direction: column; margin-top: 40px; width: 500px; } word { display: flex; flex-direction: row; margin: auto; }"literal value or data structure?;1 +static stringType? setInStylename?(string sparameter definitions?) { // function2 return "<style>" + s + "<style>"value or expression?;3 } // function +[TestMethod] static void test_setInStyletest_name?() { 4 Assert.AreEqual("<style>" + sGW + "<style>"expected value?, setInStyle(sGW)actual (computed) value?); not run5 // assert fail message will be "s found at [146] expected: )"comment? } // test
+Const sGWname? = "grid { display: flex; flex-direction: column; margin-top: 40px; width: 500px; } word { display: flex; flex-direction: row; margin: auto; }"literal value or data structure?1 +Function setInStylename?(s As Stringparameter definitions?) As StringType? 2 Return "<style>" + s + "<style>"value or expression?3 End Function +<TestMethod> Sub test_setInStyletest_name?() 4 Assert.AreEqual("<style>" + sGW + "<style>"expected value?, setInStyle(sGW)actual (computed) value?) not run5 ' assert fail message will be "s found at [146] expected: )"comment? End Sub
public class Global {

+final String sGWname? = "grid { display: flex; flex-direction: column; margin-top: 40px; width: 500px; } word { display: flex; flex-direction: row; margin: auto; }"literal value or data structure?; // constant1 +static StringType? setInStylename?(String sparameter definitions?) { // function2 return "<style>" + s + "<style>"value or expression?;3 } // function +@Test static void test_setInStyletest_name?() { 4 assertEquals("<style>" + sGW + "<style>"expected value?, setInStyle(sGW)actual (computed) value?); not run5 // assert fail message will be "s found at [146] expected: )"comment? } // test
}

Non-terminating loops and recursion

The principal reason for ghosting a test is when either the test code, or code in any function being called, does not terminate. This typically means that there is a loop (or a recursive call) with no exit condition, or where the exit condition is never met.

If you do create such code without realising it, then when the tests are executed the test runner will time out after a few seconds (most tests will pass in milliseconds), and an error message will appear. Your priority should then be to identify the cause of the timeout and attempt to fix it before then unghosting the testtesttesttesttest.

Constant

Examples of constants

From the Turtle Snowflake demo. The constant value sidesidesidesideside is defined here:

+constant sidename? set to 100literal value or data structure?1
+sidename? = 100literal value or data structure? # constant1
+const Int sidename? = 100literal value or data structure?;1
+Const sidename? = 100literal value or data structure?1
public class Global {

+final Int sidename? = 100literal value or data structure?; // constant1
}

and used within the main routine as an argument to pass into the drawSide procedure:

call drawSideprocedureName?(side, targuments?)0
drawSideprocedureName?(side, targuments?) # call procedure0
drawSideprocedureName?(side, targuments?); // call procedure0
drawSideprocedureName?(side, targuments?) ' call procedure0
drawSideprocedureName?(side, targuments?); // call procedure0
+constant maxHitsname? set to 10literal value or data structure?1 +constant turquoisename? set to 0x00ced1literal value or data structure?2 +constant liveCellname? set to blackliteral value or data structure?3 +constant speedOfLightname? set to 299792.458literal value or data structure?4 +constant gameOvername? set to trueliteral value or data structure?5 +constant euroname? set to 0x20acliteral value or data structure?6 +constant warningMsgname? set to "Limit reached"literal value or data structure?7
+maxHitsname? = 10literal value or data structure? # constant1 +turquoisename? = 0x00ced1literal value or data structure? # constant2 +liveCellname? = blackliteral value or data structure? # constant3 +speedOfLightname? = 299792.458literal value or data structure? # constant4 +gameOvername? = Trueliteral value or data structure? # constant5 +euroname? = 0x20acliteral value or data structure? # constant6 +warningMsgname? = "Limit reached"literal value or data structure? # constant7
+const Int maxHitsname? = 10literal value or data structure?;1 +const Int turquoisename? = 0x00ced1literal value or data structure?;2 +const Int liveCellname? = blackliteral value or data structure?;3 +const Float speedOfLightname? = 299792.458literal value or data structure?;4 +const Boolean gameOvername? = trueliteral value or data structure?;5 +const Int euroname? = 0x20acliteral value or data structure?;6 +const String warningMsgname? = "Limit reached"literal value or data structure?;7
+Const maxHitsname? = 10literal value or data structure?1 +Const turquoisename? = &H00ced1literal value or data structure?2 +Const liveCellname? = blackliteral value or data structure?3 +Const speedOfLightname? = 299792.458literal value or data structure?4 +Const gameOvername? = Trueliteral value or data structure?5 +Const euroname? = &H20acliteral value or data structure?6 +Const warningMsgname? = "Limit reached"literal value or data structure?7
public class Global {

+final Int maxHitsname? = 10literal value or data structure?; // constant1 +final Int turquoisename? = 0x00ced1literal value or data structure?; // constant2 +final Int liveCellname? = blackliteral value or data structure?; // constant3 +final Float speedOfLightname? = 299792.458literal value or data structure?; // constant4 +final Boolean gameOvername? = trueliteral value or data structure?; // constant5 +final Int euroname? = 0x20acliteral value or data structure?; // constant6 +final String warningMsgname? = "Limit reached"literal value or data structure?; // constant7
}

What you need to know about constants

  • A constant is a named value that never changes while a program is running.
  • The Elan library defines a few constants including pipipipipi and a handful of simple colours e.g. redredredredred.
  • The constant instruction allows you to define your own constant(s).
  • Even though Python does not natively support the idea of a constant, when writing Python with Elan, having created a constant with constant instruction you will find that you cannot change it with a reassign instruction.
  • When coding with Elan, a constant:
    • may be defined only at global (file) level.
    • must be given a unique name, starting lower-case (sidesidesidesideside above).
    • must be defined by a liter value, which must be a number, string, or Boolean (true/false).
  • If you define your own constant with the same name as a library constant, you will not be able to access the library one within your program. If you need to do this: rename your own version.

More advanced techniques

Constant data structures

To use a data structure that is to be a constant in your program, you define it as the value returned function that defines no parameters. This function can then be referenced from anywhere in the program, often from within an expression but you need to remember to and empty brackets after the name, when using it.

The following examples define a constant list of colours (using numerical hex values), and a constant dictionary that specifies the colour for each suit in a deck of cards:

+main 1 call printprocedureName?($"{rainbow()[0]} is violet"arguments?)2 call printprocedureName?($"{suitColours()["hearts"]} is red"arguments?)3 end main +function rainbowname?(parameter definitionsparameter definitions?) returns List<of Int>Type? 4 return [0x9400D3, 0x4B0082, 0x0000CD, 0x008000, 0xFFFF00, 0xFFA500, 0xFF0000]value or expression?5 end function +function suitColoursname?(parameter definitionsparameter definitions?) returns Dictionary<of String, Int>Type? 6 return ["spades":black, "hearts":red, "diamonds":red, "clubs":black]value or expression?7 end function
+def main() -> None: 1 printprocedureName?(f"{rainbow()[0]} is violet"arguments?)2 printprocedureName?(f"{suitColours()["hearts"]} is red"arguments?)3 +def rainbowname?(parameter definitionsparameter definitions?) -> list[int]Type?: # function4 return [0x9400D3, 0x4B0082, 0x0000CD, 0x008000, 0xFFFF00, 0xFFA500, 0xFF0000]value or expression?5 +def suitColoursname?(parameter definitionsparameter definitions?) -> Dictionary[str, int]Type?: # function6 return ["spades":black, "hearts":red, "diamonds":red, "clubs":black]value or expression?7 main()
+static void main() { 1 printprocedureName?($"{rainbow()[0]} is violet"arguments?);2 printprocedureName?($"{suitColours()["hearts"]} is red"arguments?);3 } // main +static List<int>Type? rainbowname?(parameter definitionsparameter definitions?) { // function4 return [0x9400D3, 0x4B0082, 0x0000CD, 0x008000, 0xFFFF00, 0xFFA500, 0xFF0000]value or expression?;5 } // function +static Dictionary<string, int>Type? suitColoursname?(parameter definitionsparameter definitions?) { // function6 return ["spades":black, "hearts":red, "diamonds":red, "clubs":black]value or expression?;7 } // function
+Sub main() 1 printprocedureName?($"{rainbow()[0]} is violet"arguments?)2 printprocedureName?($"{suitColours()["hearts"]} is red"arguments?)3 End Sub +Function rainbowname?(parameter definitionsparameter definitions?) As List(Of Integer)Type? 4 Return {&H9400D3, &H4B0082, &H0000CD, &H008000, &HFFFF00, &HFFA500, &HFF0000}value or expression?5 End Function +Function suitColoursname?(parameter definitionsparameter definitions?) As Dictionary(Of String, Integer)Type? 6 Return ["spades":black, "hearts":red, "diamonds":red, "clubs":black]value or expression?7 End Function
public class Global {

+static void main() { 1 printprocedureName?(String.format("% is violet", rainbow()[0])arguments?);2 printprocedureName?(String.format("% is red", suitColours()["hearts"])arguments?);3 } // main +static List<int>Type? rainbowname?(parameter definitionsparameter definitions?) { // function4 return [0x9400D3, 0x4B0082, 0x0000CD, 0x008000, 0xFFFF00, 0xFFA500, 0xFF0000]value or expression?;5 } // function +static Dictionary<String, int>Type? suitColoursname?(parameter definitionsparameter definitions?) { // function6 return ["spades":black, "hearts":red, "diamonds":red, "clubs":black]value or expression?;7 } // function
}

Enum

Examples of enums

From the Snake - procedural demo, the enum is defined here:

enum DirectionName? up, down, left, rightvalues?values?1
class DirectionName?(Enum):
up = 1
down = 2
left = 3
right = 4
values?
1
enum DirectionName? {up, down, left, rightvalues?values?}1
Enum DirectionName?
up = 0
down = 1
left = 2
right = 3
End Enum
values?
1
public class Global {

enum DirectionName? {up, down, left, rightvalues?values?}1
}

and is used in several places in the code, for example:

+function getAdjacentSquarename?(sq as List<of Int>, dir as Directionparameter definitions?) returns List<of Int>Type? 1 variable newXname? set to sq[0]value or expression?2 variable newYname? set to sq[1]value or expression?3 +if dir is Direction.leftcondition? then 4 reassign newXvariableName? to newX - 1value or expression?5 elif dir is Direction.rightcondition? then6 reassign newXvariableName? to newX + 1value or expression?7 elif dir is Direction.upcondition? then8 reassign newYvariableName? to newY - 1value or expression?9 elif dir is Direction.downcondition? then10 reassign newYvariableName? to newY + 1value or expression?11 end if return [newX, newY]value or expression?12 end function
+def getAdjacentSquarename?(sq: list[int], dir: Directionparameter definitions?) -> list[int]Type?: # function1 newXname? = sq[0]value or expression? # variable definition2 newYname? = sq[1]value or expression? # variable definition3 +if dir == Direction.leftcondition?: 4 newXvariableName? = newX - 1value or expression? # reassign variable5 elif dir == Direction.rightcondition?: # else if6 newXvariableName? = newX + 1value or expression? # reassign variable7 elif dir == Direction.upcondition?: # else if8 newYvariableName? = newY - 1value or expression? # reassign variable9 elif dir == Direction.downcondition?: # else if10 newYvariableName? = newY + 1value or expression? # reassign variable11 return [newX, newY]value or expression?12
+static List<int>Type? getAdjacentSquarename?(List<int> sq, Direction dirparameter definitions?) { // function1 var newXname? = sq[0]value or expression?;2 var newYname? = sq[1]value or expression?;3 +if (dir == Direction.leftcondition?) { 4 newXvariableName? = newX - 1value or expression?; // reassign variable5 } else if (dir == Direction.rightcondition?) {6 newXvariableName? = newX + 1value or expression?; // reassign variable7 } else if (dir == Direction.upcondition?) {8 newYvariableName? = newY - 1value or expression?; // reassign variable9 } else if (dir == Direction.downcondition?) {10 newYvariableName? = newY + 1value or expression?; // reassign variable11 } // if return [newX, newY]value or expression?;12 } // function
+Function getAdjacentSquarename?(sq As List(Of Integer), dir As Directionparameter definitions?) As List(Of Integer)Type? 1 Dim newXname? = sq[0]value or expression? ' variable definition2 Dim newYname? = sq[1]value or expression? ' variable definition3 +If dir = Direction.leftcondition? Then 4 newXvariableName? = newX - 1value or expression? ' reassign variable5 ElseIf dir = Direction.rightcondition? Then6 newXvariableName? = newX + 1value or expression? ' reassign variable7 ElseIf dir = Direction.upcondition? Then8 newYvariableName? = newY - 1value or expression? ' reassign variable9 ElseIf dir = Direction.downcondition? Then10 newYvariableName? = newY + 1value or expression? ' reassign variable11 End If Return {newX, newY}value or expression?12 End Function
public class Global {

+static List<int>Type? getAdjacentSquarename?(List<int> sq, Direction dirparameter definitions?) { // function1 var newXname? = sq[0]value or expression?;2 var newYname? = sq[1]value or expression?;3 +if (dir == Direction.leftcondition?) { 4 newXvariableName? = newX - 1value or expression?; // reassign variable5 } else if (dir == Direction.rightcondition?) {6 newXvariableName? = newX + 1value or expression?; // reassign variable7 } else if (dir == Direction.upcondition?) {8 newYvariableName? = newY - 1value or expression?; // reassign variable9 } else if (dir == Direction.downcondition?) {10 newYvariableName? = newY + 1value or expression?; // reassign variable11 } // if return [newX, newY]value or expression?;12 } // function
}
From the Blackjack demo:
enum SuitName? spades, hearts, diamonds, clubsvalues?values?1 enum OutcomeName? undecided, win, lose, draw, winDoublevalues?values?2
class SuitName?(Enum):
spades = 1
hearts = 2
diamonds = 3
clubs = 4
values?
1
class OutcomeName?(Enum):
undecided = 1
win = 2
lose = 3
draw = 4
winDouble = 5
values?
2
enum SuitName? {spades, hearts, diamonds, clubsvalues?values?}1 enum OutcomeName? {undecided, win, lose, draw, winDoublevalues?values?}2
Enum SuitName?
spades = 0
hearts = 1
diamonds = 2
clubs = 3
End Enum
values?
1
Enum OutcomeName?
undecided = 0
win = 1
lose = 2
draw = 3
winDouble = 4
End Enum
values?
2
public class Global {

enum SuitName? {spades, hearts, diamonds, clubsvalues?values?}1 enum OutcomeName? {undecided, win, lose, draw, winDoublevalues?values?}2
}

What you need to know about enums

  • An enum – short for 'enumeration' – is the simplest form of user-defined type.

  • Because it is a type, the name given to the enum (DirectionDirectionDirectionDirectionDirection above must start with an upper-case letter.
  • An enum forces any named value of that type to be one of a fixed number of named values, listed in the enum definition.
  • The instruction variable currentDirname? set to Direction.rightvalue or expression?0currentDirname? = Direction.rightvalue or expression? # variable definition0var currentDirname? = Direction.rightvalue or expression?;0Dim currentDirname? = Direction.rightvalue or expression? ' variable definition0var currentDirname? = Direction.rightvalue or expression?;0 initialises currentDircurrentDircurrentDircurrentDircurrentDir with the value rightrightrightrightright defined on the enum named DirectionDirectionDirectionDirectionDirection.
  • Because the variable currentDirectcurrentDirectcurrentDirectcurrentDirectcurrentDirect has the type
  • Direction, it may only be reassigned to another of the named values defined on new codenew codenew codenew code
    public class Global {

    new code
    }
    , it may only be reassigned to another of the named values defined on currentDircurrentDircurrentDircurrentDircurrentDir
  • An enum value cannot be converted to anything else.
  • The advantages of using enums are:
    • Readability
    • Safety. If the four directions were defined as strings, e.g. "right""right""right""right""right" a mis-spelling might not picked up until runtime; however, writing e.g. Direction.rifhtDirection.rifhtDirection.rifhtDirection.rifhtDirection.rifht would be a compile error.

Comment

Examples of comments

From the Snake - procedural demo, where a comment at global level describes how to use the program:

# Use the w,a,s,d keys to change snake's directioncomment?
# Use the w,a,s,d keys to change snake's directioncomment?
// Use the w,a,s,d keys to change snake's directioncomment?
' Use the w,a,s,d keys to change snake's directioncomment?
public class Global {

// Use the w,a,s,d keys to change snake's directioncomment?
}

From the Bubbles demo, where comments explain specific blocks of instructions

+procedure moveGrowBurstname?(bubbles as List<of CircleVG>parameter definitions?) 1 +for bitem? in bubblessource? 2 +if random() < 0.05condition? then 3 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? call b.setRadiusprocedureName?(0arguments?)4 call b.setCentreYprocedureName?(75arguments?)5 else6 # bubble rises and grows slightlycomment? call b.setCentreYprocedureName?(b.centreY - 1arguments?)7 call b.setRadiusprocedureName?(b.radius + 0.2arguments?)8 end if end for call displayVectorGraphicsprocedureName?(bubblesarguments?)9 call sleep_msprocedureName?(5arguments?)10 end procedure
+def moveGrowBurstname?(bubbles: list[CircleVG]parameter definitions?) -> None: # procedure1 +for bitem? in bubblessource?: 2 +if random() < 0.05condition?: 3 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) # call procedure4 b.setCentreYprocedureName?(75arguments?) # call procedure5 else:6 # bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) # call procedure7 b.setRadiusprocedureName?(b.radius + 0.2arguments?) # call procedure8 displayVectorGraphicsprocedureName?(bubblesarguments?) # call procedure9 sleep_msprocedureName?(5arguments?) # call procedure10
+static void moveGrowBurstname?(List<CircleVG> bubblesparameter definitions?) { // procedure1 +foreach (bitem? in bubblessource?) { 2 +if (random() < 0.05condition?) { 3 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // call procedure4 b.setCentreYprocedureName?(75arguments?); // call procedure5 } else {6 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // call procedure7 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // call procedure8 } // if } // foreach displayVectorGraphicsprocedureName?(bubblesarguments?); // call procedure9 sleep_msprocedureName?(5arguments?); // call procedure10 } // procedure
+Sub moveGrowBurstname?(bubbles As List(Of CircleVG)parameter definitions?) ' procedure1 +For Each bitem? In bubblessource? 2 +If random() < 0.05condition? Then 3 ' 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) ' call procedure4 b.setCentreYprocedureName?(75arguments?) ' call procedure5 Else6 ' bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) ' call procedure7 b.setRadiusprocedureName?(b.radius + 0.2arguments?) ' call procedure8 End If Next b displayVectorGraphicsprocedureName?(bubblesarguments?) ' call procedure9 sleep_msprocedureName?(5arguments?) ' call procedure10 End Sub
public class Global {

+static void moveGrowBurstname?(List<CircleVG> bubblesparameter definitions?) { // procedure1 +foreach (bitem? in bubblessource?) { 2 +if (random() < 0.05condition?) { 3 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // call procedure4 b.setCentreYprocedureName?(75arguments?); // call procedure5 } else {6 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // call procedure7 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // call procedure8 } // if } // foreach displayVectorGraphicsprocedureName?(bubblesarguments?); // call procedure9 sleep_msprocedureName?(5arguments?); // call procedure10 } // procedure
}

What you need to know about comments

  • A comment is not an instruction: it is ignored by the compiler and does not change how the program works.
  • Rather, a comment contains information about the program, intended to be read by a person seeking to understand or modify the code.
  • A comment may be entered at any new code prompt by typing the symbol # Code does not parse as Elan.# Code does not parse as Elan.# Code does not parse as Elan.# Code does not parse as Elan.# Code does not parse as Elan. which then provides a field in which you may enter text, or leave blank.
  • Your text may contain any characters, except that it must not start with an open square bracket [.
  • When coding with Elan, a comment added by the programmer always starts on a new line.
  • Comments that you see written after an instruction are generated by the system and may not be edited or deleted.
  • Every Elan program has a single comment at the top of the file, which is generated by the system and cannot be edited or deleted by the user; this comment is known as the 'file header'.

Statement instructions

Statement instructions (sometimes referred to as 'statements') are the instructions used with in the methods of a program: the main routine, functions, and procedures.

Variable definition

Examples of variable definitions

From the Burrow demo, where variables named xxxxx and yyyyy represent the current coordinates for the burrowing 'animal':

variable xname? set to 20value or expression?0
xname? = 20value or expression? # variable definition0
var xname? = 20value or expression?;0
Dim xname? = 20value or expression? ' variable definition0
var xname? = 20value or expression?;0
variable yname? set to 15value or expression?0
yname? = 15value or expression? # variable definition0
var yname? = 15value or expression?;0
Dim yname? = 15value or expression? ' variable definition0
var yname? = 15value or expression?;0

and the values of these variables are used in multiple places within the program, for example here:

reassign blocks[x][y]variableName? to redvalue or expression?0
blocks[x][y]variableName? = redvalue or expression? # reassign variable0
blocks[x][y]variableName? = redvalue or expression?; // reassign variable0
blocks[x][y]variableName? = redvalue or expression? ' reassign variable0
blocks[x][y]variableName? = redvalue or expression?; // reassign variable0

From the Life - procedural demo where a variable is initialised using an expression, in this case a function call:

variable gridname? set to createBlockGraphics(white)value or expression?0
gridname? = createBlockGraphics(white)value or expression? # variable definition0
var gridname? = createBlockGraphics(white)value or expression?;0
Dim gridname? = createBlockGraphics(white)value or expression? ' variable definition0
var gridname? = createBlockGraphics(white)value or expression?;0

Example from the Ripple Sort demo where a variable being initialised using a literal llist of integers:

variable liname? set to [7, 1, 0, 4, 8, 3, 6]value or expression?0
liname? = [7, 1, 0, 4, 8, 3, 6]value or expression? # variable definition0
var liname? = [7, 1, 0, 4, 8, 3, 6]value or expression?;0
Dim liname? = {7, 1, 0, 4, 8, 3, 6}value or expression? ' variable definition0
var liname? = [7, 1, 0, 4, 8, 3, 6]value or expression?;0

What you need to know about variable definitions

  • A variable is a named value, were the value may be changed during the course of running the program.
  • The variable definition instruction allows you to define a new variable.
  • The instructions require a name (e.g. xxxxx) and an initial value.
  • The initial value may be specified using a literal value (such as 2020202020, or "apple""apple""apple""apple""apple") or by an expression.
  • The type of the initial value determines the type of the variable, so if/when the variable is later changed to a new value, that new value must be of that same type./li>
  • Variables may only be defined within a method (the main routine, a procedure, or function).
  • Elan does not permit 'global variables' to be defined - even though, outside Elan, that would be allowed by all the languages - because it is considered a bad practice.

Reassign variable

Examples of variable reassignment

From the Ripple sort demo, where the hasChangedhasChangedhasChangedhasChangedhasChanged variable, having been defined higher up, is reassigned to falseFalsefalseFalsefalse at the start of the while loop, and then to trueTruetrueTruetrue if two values are swapped:

+procedure inPlaceRippleSortname?(li as List<of Int>parameter definitions?) 1 variable hasChangedname? set to truevalue or expression?2 variable lastCompname? set to li.length() - 2value or expression?3 +while hasChanged is truecondition? 4 reassign hasChangedvariableName? to falsevalue or expression?5 +for iitem? in range(0, lastComp + 1)source? 6 +if li[i] > li[i + 1]condition? then 7 variable tempname? set to li[i]value or expression?8 reassign li[i]variableName? to li[i + 1]value or expression?9 reassign li[i + 1]variableName? to tempvalue or expression?10 reassign hasChangedvariableName? to truevalue or expression?11 end if end for reassign lastCompvariableName? to lastComp - 1value or expression?12 end while end procedure
+def inPlaceRippleSortname?(li: list[int]parameter definitions?) -> None: # procedure1 hasChangedname? = Truevalue or expression? # variable definition2 lastCompname? = li.length() - 2value or expression? # variable definition3 +while hasChanged == Truecondition?: 4 hasChangedvariableName? = Falsevalue or expression? # reassign variable5 +for iitem? in range(0, lastComp + 1)source?: 6 +if li[i] > li[i + 1]condition?: 7 tempname? = li[i]value or expression? # variable definition8 li[i]variableName? = li[i + 1]value or expression? # reassign variable9 li[i + 1]variableName? = tempvalue or expression? # reassign variable10 hasChangedvariableName? = Truevalue or expression? # reassign variable11 lastCompvariableName? = lastComp - 1value or expression? # reassign variable12
+static void inPlaceRippleSortname?(List<int> liparameter definitions?) { // procedure1 var hasChangedname? = truevalue or expression?;2 var lastCompname? = li.length() - 2value or expression?;3 +while (hasChanged == truecondition?) { 4 hasChangedvariableName? = falsevalue or expression?; // reassign variable5 +foreach (iitem? in range(0, lastComp + 1)source?) { 6 +if (li[i] > li[i + 1]condition?) { 7 var tempname? = li[i]value or expression?;8 li[i]variableName? = li[i + 1]value or expression?; // reassign variable9 li[i + 1]variableName? = tempvalue or expression?; // reassign variable10 hasChangedvariableName? = truevalue or expression?; // reassign variable11 } // if } // foreach lastCompvariableName? = lastComp - 1value or expression?; // reassign variable12 } // while } // procedure
+Sub inPlaceRippleSortname?(li As List(Of Integer)parameter definitions?) ' procedure1 Dim hasChangedname? = Truevalue or expression? ' variable definition2 Dim lastCompname? = li.length() - 2value or expression? ' variable definition3 +While hasChanged = Truecondition? 4 hasChangedvariableName? = Falsevalue or expression? ' reassign variable5 +For Each iitem? In range(0, lastComp + 1)source? 6 +If li[i] > li[i + 1]condition? Then 7 Dim tempname? = li[i]value or expression? ' variable definition8 li[i]variableName? = li[i + 1]value or expression? ' reassign variable9 li[i + 1]variableName? = tempvalue or expression? ' reassign variable10 hasChangedvariableName? = Truevalue or expression? ' reassign variable11 End If Next i lastCompvariableName? = lastComp - 1value or expression? ' reassign variable12 End While End Sub
public class Global {

+static void inPlaceRippleSortname?(List<int> liparameter definitions?) { // procedure1 var hasChangedname? = truevalue or expression?;2 var lastCompname? = li.length() - 2value or expression?;3 +while (hasChanged == truecondition?) { 4 hasChangedvariableName? = falsevalue or expression?; // reassign variable5 +foreach (iitem? in range(0, lastComp + 1)source?) { 6 +if (li[i] > li[i + 1]condition?) { 7 var tempname? = li[i]value or expression?;8 li[i]variableName? = li[i + 1]value or expression?; // reassign variable9 li[i + 1]variableName? = tempvalue or expression?; // reassign variable10 hasChangedvariableName? = truevalue or expression?; // reassign variable11 } // if } // foreach lastCompvariableName? = lastComp - 1value or expression?; // reassign variable12 } // while } // procedure
}

From the Snake -procedural demo, where the variable currentDefcurrentDefcurrentDefcurrentDefcurrentDef is reassigned to a new value by calling the function directionByKey, which is passed a keypress that has been read from the keyboard using the getKey system method:

+procedure updateSnakename?(currentDirRef as AsRef<of Direction>, tailRef as AsRef<of List<of Int>>, headRef as AsRef<of List<of Int>>, body as List<of List<of Int>>parameter definitions?) 1 variable headname? set to headRef.value()value or expression?2 variable tailname? set to tailRef.value()value or expression?3 variable currentDirname? set to currentDirRef.value()value or expression?4 reassign currentDirvariableName? to directionByKey(currentDir, getKey())value or expression?5 call tailRef.setprocedureName?(body[0]arguments?)6 call body.appendprocedureName?(headarguments?)7 call headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?)8 call currentDirRef.setprocedureName?(currentDirarguments?)9 end procedure
+def updateSnakename?(currentDirRef: AsRef[Direction], tailRef: AsRef[list[int]], headRef: AsRef[list[int]], body: list[list[int]]parameter definitions?) -> None: # procedure1 headname? = headRef.value()value or expression? # variable definition2 tailname? = tailRef.value()value or expression? # variable definition3 currentDirname? = currentDirRef.value()value or expression? # variable definition4 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression? # reassign variable5 tailRef.setprocedureName?(body[0]arguments?) # call procedure6 body.appendprocedureName?(headarguments?) # call procedure7 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?) # call procedure8 currentDirRef.setprocedureName?(currentDirarguments?) # call procedure9
+static void updateSnakename?(AsRef<Direction> currentDirRef, AsRef<List<int>> tailRef, AsRef<List<int>> headRef, List<List<int>> bodyparameter definitions?) { // procedure1 var headname? = headRef.value()value or expression?;2 var tailname? = tailRef.value()value or expression?;3 var currentDirname? = currentDirRef.value()value or expression?;4 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression?; // reassign variable5 tailRef.setprocedureName?(body[0]arguments?); // call procedure6 body.appendprocedureName?(headarguments?); // call procedure7 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?); // call procedure8 currentDirRef.setprocedureName?(currentDirarguments?); // call procedure9 } // procedure
+Sub updateSnakename?(currentDirRef As AsRef(Of Direction), tailRef As AsRef(Of List(Of Integer)), headRef As AsRef(Of List(Of Integer)), body As List(Of List(Of Integer))parameter definitions?) ' procedure1 Dim headname? = headRef.value()value or expression? ' variable definition2 Dim tailname? = tailRef.value()value or expression? ' variable definition3 Dim currentDirname? = currentDirRef.value()value or expression? ' variable definition4 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression? ' reassign variable5 tailRef.setprocedureName?(body[0]arguments?) ' call procedure6 body.appendprocedureName?(headarguments?) ' call procedure7 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?) ' call procedure8 currentDirRef.setprocedureName?(currentDirarguments?) ' call procedure9 End Sub
public class Global {

+static void updateSnakename?(AsRef<Direction> currentDirRef, AsRef<List<int>> tailRef, AsRef<List<int>> headRef, List<List<int>> bodyparameter definitions?) { // procedure1 var headname? = headRef.value()value or expression?;2 var tailname? = tailRef.value()value or expression?;3 var currentDirname? = currentDirRef.value()value or expression?;4 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression?; // reassign variable5 tailRef.setprocedureName?(body[0]arguments?); // call procedure6 body.appendprocedureName?(headarguments?); // call procedure7 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?); // call procedure8 currentDirRef.setprocedureName?(currentDirarguments?); // call procedure9 } // procedure
}

What you need to know about reassigning a variable

  • The reassign variable instruction assigns a new value to a variable that has been defined earlier within the method.
  • The new value to be assigned may be defined as a literal value, or as the result of evaluating an expression.
  • The new value must be of the same type as the one that the variable was defined with.

Input

Example of input instructions

From the Binary Search demo, showing an input instruction with a clear user-prompt:

input wantedname? set to inputString("What type of fruit do you want ('x' to exit)? "prompt message?)0
wantedname? = input("What type of fruit do you want ('x' to exit)? "prompt message?) # input0
Console.WriteLine("What type of fruit do you want ('x' to exit)? "prompt message?);
var wantedname? = Console.ReadLine();
// input0
Console.WriteLine("What type of fruit do you want ('x' to exit)? "prompt message?)
Dim wantedname? = Console.ReadLine()
' input0
var wantedname? = Console.ReadLine("What type of fruit do you want ('x' to exit)? "prompt message?); // input0

What you need to know about input instructions

  • The input instruction is the simplest way to read a string value typed in by the user (followed by Enter.
  • You must specify a user-prompt string, though this may be an empty string - """""""""".
  • When working in Elan, the user entry will always be on the line below the displayed prompt.
  • The entered value is put into a new variable - equivalent to if it was a variable definition.
  • If you want to assign the input to an existing variable you can do this by adding a reassign variable instruction on the next line.
  • Also in a separate instruction, you can parse the string into a numeric type by applying the int()int()int()int()int() or float()float()float()float()float() function to the input variable.
  • See below for more advanced options for handling specific forms of user input.

More advanced techniques

System methods for keyboard input

As an alternative to using the input instruction you may also use a range of system methods from the standard Elan library, which offer several advantages, including:

  • Enforcing that a user input matches the requirement for an integer, or floating-point number, and then doing the conversion invisibly:
  • Enforcing that either form of numeric input is within specified numeric bounds.
  • Enforcing that a string input is within specified bounds for the number of characters.
  • Enforcing that a string input matches one of a list of valid string responses.

In all these cases, if the user input does not match the specified rules, the user will be advised, and asked to input a value response.

See

Keyboard input for the full list of relevant system method.

If statement

Examples of if statements

From the Life - procedural demo, where the resultresultresultresultresult is changed from black to white if a given random value (expected to be in the range 0 to 1) is greater than 0.5:

+function blackOrWhitename?(random as Floatparameter definitions?) returns IntType? 1 variable resultname? set to blackvalue or expression?2 +if random > 0.5condition? then 3 reassign resultvariableName? to whitevalue or expression?4 end if return resultvalue or expression?5 end function
+def blackOrWhitename?(random: floatparameter definitions?) -> intType?: # function1 resultname? = blackvalue or expression? # variable definition2 +if random > 0.5condition?: 3 resultvariableName? = whitevalue or expression? # reassign variable4 return resultvalue or expression?5
+static intType? blackOrWhitename?(double randomparameter definitions?) { // function1 var resultname? = blackvalue or expression?;2 +if (random > 0.5condition?) { 3 resultvariableName? = whitevalue or expression?; // reassign variable4 } // if return resultvalue or expression?;5 } // function
+Function blackOrWhitename?(random As Doubleparameter definitions?) As IntegerType? 1 Dim resultname? = blackvalue or expression? ' variable definition2 +If random > 0.5condition? Then 3 resultvariableName? = whitevalue or expression? ' reassign variable4 End If Return resultvalue or expression?5 End Function
public class Global {

+static intType? blackOrWhitename?(double randomparameter definitions?) { // function1 var resultname? = blackvalue or expression?;2 +if (random > 0.5condition?) { 3 resultvariableName? = whitevalue or expression?; // reassign variable4 } // if return resultvalue or expression?;5 } // function
}

From the Bubbles demo where the if statement includes an else clause, so that one of two separate blocks of statements is always executed depending on the value of a randomly-generated number.

+if random() < 0.05condition? then 0 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? call b.setRadiusprocedureName?(0arguments?)1 call b.setCentreYprocedureName?(75arguments?)2 else3 # bubble rises and grows slightlycomment? call b.setCentreYprocedureName?(b.centreY - 1arguments?)4 call b.setRadiusprocedureName?(b.radius + 0.2arguments?)5 end if
+if random() < 0.05condition?: 0 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) # call procedure1 b.setCentreYprocedureName?(75arguments?) # call procedure2 else:3 # bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) # call procedure4 b.setRadiusprocedureName?(b.radius + 0.2arguments?) # call procedure5
+if (random() < 0.05condition?) { 0 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // call procedure1 b.setCentreYprocedureName?(75arguments?); // call procedure2 } else {3 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // call procedure4 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // call procedure5 } // if
+If random() < 0.05condition? Then 0 ' 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) ' call procedure1 b.setCentreYprocedureName?(75arguments?) ' call procedure2 Else3 ' bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) ' call procedure4 b.setRadiusprocedureName?(b.radius + 0.2arguments?) ' call procedure5 End If
+if (random() < 0.05condition?) { 0 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // call procedure1 b.setCentreYprocedureName?(75arguments?); // call procedure2 } else {3 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // call procedure4 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // call procedure5 } // if

From the Burrow demo, where the if includes multiple else if (shortened to 'elif' in some languages) clauses, such that depending on the value of the directiondirectiondirectiondirectiondirection variable, one of four different statements is executed:

+if direction is 0condition? then 0 reassign xvariableName? to min([x + 1, 39])value or expression?1 elif direction is 1condition? then2 reassign xvariableName? to max([x - 1, 0])value or expression?3 elif direction is 2condition? then4 reassign yvariableName? to min([y + 1, 29])value or expression?5 elif direction is 3condition? then6 reassign yvariableName? to max([y - 1, 0])value or expression?7 end if
+if direction == 0condition?: 0 xvariableName? = min([x + 1, 39])value or expression? # reassign variable1 elif direction == 1condition?: # else if2 xvariableName? = max([x - 1, 0])value or expression? # reassign variable3 elif direction == 2condition?: # else if4 yvariableName? = min([y + 1, 29])value or expression? # reassign variable5 elif direction == 3condition?: # else if6 yvariableName? = max([y - 1, 0])value or expression? # reassign variable7
+if (direction == 0condition?) { 0 xvariableName? = min([x + 1, 39])value or expression?; // reassign variable1 } else if (direction == 1condition?) {2 xvariableName? = max([x - 1, 0])value or expression?; // reassign variable3 } else if (direction == 2condition?) {4 yvariableName? = min([y + 1, 29])value or expression?; // reassign variable5 } else if (direction == 3condition?) {6 yvariableName? = max([y - 1, 0])value or expression?; // reassign variable7 } // if
+If direction = 0condition? Then 0 xvariableName? = min({x + 1, 39})value or expression? ' reassign variable1 ElseIf direction = 1condition? Then2 xvariableName? = max({x - 1, 0})value or expression? ' reassign variable3 ElseIf direction = 2condition? Then4 yvariableName? = min({y + 1, 29})value or expression? ' reassign variable5 ElseIf direction = 3condition? Then6 yvariableName? = max({y - 1, 0})value or expression? ' reassign variable7 End If
+if (direction == 0condition?) { 0 xvariableName? = min([x + 1, 39])value or expression?; // reassign variable1 } else if (direction == 1condition?) {2 xvariableName? = max([x - 1, 0])value or expression?; // reassign variable3 } else if (direction == 2condition?) {4 yvariableName? = min([y + 1, 29])value or expression?; // reassign variable5 } else if (direction == 3condition?) {6 yvariableName? = max([y - 1, 0])value or expression?; // reassign variable7 } // if

(Note: the last else if clause could have been written as an else clause, but writing it as an else if makes the intention clearer to a human reader).

What you need to know about an if statement

  • The if instruction specifies which of several blocks of instructions is to be executed next.
  • It requires a condition to be specified, which must evaluate to a Boolean value - trueTruetrueTruetrue or falseFalsefalseFalsefalse.
  • That expression might take any of the following forms:
    • comparing two values using one of the Boolean operators e.g. random() < 0.05random() < 0.05random() < 0.05random() < 0.05random() < 0.05 or direction is 0direction == 0direction == 0direction = 0direction == 0
    • a single existing variable that is of Boolean type
    • a function call, or any more complex expression, that evaluates to a Boolean value
  • Within the body of an if expression you may add one or more else if clauses and/or a single else clause
  • If you specify an else clause it must be the final clause within the if statement body

While loop

Examples of while loops

From the Ripple Sort demo, where the loop is executed until a pass right through the loop has left the hasChangedhasChangedhasChangedhasChangedhasChanged variable as false (meaning that no more swaps have occurred): Code does not parse as Elan.false (meaning that no more swaps have occurred): Code does not parse as Elan.false (meaning that no more swaps have occurred): Code does not parse as Elan.false (meaning that no more swaps have occurred): Code does not parse as Elan.false (meaning that no more swaps have occurred): Code does not parse as Elan.

+while hasChanged is truecondition? 0 reassign hasChangedvariableName? to falsevalue or expression?1 +for iitem? in range(0, lastComp + 1)source? 2 +if li[i] > li[i + 1]condition? then 3 variable tempname? set to li[i]value or expression?4 reassign li[i]variableName? to li[i + 1]value or expression?5 reassign li[i + 1]variableName? to tempvalue or expression?6 reassign hasChangedvariableName? to truevalue or expression?7 end if end for reassign lastCompvariableName? to lastComp - 1value or expression?8 end while
+while hasChanged == Truecondition?: 0 hasChangedvariableName? = Falsevalue or expression? # reassign variable1 +for iitem? in range(0, lastComp + 1)source?: 2 +if li[i] > li[i + 1]condition?: 3 tempname? = li[i]value or expression? # variable definition4 li[i]variableName? = li[i + 1]value or expression? # reassign variable5 li[i + 1]variableName? = tempvalue or expression? # reassign variable6 hasChangedvariableName? = Truevalue or expression? # reassign variable7 lastCompvariableName? = lastComp - 1value or expression? # reassign variable8
+while (hasChanged == truecondition?) { 0 hasChangedvariableName? = falsevalue or expression?; // reassign variable1 +foreach (iitem? in range(0, lastComp + 1)source?) { 2 +if (li[i] > li[i + 1]condition?) { 3 var tempname? = li[i]value or expression?;4 li[i]variableName? = li[i + 1]value or expression?; // reassign variable5 li[i + 1]variableName? = tempvalue or expression?; // reassign variable6 hasChangedvariableName? = truevalue or expression?; // reassign variable7 } // if } // foreach lastCompvariableName? = lastComp - 1value or expression?; // reassign variable8 } // while
+While hasChanged = Truecondition? 0 hasChangedvariableName? = Falsevalue or expression? ' reassign variable1 +For Each iitem? In range(0, lastComp + 1)source? 2 +If li[i] > li[i + 1]condition? Then 3 Dim tempname? = li[i]value or expression? ' variable definition4 li[i]variableName? = li[i + 1]value or expression? ' reassign variable5 li[i + 1]variableName? = tempvalue or expression? ' reassign variable6 hasChangedvariableName? = Truevalue or expression? ' reassign variable7 End If Next i lastCompvariableName? = lastComp - 1value or expression? ' reassign variable8 End While
+while (hasChanged == truecondition?) { 0 hasChangedvariableName? = falsevalue or expression?; // reassign variable1 +foreach (iitem? in range(0, lastComp + 1)source?) { 2 +if (li[i] > li[i + 1]condition?) { 3 var tempname? = li[i]value or expression?;4 li[i]variableName? = li[i + 1]value or expression?; // reassign variable5 li[i + 1]variableName? = tempvalue or expression?; // reassign variable6 hasChangedvariableName? = truevalue or expression?; // reassign variable7 } // if } // foreach lastCompvariableName? = lastComp - 1value or expression?; // reassign variable8 } // while

From the Bubbles demo, where the condition is 'hard-wired' to trueTruetrueTruetrue, which means that the loop will executive indefinitely (until you stop the program manually with the stop button):

+while truecondition? 0 call moveGrowBurstprocedureName?(bubblesarguments?)1 end while
+while Truecondition?: 0 moveGrowBurstprocedureName?(bubblesarguments?) # call procedure1
+while (truecondition?) { 0 moveGrowBurstprocedureName?(bubblesarguments?); // call procedure1 } // while
+While Truecondition? 0 moveGrowBurstprocedureName?(bubblesarguments?) ' call procedure1 End While
+while (truecondition?) { 0 moveGrowBurstprocedureName?(bubblesarguments?); // call procedure1 } // while

From the Tower of Hanoi demo, which uses a more complicated expression - to exit the loop only when the third stack has all the discs on it:

+while stacks[2].length() isnt nDiscscondition? 0 +if (stacks[0].length() mod 2) is 0condition? then 1 call moveBetweenprocedureName?(stacks, 0, 1arguments?)2 call moveBetweenprocedureName?(stacks, 0, 2arguments?)3 call moveBetweenprocedureName?(stacks, 1, 2arguments?)4 else5 call moveBetweenprocedureName?(stacks, 0, 2arguments?)6 call moveBetweenprocedureName?(stacks, 0, 1arguments?)7 call moveBetweenprocedureName?(stacks, 1, 2arguments?)8 end if end while
+while stacks[2].length() != nDiscscondition?: 0 +if (stacks[0].length() % 2) == 0condition?: 1 moveBetweenprocedureName?(stacks, 0, 1arguments?) # call procedure2 moveBetweenprocedureName?(stacks, 0, 2arguments?) # call procedure3 moveBetweenprocedureName?(stacks, 1, 2arguments?) # call procedure4 else:5 moveBetweenprocedureName?(stacks, 0, 2arguments?) # call procedure6 moveBetweenprocedureName?(stacks, 0, 1arguments?) # call procedure7 moveBetweenprocedureName?(stacks, 1, 2arguments?) # call procedure8
+while (stacks[2].length() != nDiscscondition?) { 0 +if ((stacks[0].length() % 2) == 0condition?) { 1 moveBetweenprocedureName?(stacks, 0, 1arguments?); // call procedure2 moveBetweenprocedureName?(stacks, 0, 2arguments?); // call procedure3 moveBetweenprocedureName?(stacks, 1, 2arguments?); // call procedure4 } else {5 moveBetweenprocedureName?(stacks, 0, 2arguments?); // call procedure6 moveBetweenprocedureName?(stacks, 0, 1arguments?); // call procedure7 moveBetweenprocedureName?(stacks, 1, 2arguments?); // call procedure8 } // if } // while
+While stacks[2].length() <> nDiscscondition? 0 +If (stacks[0].length() Mod 2) = 0condition? Then 1 moveBetweenprocedureName?(stacks, 0, 1arguments?) ' call procedure2 moveBetweenprocedureName?(stacks, 0, 2arguments?) ' call procedure3 moveBetweenprocedureName?(stacks, 1, 2arguments?) ' call procedure4 Else5 moveBetweenprocedureName?(stacks, 0, 2arguments?) ' call procedure6 moveBetweenprocedureName?(stacks, 0, 1arguments?) ' call procedure7 moveBetweenprocedureName?(stacks, 1, 2arguments?) ' call procedure8 End If End While
+while (stacks[2].length() != nDiscscondition?) { 0 +if ((stacks[0].length() % 2) == 0condition?) { 1 moveBetweenprocedureName?(stacks, 0, 1arguments?); // call procedure2 moveBetweenprocedureName?(stacks, 0, 2arguments?); // call procedure3 moveBetweenprocedureName?(stacks, 1, 2arguments?); // call procedure4 } else {5 moveBetweenprocedureName?(stacks, 0, 2arguments?); // call procedure6 moveBetweenprocedureName?(stacks, 0, 1arguments?); // call procedure7 moveBetweenprocedureName?(stacks, 1, 2arguments?); // call procedure8 } // if } // while

What you need to know about a while loop

  • The while loop specifies that a block of statements is to be repeated (or 'iterated') for as long as a specified condition is true.
  • It should be used when you which to repeat something, but you don't know, in advance, how many times you need it to be repeated.
  • As on an if statement, the condition in a while loop may be any expression that evaluates to a Boolean value.
  • The condition is evaluated at the beginning of each pass through the loop.
  • If the condition makes use of one or more variables (most do) then the variable(s) must be defined before the start of the loop.
  • It is possible that the condition could evaluate to false even on the first pass, in which case the instructions within the loop are not executed at all.
  • Conversely, if the condition always evaluates to true, then the loop will executive indefintely; this can be deliberate, but may also be an accidental and unwanted behaviour caused by specifying the condition inaccurately.

See also

For loop

Examples of for loops

From the Bubbles demo, where the loop is designed to execute exactly 20 times (the upped bound of the range function being exclusive) to create 20 bubbles of the same size, but of different (randomised) colours, and spread evenly across the bottom of the display:

+for iitem? in range(1, 21)source? 0 variable bname? set to (new CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression?1 call bubbles.appendprocedureName?(barguments?)2 end for
+for iitem? in range(1, 21)source?: 0 bname? = (CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression? # variable definition1 bubbles.appendprocedureName?(barguments?) # call procedure2
+foreach (iitem? in range(1, 21)source?) { 0 var bname? = (new CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression?;1 bubbles.appendprocedureName?(barguments?); // call procedure2 } // foreach
+For Each iitem? In range(1, 21)source? 0 Dim bname? = (New CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression? ' variable definition1 bubbles.appendprocedureName?(barguments?) ' call procedure2 Next i
+foreach (iitem? in range(1, 21)source?) { 0 var bname? = (new CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression?;1 bubbles.appendprocedureName?(barguments?); // call procedure2 } // foreach

From the Turtle Dragon demo, where the number of times to execute the loop is calculated - based on the number of characters in the string variable named turnsturnsturnsturnsturns:

+procedure drawDragonname?(t as Turtle, order as Int, turns as String, side as Float, corner as Floatparameter definitions?) 1 variable pname? set to (200.0/order).floor()value or expression?2 variable turnIname? set to 0value or expression?3 +for turnitem? in turnssource? 4 reassign turnIvariableName? to (if(turn.equals(left), 1, -1))value or expression?5 call t.turnprocedureName?(-45*turnIarguments?)6 call t.moveprocedureName?(cornerarguments?)7 call t.turnprocedureName?(-45*turnIarguments?)8 call t.moveprocedureName?(sidearguments?)9 call sleep_msprocedureName?(parguments?)10 end for call t.penUpprocedureName?(arguments?)11 call t.hideprocedureName?(arguments?)12 end procedure
+def drawDragonname?(t: Turtle, order: int, turns: str, side: float, corner: floatparameter definitions?) -> None: # procedure1 pname? = (200.0/order).floor()value or expression? # variable definition2 turnIname? = 0value or expression? # variable definition3 +for turnitem? in turnssource?: 4 turnIvariableName? = (if(turn.equals(left), 1, -1))value or expression? # reassign variable5 t.turnprocedureName?(-45*turnIarguments?) # call procedure6 t.moveprocedureName?(cornerarguments?) # call procedure7 t.turnprocedureName?(-45*turnIarguments?) # call procedure8 t.moveprocedureName?(sidearguments?) # call procedure9 sleep_msprocedureName?(parguments?) # call procedure10 t.penUpprocedureName?(arguments?) # call procedure11 t.hideprocedureName?(arguments?) # call procedure12
+static void drawDragonname?(Turtle t, int order, string turns, double side, double cornerparameter definitions?) { // procedure1 var pname? = (200.0/order).floor()value or expression?;2 var turnIname? = 0value or expression?;3 +foreach (turnitem? in turnssource?) { 4 turnIvariableName? = (if(turn.equals(left), 1, -1))value or expression?; // reassign variable5 t.turnprocedureName?(-45*turnIarguments?); // call procedure6 t.moveprocedureName?(cornerarguments?); // call procedure7 t.turnprocedureName?(-45*turnIarguments?); // call procedure8 t.moveprocedureName?(sidearguments?); // call procedure9 sleep_msprocedureName?(parguments?); // call procedure10 } // foreach t.penUpprocedureName?(arguments?); // call procedure11 t.hideprocedureName?(arguments?); // call procedure12 } // procedure
+Sub drawDragonname?(t As Turtle, order As Integer, turns As String, side As Double, corner As Doubleparameter definitions?) ' procedure1 Dim pname? = (200.0/order).floor()value or expression? ' variable definition2 Dim turnIname? = 0value or expression? ' variable definition3 +For Each turnitem? In turnssource? 4 turnIvariableName? = (if(turn.equals(left), 1, -1))value or expression? ' reassign variable5 t.turnprocedureName?(-45*turnIarguments?) ' call procedure6 t.moveprocedureName?(cornerarguments?) ' call procedure7 t.turnprocedureName?(-45*turnIarguments?) ' call procedure8 t.moveprocedureName?(sidearguments?) ' call procedure9 sleep_msprocedureName?(parguments?) ' call procedure10 Next turn t.penUpprocedureName?(arguments?) ' call procedure11 t.hideprocedureName?(arguments?) ' call procedure12 End Sub
public class Global {

+static void drawDragonname?(Turtle t, int order, String turns, double side, double cornerparameter definitions?) { // procedure1 var pname? = (200.0/order).floor()value or expression?;2 var turnIname? = 0value or expression?;3 +foreach (turnitem? in turnssource?) { 4 turnIvariableName? = (if(turn.equals(left), 1, -1))value or expression?; // reassign variable5 t.turnprocedureName?(-45*turnIarguments?); // call procedure6 t.moveprocedureName?(cornerarguments?); // call procedure7 t.turnprocedureName?(-45*turnIarguments?); // call procedure8 t.moveprocedureName?(sidearguments?); // call procedure9 sleep_msprocedureName?(parguments?); // call procedure10 } // foreach t.penUpprocedureName?(arguments?); // call procedure11 t.hideprocedureName?(arguments?); // call procedure12 } // procedure
}

From the Life - procedural demo, a 'nested' for loop (one inside another) to apply the two inner instructions to each cell in the 40 x 30 grid:

+for xitem? in range(0, 40)source? 0 +for yitem? in range(0, 30)source? 1 variable colourname? set to nextCellValue(grid, x, y)value or expression?2 reassign nextGen[x][y]variableName? to colourvalue or expression?3 end for end for
+for xitem? in range(0, 40)source?: 0 +for yitem? in range(0, 30)source?: 1 colourname? = nextCellValue(grid, x, y)value or expression? # variable definition2 nextGen[x][y]variableName? = colourvalue or expression? # reassign variable3
+foreach (xitem? in range(0, 40)source?) { 0 +foreach (yitem? in range(0, 30)source?) { 1 var colourname? = nextCellValue(grid, x, y)value or expression?;2 nextGen[x][y]variableName? = colourvalue or expression?; // reassign variable3 } // foreach } // foreach
+For Each xitem? In range(0, 40)source? 0 +For Each yitem? In range(0, 30)source? 1 Dim colourname? = nextCellValue(grid, x, y)value or expression? ' variable definition2 nextGen[x][y]variableName? = colourvalue or expression? ' reassign variable3 Next y Next x
+foreach (xitem? in range(0, 40)source?) { 0 +foreach (yitem? in range(0, 30)source?) { 1 var colourname? = nextCellValue(grid, x, y)value or expression?;2 nextGen[x][y]variableName? = colourvalue or expression?; // reassign variable3 } // foreach } // foreach

What you need to know about for loops

  • The Elan concept of a for loop is equivalent to a 'for each' loop in several languages.
  • It should be used when you know (or can work out), in advance, how many times you need it to be repeated.
  • The for loop specifies that a block of statements that is repeated, once for each value in a sequence.
  • The value is held in a variable - which is defined in the first editable field when you create a new for instruction.
  • The second field in the instruction specifies the sequence of values that will be assigned to the variable, successively.
  • The sequence of values may be specified:
    • as a literal list (of any kind), or a variable of type list
    • as a string value, in which case each single character that makes up the string is assigned to the variable in succession
    • as any expression that generates a List
  • When a sequence of consecutive integer values is needed the most common way to do this is by calling one of these two functions
    • range requires two arguments: the lower and upper bound; the upper bound is 'exclusive' so that range(1, 11)range(1, 11)range(1, 11)range(1, 11)range(1, 11) creates the sequence 1,2,3,4,5,6,7,8,9,10
    • rangeInSteps requires three arguments: lower bound, upper bound (exclusive), and the 'step' or increment value, which can be negative provided that the upper bound is then less than the lower bound.
  • The variable is updated each pass around the loop; you should not attempt to modify the variable defined in the loop's signature from within the loop.

See also

Call procedure

Examples of procedure calls

From the Burrow demo: a call to the library procedured named displayBlocks, passing in the data structure held in variable blocks: Code does not parse as Elan.blocks: Code does not parse as Elan.blocks: Code does not parse as Elan.blocks: Code does not parse as Elan.blocks: Code does not parse as Elan.

call displayBlocksprocedureName?(blocksarguments?)0
displayBlocksprocedureName?(blocksarguments?) # call procedure0
displayBlocksprocedureName?(blocksarguments?); // call procedure0
displayBlocksprocedureName?(blocksarguments?) ' call procedure0
displayBlocksprocedureName?(blocksarguments?); // call procedure0

From the Bubbles demo, a call from within the main routine to the procedure named moveGrowBurst procedure:

call moveGrowBurstprocedureName?(bubblesarguments?)0
moveGrowBurstprocedureName?(bubblesarguments?) # call procedure0
moveGrowBurstprocedureName?(bubblesarguments?); // call procedure0
moveGrowBurstprocedureName?(bubblesarguments?) ' call procedure0
moveGrowBurstprocedureName?(bubblesarguments?); // call procedure0
which is defined immmediately below the main routine at the same (global) level.

From the Snake - procedural demo, a procedure that contains a sequence of four calls to other procedures:

+procedure updateSnakename?(currentDirRef as AsRef<of Direction>, tailRef as AsRef<of List<of Int>>, headRef as AsRef<of List<of Int>>, body as List<of List<of Int>>parameter definitions?) 1 variable headname? set to headRef.value()value or expression?2 variable tailname? set to tailRef.value()value or expression?3 variable currentDirname? set to currentDirRef.value()value or expression?4 reassign currentDirvariableName? to directionByKey(currentDir, getKey())value or expression?5 call tailRef.setprocedureName?(body[0]arguments?)6 call body.appendprocedureName?(headarguments?)7 call headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?)8 call currentDirRef.setprocedureName?(currentDirarguments?)9 end procedure
+def updateSnakename?(currentDirRef: AsRef[Direction], tailRef: AsRef[list[int]], headRef: AsRef[list[int]], body: list[list[int]]parameter definitions?) -> None: # procedure1 headname? = headRef.value()value or expression? # variable definition2 tailname? = tailRef.value()value or expression? # variable definition3 currentDirname? = currentDirRef.value()value or expression? # variable definition4 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression? # reassign variable5 tailRef.setprocedureName?(body[0]arguments?) # call procedure6 body.appendprocedureName?(headarguments?) # call procedure7 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?) # call procedure8 currentDirRef.setprocedureName?(currentDirarguments?) # call procedure9
+static void updateSnakename?(AsRef<Direction> currentDirRef, AsRef<List<int>> tailRef, AsRef<List<int>> headRef, List<List<int>> bodyparameter definitions?) { // procedure1 var headname? = headRef.value()value or expression?;2 var tailname? = tailRef.value()value or expression?;3 var currentDirname? = currentDirRef.value()value or expression?;4 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression?; // reassign variable5 tailRef.setprocedureName?(body[0]arguments?); // call procedure6 body.appendprocedureName?(headarguments?); // call procedure7 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?); // call procedure8 currentDirRef.setprocedureName?(currentDirarguments?); // call procedure9 } // procedure
+Sub updateSnakename?(currentDirRef As AsRef(Of Direction), tailRef As AsRef(Of List(Of Integer)), headRef As AsRef(Of List(Of Integer)), body As List(Of List(Of Integer))parameter definitions?) ' procedure1 Dim headname? = headRef.value()value or expression? ' variable definition2 Dim tailname? = tailRef.value()value or expression? ' variable definition3 Dim currentDirname? = currentDirRef.value()value or expression? ' variable definition4 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression? ' reassign variable5 tailRef.setprocedureName?(body[0]arguments?) ' call procedure6 body.appendprocedureName?(headarguments?) ' call procedure7 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?) ' call procedure8 currentDirRef.setprocedureName?(currentDirarguments?) ' call procedure9 End Sub
public class Global {

+static void updateSnakename?(AsRef<Direction> currentDirRef, AsRef<List<int>> tailRef, AsRef<List<int>> headRef, List<List<int>> bodyparameter definitions?) { // procedure1 var headname? = headRef.value()value or expression?;2 var tailname? = tailRef.value()value or expression?;3 var currentDirname? = currentDirRef.value()value or expression?;4 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression?; // reassign variable5 tailRef.setprocedureName?(body[0]arguments?); // call procedure6 body.appendprocedureName?(headarguments?); // call procedure7 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?); // call procedure8 currentDirRef.setprocedureName?(currentDirarguments?); // call procedure9 } // procedure
}

Note that in each of those calls, the name of the procedure being called is preceded by an variable name and dot. This is known as 'dot syntax' and is used when the procedure is defined as a procedure method on a specified type. For example, in the call to body.append(head)body.append(head)body.append(head)body.append(head)body.append(head), append is a procedure method on the type ListlistListListList, which is the type of the bodybodybodybodybody variable.

What you need to know about procedure calls

  • The call procedure instruction is used when you want to run a procedure .
  • The arguments provided must match the number and type of the parameters specified in the definition of the procedure; if there are no parameters, leave the brackets empty.

    Procedures may have side effects, for example input/output or changing a data value in an object. For this reason, procedures cannot be called from functions, which are not allowed to have side effects. To enforce this, call procedure instructions are not allowed in functions.

    There is a limit to the complexity of a call procedure instruction. Only one dot is allowed in the procedure name field, or two dots if the first word is property. If you need anything more complicated, use a change variable instruction on the line above. See the error message explanation for 'procedureName' in a call instruction .

More advanced techniques

TODO: write afresh descriptions of mutating a (mutable) parameter, and, separately passing by Ref.

Try

Examples try statements

From the Date Time demo, where the input may be "Q""Q""Q""Q""Q" (to quit), an empty string (to get the current date/time), or may be a numeric value to be converted. The function call int(reply)int(reply)int(reply)int(reply)int(reply) attempts to read a string as an integer, but if it is not a valid integer will throw an exception. A try instruction is used to intercept (or 'catch') this exception and just ignore that input:

+main 1 variable replyname? set to ""value or expression?2 +while not reply.upperCase().equals("Q")condition? 3 reassign replyvariableName? to input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression?4 +if reply.equals("")condition? then 5 variable nowname? set to divAsInt(clock(), 1000)value or expression?6 print(nowarguments?)7 print(getDate(now)arguments?)8 else9 +try 10 variable tdname? set to int(reply)value or expression?11 +if td >= 0condition? then 12 print(getDate(td)arguments?)13 end if catch evariableName? as ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError?14 end try end if end while end main
+def main() -> None: 1 replyname? = ""value or expression? # variable definition2 +while not reply.upperCase().equals("Q")condition?: 3 replyvariableName? = input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression? # reassign variable4 +if reply.equals("")condition?: 5 nowname? = divAsInt(clock(), 1000)value or expression? # variable definition6 print(nowarguments?)7 print(getDate(now)arguments?)8 else:9 +try: 10 tdname? = int(reply)value or expression? # variable definition11 +if td >= 0condition?: 12 print(getDate(td)arguments?)13 except ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError? as evariableName?: # catch14 main()
+static void main() { 1 var replyname? = ""value or expression?;2 +while (!reply.upperCase().equals("Q")condition?) { 3 replyvariableName? = input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression?; // reassign variable4 +if (reply.equals("")condition?) { 5 var nowname? = divAsInt(clock(), 1000)value or expression?;6 Console.WriteLine(nowarguments?); // print7 Console.WriteLine(getDate(now)arguments?); // print8 } else {9 +try { 10 var tdname? = int(reply)value or expression?;11 +if (td >= 0condition?) { 12 Console.WriteLine(getDate(td)arguments?); // print13 } // if } catch (ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError? evariableName?) {14 } // try } // if } // while } // main
+Sub main() 1 Dim replyname? = ""value or expression? ' variable definition2 +While Not reply.upperCase().equals("Q")condition? 3 replyvariableName? = input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression? ' reassign variable4 +If reply.equals("")condition? Then 5 Dim nowname? = divAsInt(clock(), 1000)value or expression? ' variable definition6 Console.WriteLine(nowarguments?) ' print7 Console.WriteLine(getDate(now)arguments?) ' print8 Else9 +Try 10 Dim tdname? = int(reply)value or expression? ' variable definition11 +If td >= 0condition? Then 12 Console.WriteLine(getDate(td)arguments?) ' print13 End If Catch evariableName? As ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError?14 End Try End If End While End Sub
public class Global {

+static void main() { 1 var replyname? = ""value or expression?;2 +while (!reply.upperCase().equals("Q")condition?) { 3 replyvariableName? = input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression?; // reassign variable4 +if (reply.equals("")condition?) { 5 var nowname? = divAsInt(clock(), 1000)value or expression?;6 System.out.println(nowarguments?); // print7 System.out.println(getDate(now)arguments?); // print8 } else {9 +try { 10 var tdname? = int(reply)value or expression?;11 +if (td >= 0condition?) { 12 System.out.println(getDate(td)arguments?); // print13 } // if } catch (ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError? evariableName?) {14 } // try } // if } // while } // main
}

What you need to know about try instructions

  • The try instruction is a mechanism to execute code that might result in an 'exception' (called an 'error' in some languages), and to catch that exception without the program being automatically halted.
  • The try instruction is often referred to as a `try-catch` - because it makes no sense to have a try statement without a 'catch' clause.
  • When you add a try instruction in Elan, the 'catch' clause is automatically included in the instruction frame (template).
  • 'except' is Python's word for 'catch'
  • When you add a new try the instructions that you want to try to execute are specified immediately underneath the first line.
  • Underneath the catch clause you may instructions to handle the exception, for example by advising the user to try again.
  • The catch clause must specify the type of the exception to be caught. When working in Elan, only two types are permitted:
    • ElanRuntimeErrorElanRuntimeErrorElanRuntimeErrorElanRuntimeErrorElanRuntimeError which covers all errors thrown by the Elan system, including, for example 'Out of range index ...'
    • CustomErrorCustomErrorCustomErrorCustomErrorCustomError an exception explicitly thrown by code by a throw instruction in your own code (see below).
  • Currently Elan permits only one catch clause to be associated with each try

You can enclose code in a try construction when you want it to be able, at execution, to throw an exception that you handle.

This might arise when calling a System method that is dependent upon external conditions, such as when cancelling the writing of a text file, as in the example in Writing text files .

The try construction automatically provides a catch clause in which you provide a String for an message.

You can also use a test to check whether another piece of code might throw an exception by wrapping it in a try instruction.

Throw

You can deliberately generate, or 'throw', an error exception when a specific circumstance is identified, using a throw instruction which defines an explanatory string.

For the program to retain control when an exception is raised, use the try instruction.

Example of a throwing an exception

The following function to calculate factorials deliberately 'throws' an exception (error) if it is given a negative value for the argument:

+function factorialname?(n as Intparameter definitions?) returns IntType? 1 variable resultname? set to 1value or expression?2 +if n > 1condition? then 3 reassign resultvariableName? to n*factorial(n - 1)value or expression?4 elif n < 0condition? then5 throw CustomErrorexception type? "Factorial cannot be called with a negative argument"message?6 end if return resultvalue or expression?7 end function
+def factorialname?(n: intparameter definitions?) -> intType?: # function1 resultname? = 1value or expression? # variable definition2 +if n > 1condition?: 3 resultvariableName? = n*factorial(n - 1)value or expression? # reassign variable4 elif n < 0condition?: # else if5 raise CustomErrorexception type?("Factorial cannot be called with a negative argument"message?)6 return resultvalue or expression?7
+static intType? factorialname?(int nparameter definitions?) { // function1 var resultname? = 1value or expression?;2 +if (n > 1condition?) { 3 resultvariableName? = n*factorial(n - 1)value or expression?; // reassign variable4 } else if (n < 0condition?) {5 throw new CustomErrorexception type?("Factorial cannot be called with a negative argument"message?);6 } // if return resultvalue or expression?;7 } // function
+Function factorialname?(n As Integerparameter definitions?) As IntegerType? 1 Dim resultname? = 1value or expression? ' variable definition2 +If n > 1condition? Then 3 resultvariableName? = n*factorial(n - 1)value or expression? ' reassign variable4 ElseIf n < 0condition? Then5 Throw New CustomErrorexception type?("Factorial cannot be called with a negative argument"message?)6 End If Return resultvalue or expression?7 End Function
public class Global {

+static intType? factorialname?(int nparameter definitions?) { // function1 var resultname? = 1value or expression?;2 +if (n > 1condition?) { 3 resultvariableName? = n*factorial(n - 1)value or expression?; // reassign variable4 } else if (n < 0condition?) {5 throw new CustomErrorexception type?("Factorial cannot be called with a negative argument"message?);6 } // if return resultvalue or expression?;7 } // function
}

What you need to know about throwing an exception

  • The throw instruction will cause an 'exception' to be 'thrown' (or 'raised' in some languages).
  • Unless the throw instruction is directly or indirectly within a Try instruction (see above), program execution will be halted and the message defined in the throw instruction printed on the display.
  • Because of this drastic effect it is recommended not to throw exceptions only in rare circumstances.

Assert

Examples of asserts

From the Date Time demo, showing asserts for normal and edge cases

new code
new code
new code
new code
public class Global {

new code
}
test test_leap # normal cases assert leap(2025) is false assert leap(2024) is true # boundary cases assert leap(1900) is false assert leap(2000) is true end test

From the Tower of Hanoi

demo, showing an assert for an error case:
new code
new code
new code
new code
public class Global {

new code
}

From the Tower of Hanoi

demo, showing an assert for an error case:
+test test_colourtest_name? 1 # Normal casescomment? assert colour(5)actual (computed) value? is greenexpected value? not run2 # Edge casescomment? assert colour(1)actual (computed) value? is redexpected value? not run3 assert colour(10)actual (computed) value? is 0xFF99CCexpected value? not run4 # Error casescomment? assert colour(11)actual (computed) value? is "Out of range index: 10 size: 10"expected value? not run5 end test
+def test_colourtest_name?(self) -> None: 1 # Normal casescomment? self.assertEqual(colour(5)actual (computed) value?, greenexpected value?) not run2 # Edge casescomment? self.assertEqual(colour(1)actual (computed) value?, redexpected value?) not run3 self.assertEqual(colour(10)actual (computed) value?, 0xFF99CCexpected value?) not run4 # Error casescomment? self.assertEqual(colour(11)actual (computed) value?, "Out of range index: 10 size: 10"expected value?) not run5
+[TestMethod] static void test_colourtest_name?() { 1 // Normal casescomment? Assert.AreEqual(greenexpected value?, colour(5)actual (computed) value?); not run2 // Edge casescomment? Assert.AreEqual(redexpected value?, colour(1)actual (computed) value?); not run3 Assert.AreEqual(0xFF99CCexpected value?, colour(10)actual (computed) value?); not run4 // Error casescomment? Assert.AreEqual("Out of range index: 10 size: 10"expected value?, colour(11)actual (computed) value?); not run5 } // test
+<TestMethod> Sub test_colourtest_name?() 1 ' Normal casescomment? Assert.AreEqual(greenexpected value?, colour(5)actual (computed) value?) not run2 ' Edge casescomment? Assert.AreEqual(redexpected value?, colour(1)actual (computed) value?) not run3 Assert.AreEqual(&HFF99CCexpected value?, colour(10)actual (computed) value?) not run4 ' Error casescomment? Assert.AreEqual("Out of range index: 10 size: 10"expected value?, colour(11)actual (computed) value?) not run5 End Sub
public class Global {

+@Test static void test_colourtest_name?() { 1 // Normal casescomment? assertEquals(greenexpected value?, colour(5)actual (computed) value?); not run2 // Edge casescomment? assertEquals(redexpected value?, colour(1)actual (computed) value?); not run3 assertEquals(0xFF99CCexpected value?, colour(10)actual (computed) value?); not run4 // Error casescomment? assertEquals("Out of range index: 10 size: 10"expected value?, colour(11)actual (computed) value?); not run5 } // test
}

What you need to know about asserts

  • An assert instruction is used to test an individual case within a test, or sometimes to test individual result values in a single case that returns a data structure.
  • The assert defines two fields that the user must specify:
    • The actual value as returned by a call to the function being tested.
    • The expected value, which will typically be a literal value of the same type as the actual.
    • When the tests are run (automatically whenever the code compiles) the result - pass or fail - will be shown alongside each assert.
    • A fail indicates that the expected and actual values do not match.
    • The expected value should be of the same type as the actual; if they are not that will be reported as a test failure, without showing the actual values.
    • You can, however, test for an exception having been thrown, by setting the expected value to a the expected error message (as a string).
    • Unlike on many systems, Elan does not stop at the first failure within each test - it will attempt to evaluate all the asserts.
    • The required order of the actual and expected fields varies between the supported languages; Elan handles this automatically when translating between languages.

Comment

See Comments

Expressions

One of the most important constructs in programming is the expression. An expression evaluates and returns a value using the following elements:

Literal value

A literal value is where a value is written 'literally' in the code, such as 3.1423.1423.1423.1423.142, in contrast to a value that is referred to by a name.

Here is a table showing some example literal values. Follow the links for more information about each Type.

TypeExample of literal
Int 33333
Float 2.02.02.02.02.0
Boolean true
String "Hello""Hello""Hello""Hello""Hello"
Tuple (3, "banana")(3, "banana")(3, "banana")(3, "banana")(3, "banana")
List ["lemon", "lime", "orange"]["lemon", "lime", "orange"]["lemon", "lime", "orange"]{"lemon", "lime", "orange"}["lemon", "lime", "orange"]
Dictionary ["a":3, "b":5]["a":3, "b":5]["a":3, "b":5]["a":3, "b":5]["a":3, "b":5]

For more about List and Dictionary literals see the Standard data structures table.

Indexed values

If a named value is of an indexable type (StringstrstringStringString or ListlistListListList), then you can refer to a contiguous subset (or range) of its values using methods subString and subList respectively.

The upper index of a range is exclusive, so to define a range that picks items indexed from i to j, you would specify the range as (i, j + 1)(i, j + 1)(i, j + 1)(i, j + 1)(i, j + 1).

If the two index values of a range are equal, or the second is smaller than the first, then an empty string or list is returned.

Indexes are used only for reading values. Writing a value to a specific index location in a list is done using method put .

Example using indexing on a string:

+main 1 variable aname? set to "Hello world!"value or expression?2 call printprocedureName?(a[4]arguments?)3 call printprocedureName?(a.subString(4, a.length())arguments?)4 call printprocedureName?(a.subString(0, 7)arguments?)5 end main
+def main() -> None: 1 aname? = "Hello world!"value or expression? # variable definition2 printprocedureName?(a[4]arguments?)3 printprocedureName?(a.subString(4, a.length())arguments?)4 printprocedureName?(a.subString(0, 7)arguments?)5 main()
+static void main() { 1 var aname? = "Hello world!"value or expression?;2 printprocedureName?(a[4]arguments?);3 printprocedureName?(a.subString(4, a.length())arguments?);4 printprocedureName?(a.subString(0, 7)arguments?);5 } // main
+Sub main() 1 Dim aname? = "Hello world!"value or expression? ' variable definition2 printprocedureName?(a[4]arguments?)3 printprocedureName?(a.subString(4, a.length())arguments?)4 printprocedureName?(a.subString(0, 7)arguments?)5 End Sub
public class Global {

+static void main() { 1 var aname? = "Hello world!"value or expression?;2 printprocedureName?(a[4]arguments?);3 printprocedureName?(a.subString(4, a.length())arguments?);4 printprocedureName?(a.subString(0, 7)arguments?);5 } // main
}






o
o world!
Hello w
    (since the upper bound of a range is exclusive)

And on a list:

+main 1 variable liname? set to new List<of Int>()value or expression?2 reassign livariableName? to [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]value or expression?3 call printprocedureName?(li[4]arguments?)4 call printprocedureName?(li.subList(4, li.length())arguments?)5 call printprocedureName?(li.subList(0, 7)arguments?)6 end main
+def main() -> None: 1 liname? = list[int]()value or expression? # variable definition2 livariableName? = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]value or expression? # reassign variable3 printprocedureName?(li[4]arguments?)4 printprocedureName?(li.subList(4, li.length())arguments?)5 printprocedureName?(li.subList(0, 7)arguments?)6 main()
+static void main() { 1 var liname? = new List<int>()value or expression?;2 livariableName? = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]value or expression?; // reassign variable3 printprocedureName?(li[4]arguments?);4 printprocedureName?(li.subList(4, li.length())arguments?);5 printprocedureName?(li.subList(0, 7)arguments?);6 } // main
+Sub main() 1 Dim liname? = New List(Of Integer)()value or expression? ' variable definition2 livariableName? = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}value or expression? ' reassign variable3 printprocedureName?(li[4]arguments?)4 printprocedureName?(li.subList(4, li.length())arguments?)5 printprocedureName?(li.subList(0, 7)arguments?)6 End Sub
public class Global {

+static void main() { 1 var liname? = new List<int>()value or expression?;2 livariableName? = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]value or expression?; // reassign variable3 printprocedureName?(li[4]arguments?);4 printprocedureName?(li.subList(4, li.length())arguments?);5 printprocedureName?(li.subList(0, 7)arguments?);6 } // main
}








4
    (the value of the list item)
[4, 5, 6, 7, 8, 9, 10, 11]     (a new list)
[0, 1, 2, 3, 4, 5, 6]     (since the upper bound of a range is exclusive)

Operators

Arithmetic operators

Arithmetic operators can be applied to FloatfloatdoubleDoubledouble or IntintintIntegerint arguments. The result may be a FloatfloatdoubleDoubledouble or an IntintintIntegerint depending on the arguments.

For the + − * operators, the result is a FloatfloatdoubleDoubledouble if either of the arguments is a FloatfloatdoubleDoubledouble, and an IntintintIntegerint if both arguments are IntintintIntegerint.

For the / operator, at least one of the values must be a FloatfloatdoubleDoubledouble, and the result is always a FloatfloatdoubleDoubledouble. It can be converted to an IntintintIntegerint using standalone function floor .

For raising one value to the power of another, use the syntax in these examples:

call printprocedureName?(pow(4, 4)arguments?)0printprocedureName?(pow(4, 4)arguments?)0printprocedureName?(pow(4, 4)arguments?);0printprocedureName?(pow(4, 4)arguments?)0printprocedureName?(pow(4, 4)arguments?);0
variable cname? set to sqrt(pow(a, 2) + pow(b, 2))value or expression?0cname? = sqrt(pow(a, 2) + pow(b, 2))value or expression? # variable definition0var cname? = sqrt(pow(a, 2) + pow(b, 2))value or expression?;0Dim cname? = sqrt(pow(a, 2) + pow(b, 2))value or expression? ' variable definition0var cname? = sqrt(pow(a, 2) + pow(b, 2))value or expression?;0

If, when including a power in an expression, it does not parse, try enclosing parts of the expression in brackets to remove potential ambiguity.

The operator mod (integer remainder) is applied only to IntintintIntegerint arguments, and the result is IntintintIntegerint.

2/3.02/3.02/3.02/3.02/3.00.666..
2*32*32*32*32*36
2 + 32 + 32 + 32 + 32 + 35
2 - 32 - 32 - 32 - 32 - 3−1
11 mod 311 % 311 % 311 Mod 311 % 32

For integer division, either use method floor on a FloatfloatdoubleDoubledouble result, or use method divAsInt on FloatfloatdoubleDoubledouble arguments (which returns an IntintintIntegerint):

(11.0/3.0).floor()(11.0/3.0).floor()(11.0/3.0).floor()(11.0/3.0).floor()(11.0/3.0).floor()3
(-11.0/3.0).floor()(-11.0/3.0).floor()(-11.0/3.0).floor()(-11.0/3.0).floor()(-11.0/3.0).floor()−4
divAsInt(11.0, 3.0)divAsInt(11.0, 3.0)divAsInt(11.0, 3.0)divAsInt(11.0, 3.0)divAsInt(11.0, 3.0)3

Note that rounding is always down.

Arithmetic operators follow the conventional rules for precedence i.e. 'BIDMAS' (or 'BODMAS').

When combining mod with other operators in an expression, insert round brackets to avoid ambiguity e.g.:

(5 + 6) mod 3(5 + 6) % 3(5 + 6) % 3(5 + 6) Mod 3(5 + 6) % 32

Note that mod is more of a remainder operator than a modulus operator. The result takes the sign of the first argument. If both arguments are positive, there is no difference.

The minus sign may also be used as a unary operator, and this takes precedence over binary operators so:

2*-32*-32*-32*-32*-3−6

The editor automatically puts spaces around the operators + and −, but not around / or *. This is just to visually reinforce the precedence.

Logical operators

Logical operators are applied to BooleanboolboolBooleanbool arguments and return a BooleanboolboolBooleanbool result.

andandandandand and ororororor are binary operators
notnotnotnotnot is a unary operator.

The operator precedence is notnotnotnotnotandandandandandororororor, so this example, which implements an 'exclusive or', need not use brackets and can rely on the operator precedence:

+function exOrname?(a as Boolean, b as Booleanparameter definitions?) returns BooleanType? 1 return a and not b or b and not avalue or expression?2 end function
+def exOrname?(a: bool, b: boolparameter definitions?) -> boolType?: # function1 return a and not b or b and not avalue or expression?2
+static boolType? exOrname?(bool a, bool bparameter definitions?) { // function1 return a && !b || b && !avalue or expression?;2 } // function
+Function exOrname?(a As Boolean, b As Booleanparameter definitions?) As BooleanType? 1 Return a And Not b Or b And Not avalue or expression?2 End Function
public class Global {

+static boolType? exOrname?(bool a, bool bparameter definitions?) { // function1 return a && !b || b && !avalue or expression?;2 } // function
}

String operator

The + operator is also used for concatenating StringstrstringStringString values.

Equality testing

Testing equality of IntintintIntegerint, FloatfloatdoubleDoubledouble or BooleanboolboolBooleanbool values

Equality testing of these Value Types uses the is and isnt operators between two values and returns a BooleanboolboolBooleanbool result:

Testing equality of StringstrstringStringString values and of all Reference Types

Equality testing of these Reference Types uses common dot methods equals and notEqualTo:

The comparison is made sequentially through the characters or items in the structures to see if the objects are equal. Two Lists compare equal if they contain the same items in the same order. And two instances of the same class compare equal if the values of all their properties compare equal.

The compiler rejects any attempt to compare instances of different classes unless abstract classes and inheritance are involved. Two instances which are subclasses of the same abstract class compare equal only if they are of the same class (and have the same property values).

Numeric comparison

The numeric comparison operators are:

>forgreater than
<forless than
>=forgreater than or equal to
<=forless than or equal to

Each may be applied between two values of type FloatfloatdoubleDoubledouble, but any named value or expression that evaluates to an IntintintIntegerint may always be used where a FloatfloatdoubleDoubledouble is expected.

These operators cannot be applied to strings. Use the dot methods isBefore and isAfter to compare strings alphabetically. See Dot methods on a String .

Combining operators

You can combine operators of different kinds, e.g. combining numeric comparison with logical operators in a single expression. However the rules of precedence between operators of different kinds are complex. It is strongly recommend that you always use brackets to disambiguate such expressions, for example:

reassign xvariableName? to (a > b) and (b < c)value or expression?0xvariableName? = (a > b) and (b < c)value or expression? # reassign variable0xvariableName? = (a > b) && (b < c)value or expression?; // reassign variable0xvariableName? = (a > b) And (b < c)value or expression? ' reassign variable0xvariableName? = (a > b) && (b < c)value or expression?; // reassign variable0
reassign xvariableName? to (a + b) > (c - d)value or expression?0xvariableName? = (a + b) > (c - d)value or expression? # reassign variable0xvariableName? = (a + b) > (c - d)value or expression?; // reassign variable0xvariableName? = (a + b) > (c - d)value or expression? ' reassign variable0xvariableName? = (a + b) > (c - d)value or expression?; // reassign variable0

Function reference

An expression may simply be a reference to a function, or it may include several function references within it. Examples:
+main 1 call printprocedureName?(sin(radians(30))arguments?)2 variable xname? set to pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression?3 variable namename? set to input("Your name")value or expression?4 call printprocedureName?(name.upperCase()arguments?)5 end main
+def main() -> None: 1 printprocedureName?(sin(radians(30))arguments?)2 xname? = pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression? # variable definition3 namename? = input("Your name")value or expression? # variable definition4 printprocedureName?(name.upperCase()arguments?)5 main()
+static void main() { 1 printprocedureName?(sin(radians(30))arguments?);2 var xname? = pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression?;3 var namename? = input("Your name")value or expression?;4 printprocedureName?(name.upperCase()arguments?);5 } // main
+Sub main() 1 printprocedureName?(sin(radians(30))arguments?)2 Dim xname? = pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression? ' variable definition3 Dim namename? = input("Your name")value or expression? ' variable definition4 printprocedureName?(name.upperCase()arguments?)5 End Sub
public class Global {

+static void main() { 1 printprocedureName?(sin(radians(30))arguments?);2 var xname? = pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression?;3 var namename? = input("Your name")value or expression?;4 printprocedureName?(name.upperCase()arguments?);5 } // main
}

Notes

lambda

A lambda is a lightweight means of defining a function 'inline' and without a name (i.e. an anonymous function).

You can use a lambda only as an argument to a Higher-order Function .

You would typically define a lambda when the functionality it defines is needed in only one location, e.g. in a particular call to a Higher-order Function.

The syntax for a lambda is as follows:

For examples of using lambda in Higher-order Functions, see Library functions that process Lists

if (expression)

The if expression returns one of two values or expressions dependent upon the evaulation of a Boolean value:

Its syntax is shown in this reassign variable instruction:

variable isBiggername? set to if(a >= b, true, false)value or expression?0isBiggername? = if(a >= b, True, False)value or expression? # variable definition0var isBiggername? = if(a >= b, true, false)value or expression?;0Dim isBiggername? = if(a >= b, True, False)value or expression? ' variable definition0var isBiggername? = if(a >= b, true, false)value or expression?;0

Similarly, but returning an evaluated expression:

variable dname? set to if(c < 580, c - 40, c + 60)value or expression?0dname? = if(c < 580, c - 40, c + 60)value or expression? # variable definition0var dname? = if(c < 580, c - 40, c + 60)value or expression?;0Dim dname? = if(c < 580, c - 40, c + 60)value or expression? ' variable definition0var dname? = if(c < 580, c - 40, c + 60)value or expression?;0

in which variable d takes the value of c - 40c - 40c - 40c - 40c - 40 if c < 580c < 580c < 580c < 580c < 580, or c + 60c + 60c + 60c + 60c + 60 otherwise.

An if expression may be used within an if expression but may be hard to read and understand.

Examples using if expression:

   ● Choice in a return instruction:

+function fooname?(parameter definitionsparameter definitions?) returns IntType? 1 return if(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?2 end function
+def fooname?(parameter definitionsparameter definitions?) -> intType?: # function1 return if(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?2
+static intType? fooname?(parameter definitionsparameter definitions?) { // function1 return if(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?;2 } // function
+Function fooname?(parameter definitionsparameter definitions?) As IntegerType? 1 Return if(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?2 End Function
public class Global {

+static intType? fooname?(parameter definitionsparameter definitions?) { // function1 return if(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?;2 } // function
}

   ● Using an if expression in a call procedure print:

+main 1 variable pname? set to unicode(0x03c0)value or expression?2 call testPiprocedureName?(p, 3arguments?)3 call testPiprocedureName?(p, 4arguments?)4 end main +procedure testPiname?(p as String, v as Floatparameter definitions?) 5 call printprocedureName?(if(pi > v, $"{p} > {v}", $"{p} < {v}")arguments?)6 end procedure
+def main() -> None: 1 pname? = unicode(0x03c0)value or expression? # variable definition2 testPiprocedureName?(p, 3arguments?) # call procedure3 testPiprocedureName?(p, 4arguments?) # call procedure4 +def testPiname?(p: str, v: floatparameter definitions?) -> None: # procedure5 printprocedureName?(if(pi > v, f"{p} > {v}", f"{p} < {v}")arguments?)6 main()
+static void main() { 1 var pname? = unicode(0x03c0)value or expression?;2 testPiprocedureName?(p, 3arguments?); // call procedure3 testPiprocedureName?(p, 4arguments?); // call procedure4 } // main +static void testPiname?(string p, double vparameter definitions?) { // procedure5 printprocedureName?(if(pi > v, $"{p} > {v}", $"{p} < {v}")arguments?);6 } // procedure
+Sub main() 1 Dim pname? = unicode(&H03c0)value or expression? ' variable definition2 testPiprocedureName?(p, 3arguments?) ' call procedure3 testPiprocedureName?(p, 4arguments?) ' call procedure4 End Sub +Sub testPiname?(p As String, v As Doubleparameter definitions?) ' procedure5 printprocedureName?(if(pi > v, $"{p} > {v}", $"{p} < {v}")arguments?)6 End Sub
public class Global {

+static void main() { 1 var pname? = unicode(0x03c0)value or expression?;2 testPiprocedureName?(p, 3arguments?); // call procedure3 testPiprocedureName?(p, 4arguments?); // call procedure4 } // main +static void testPiname?(String p, double vparameter definitions?) { // procedure5 printprocedureName?(arguments?);6 } // procedure
}





π > 3
π < 4

new

A 'new instance' expression is used to create a new instance of a library data structure , or a user-defined class , either to assign to a named value, or as part of a more complex expression. Example of use from demo program snake_PP.elan:

variable headRefname? set to new AsRef<of List<of Int>>(head)value or expression?0headRefname? = AsRef[list[int]](head)value or expression? # variable definition0var headRefname? = new AsRef<List<int>>(head)value or expression?;0Dim headRefname? = New AsRef(Of List(Of Integer))(head)value or expression? ' variable definition0var headRefname? = new AsRef<List<int>>(head)value or expression?;0

Miscellaneous

Field help

'arguments' field in a call instruction

An argument list passed into a function or procedure call, must consist of one or more arguments separated by commas. Each argument may in general be any of:

In certain very specific contexts, however, some options are disallowed by the compiler.

'computed value' field in an assert instruction

The 'actual' field should be kept as simple as possible, preferably just a named value or a function evaluation. Generally, if you want to use a more complex expression, it is better to evaluate it in a preceding variable definition instruction and then use the named value in the 'actual' field of the assert equal instruction. Some more complex expressions are permissible, but these two restrictions apply:

'variable name' field in a set instruction

The first field in a change variable instruction most commonly takes the name of an existing variable.

'comment' field

literal value or data structure in a constant

The value of a constant must be a literal value of a type that is not mutable. This can be a simple value (e.g. a number or string), or an immutable List or Dictionary.

'values' field in an enum definition

enum values must each be a valid identifier, separated by commas.

'message' field in a throw instruction

An exception message must be either a literal string or a named value holding a string.

expression field - used within multiple instructions

This field expects an expression. For the various forms of expression see Expressions .

identifier field - used within multiple instructions

'if' field in an else clause

'inherits className' field in a class

An inheritance clause, if used, must consist of the keyword inherits followed by a space and then one or more type names separated by commas.

'name' field in a function or procedure definition

A method name must follow the rules for an identifier .

'parameter definitions' in a function or procedure definition

Each parameter definition takes the form:

name as Type

The name must follow the rules for an identifier .

The type must follow the rules for a Type .

If more than one parameter is defined, the definitions must be separated by commas.

'procedureName' in a call statement

Valid forms for a procedure call are

The last one is used only if there is a need to disambiguate between a library procedure and a user-defined (global) procedure with the same name.

'Type' field in a function or property definition

For certain Types the name may be followed by an of clause, for example:

ListlistListListList<of IntintintIntegerint>
DictionaryDictionaryDictionaryDictionaryDictionary<of StringstrstringStringString, IntintintIntegerint>

'Name' field in a class or enum definition

Type names always begin with a capital letter, optionally followed by letters of either case, numeric digits, or underscore symbols. Nothing else.

'name' field in a let or variable instruction

The definition for a variable instruction is most commonly a simple name. Less commonly, it may take the form of a deconstruction of a ListlistListListList or Tuple .