Procedural programming Reference

Table of contents

Main topics | Detailed topics

Getting started

Select the procedural programming paradigm

To undertake procedural programming (PP) in Elan, ensure that Elan is set to the procedural paradigm 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 with using the Elan IDE, especially for entering and editing code. Do this by watching the videos here . You will learn that Elan supports programming in Python, VB, C#, and Java (as well as the Reference Language Elan), and offers the option of immediate translation between these languages. If you have previously programmed in any of the languages you will find that Elan enforces best practice by means of the following constraints on how code is written. There are three forms of constraint, which are described in more detail in these sections:

  1. Best practices in coding that Elan encourages
  2. Use of a common library across all the supported languages
  3. Some, generally smaller, constraints needed for portability between the supported languages

Best practices in coding that Elan encourages

Elan supports, and in many cases enforces good programming practices, which are automatically applied across all the supported languages, even where a language ( when used outside Elan) would allow you to do otherwise. In the context of procedural programming (the procedural paradigm having been selected in the IDE), there are seven such best practices, some widely recognised, others less so.

Some programmers will argue that there are specific circumstances where breaking the rules should be acceptable. The problem with this 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 to learn to program without breaking these rules than that to adopt bad habits from the beginning. This will result in you writing better code and encountering fewer unexpected problems.

The six best practices enforced by Elan for procedural programming are:

  1. No global variables
  2. Static typing
  3. No null references
  4. No breaking out of loops before they have completed
  5. Return from a function or procedure only after the final instruction
  6. All functions must be pure
together with this seventh best practice that is supported and encouraged by Elan:
  1. Every function should be unit tested

No global variables

It is widely recognised that global variables are a bad idea: they encourage poor program structure and lead to unexpected behaviour. Although most programming languages permit them, the Elan editor solves this by offering the variable definition instruction only within the main routine, functions, and 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 to adhere to this rule, and all code must be within this. Everything added immediately within this class is deemed, in Elan terms, to be global, so you will not be able to add a variable, even in the form of a class property there.

Static typing

Static typing means that the type of every variable, 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 statically typed. but Python is inherently dynamically typed. Python 3.5 offers the option to add 'type hints' that assist 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 purely educational context, using static typing gives a stronger understanding that, in all modern languages (with static or dynamic types) all data items always have an associated type, and static typing helps you to understand the nature of those types.

When entering and editing Python, Elan therefore enforces the use of type hints.

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

No null references

All programmers have encountered the notorious 'Null Reference ...` (or 'Null Pointer ...`) error message, and the cause 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, though 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 allows you to break out of a loop before its completion. Elan does not permit any of those instructions to be used for the following reasons.

The break instruction was invented when the only possible kind of loop that a programming language offered 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. With properly 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, in that programmers find it convenient to include multiple returnreturnreturnreturnreturn instructions within a procedure or a function. All the supported languages permit this. Elan, however, deliberately prevents this practice. An Elan function always has one return instruction as its last instruction (which specifies the value to be returned), and there is no option to add another one earlier. An Elan procedure cannot have a return instruction: the procedure exits after its last instruction.

Some programmers may 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 to hold the result. This is true but it's a small price to pay and makes your code more readable

In summary, here are four reasons to avoid early returns:

All functions must be pure

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

A procedure:

By contrast, a proper function:

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, and 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, manually running through a large number of 'test scripts' (or 'cases') 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 has shifted to automated testing. Although writing the test scripts or cases still takes time, 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 and use.

Unit tests are intended for testing only proper functions though many languages do not enforce that. Running unit tests on procedures or on improper functions (that create side effects) can be dangerous – running the tests might result in data being written to a database, or a network message being sent repeatedly. There are software frameworks for automated testing 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 can benefit from:

Elan provides unit testing for all the supported languages, and it 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 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 the 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

In Python, VB, C#, and the Reference Language, global instructions are placed directly in your code file, not within other code, and are therefore not indented.

javaJava note

All Java code must be within a class so, when writing Java with Elan, the editor generates a non-editable class named Global, and all global instructions are created directly within that (and automatically indented).

Main routine

Examples of a main routine

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

+main 70 variable blocksname? set to createBlockGraphics(white)value or expression?71 variable xname? set to 20value or expression?72 variable yname? set to 15value or expression?73 +while truecondition? 74 assign blocks[x][y]variableName? to redvalue or expression?75 call displayBlocksprocedureName?(blocksarguments?)76 assign blocks[x][y]variableName? to blackvalue or expression?77 variable directionname? set to randint(0, 3)value or expression?78 +if direction is 0condition? then 79 assign xvariableName? to min([x + 1, 39])value or expression?80 elif direction is 1condition? then81 assign xvariableName? to max([x - 1, 0])value or expression?82 elif direction is 2condition? then83 assign yvariableName? to min([y + 1, 29])value or expression?84 elif direction is 3condition? then85 assign yvariableName? to max([y - 1, 0])value or expression?86 end if end while end main
+def main() -> None: 87 blocksname? = createBlockGraphics(white)value or expression? # variable definition88 xname? = 20value or expression? # variable definition89 yname? = 15value or expression? # variable definition90 +while Truecondition?: 91 blocks[x][y]variableName? = redvalue or expression? # assignment92 displayBlocksprocedureName?(blocksarguments?) # procedure call93 blocks[x][y]variableName? = blackvalue or expression? # assignment94 directionname? = randint(0, 3)value or expression? # variable definition95 +if direction == 0condition?: 96 xvariableName? = min([x + 1, 39])value or expression? # assignment97 elif direction == 1condition?: # else if98 xvariableName? = max([x - 1, 0])value or expression? # assignment99 elif direction == 2condition?: # else if100 yvariableName? = min([y + 1, 29])value or expression? # assignment101 elif direction == 3condition?: # else if102 yvariableName? = max([y - 1, 0])value or expression? # assignment103 # end if # end while # end main
+static void main() { 104 var blocksname? = createBlockGraphics(white)value or expression?;105 var xname? = 20value or expression?;106 var yname? = 15value or expression?;107 +while (truecondition?) { 108 blocks[x][y]variableName? = redvalue or expression?; // assignment109 displayBlocksprocedureName?(blocksarguments?); // procedure call110 blocks[x][y]variableName? = blackvalue or expression?; // assignment111 var directionname? = randint(0, 3)value or expression?;112 +if (direction == 0condition?) { 113 xvariableName? = min(new [] {x + 1, 39})value or expression?; // assignment114 } else if (direction == 1condition?) {115 xvariableName? = max(new [] {x - 1, 0})value or expression?; // assignment116 } else if (direction == 2condition?) {117 yvariableName? = min(new [] {y + 1, 29})value or expression?; // assignment118 } else if (direction == 3condition?) {119 yvariableName? = max(new [] {y - 1, 0})value or expression?; // assignment120 } // end if } // end while } // end main
+Sub main() 121 Dim blocksname? = createBlockGraphics(white)value or expression? ' variable definition122 Dim xname? = 20value or expression? ' variable definition123 Dim yname? = 15value or expression? ' variable definition124 +While Truecondition? 125 blocks(x)(y)variableName? = redvalue or expression? ' assignment126 displayBlocksprocedureName?(blocksarguments?) ' procedure call127 blocks(x)(y)variableName? = blackvalue or expression? ' assignment128 Dim directionname? = randint(0, 3)value or expression? ' variable definition129 +If direction = 0condition? Then 130 xvariableName? = min({x + 1, 39})value or expression? ' assignment131 ElseIf direction = 1condition? Then132 xvariableName? = max({x - 1, 0})value or expression? ' assignment133 ElseIf direction = 2condition? Then134 yvariableName? = min({y + 1, 29})value or expression? ' assignment135 ElseIf direction = 3condition? Then136 yvariableName? = max({y - 1, 0})value or expression? ' assignment137 End If End While End Sub
+static void main() { 138 var blocksname? = createBlockGraphics(white)value or expression?;139 var xname? = 20value or expression?;140 var yname? = 15value or expression?;141 +while (truecondition?) { 142 blocks[x][y]variableName? = redvalue or expression?; // assignment143 displayBlocksprocedureName?(blocksarguments?); // procedure call144 blocks[x][y]variableName? = blackvalue or expression?; // assignment145 var directionname? = randint(0, 3)value or expression?;146 +if (direction == 0condition?) { 147 xvariableName? = min(list(x + 1, 39))value or expression?; // assignment148 } else if (direction == 1condition?) {149 xvariableName? = max(list(x - 1, 0))value or expression?; // assignment150 } else if (direction == 2condition?) {151 yvariableName? = min(list(y + 1, 29))value or expression?; // assignment152 } else if (direction == 3condition?) {153 yvariableName? = max(list(y - 1, 0))value or expression?; // assignment154 } // end if } // end while } // end 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 155 variable liname? set to [3, 6, 1, 0, 99, 4, 67]value or expression?156 call inPlaceRippleSortprocedureName?(liarguments?)157 print(liarguments?)158 end main
+def main() -> None: 159 liname? = [3, 6, 1, 0, 99, 4, 67]value or expression? # variable definition160 inPlaceRippleSortprocedureName?(liarguments?) # procedure call161 print(liarguments?)162 # end main
+static void main() { 163 var liname? = new [] {3, 6, 1, 0, 99, 4, 67}value or expression?;164 inPlaceRippleSortprocedureName?(liarguments?); // procedure call165 Console.WriteLine(liarguments?); // print statement166 } // end main
+Sub main() 167 Dim liname? = {3, 6, 1, 0, 99, 4, 67}value or expression? ' variable definition168 inPlaceRippleSortprocedureName?(liarguments?) ' procedure call169 Console.WriteLine(liarguments?) ' print statement170 End Sub
+static void main() { 171 var liname? = list(3, 6, 1, 0, 99, 4, 67)value or expression?;172 inPlaceRippleSortprocedureName?(liarguments?); // procedure call173 System.out.println(liarguments?); // print statement174 } // end 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 175 variable gridname? set to createBlockGraphics(white)value or expression?176 call fillRandomprocedureName?(gridarguments?)177 +while truecondition? 178 call displayBlocksprocedureName?(gridarguments?)179 variable gridRefname? set to new AsRef<of List<of List<of Int>>>(grid)value or expression?180 call nextGenerationprocedureName?(gridRefarguments?)181 assign gridvariableName? to gridRef.value()value or expression?182 call sleep_msprocedureName?(50arguments?)183 end while end main
+def main() -> None: 184 gridname? = createBlockGraphics(white)value or expression? # variable definition185 fillRandomprocedureName?(gridarguments?) # procedure call186 +while Truecondition?: 187 displayBlocksprocedureName?(gridarguments?) # procedure call188 gridRefname? = AsRef[list[list[int]]](grid)value or expression? # variable definition189 nextGenerationprocedureName?(gridRefarguments?) # procedure call190 gridvariableName? = gridRef.value()value or expression? # assignment191 sleep_msprocedureName?(50arguments?) # procedure call192 # end while # end main
+static void main() { 193 var gridname? = createBlockGraphics(white)value or expression?;194 fillRandomprocedureName?(gridarguments?); // procedure call195 +while (truecondition?) { 196 displayBlocksprocedureName?(gridarguments?); // procedure call197 var gridRefname? = new AsRef<List<List<int>>>(grid)value or expression?;198 nextGenerationprocedureName?(gridRefarguments?); // procedure call199 gridvariableName? = gridRef.value()value or expression?; // assignment200 sleep_msprocedureName?(50arguments?); // procedure call201 } // end while } // end main
+Sub main() 202 Dim gridname? = createBlockGraphics(white)value or expression? ' variable definition203 fillRandomprocedureName?(gridarguments?) ' procedure call204 +While Truecondition? 205 displayBlocksprocedureName?(gridarguments?) ' procedure call206 Dim gridRefname? = New AsRef(Of List(Of List(Of Integer)))(grid)value or expression? ' variable definition207 nextGenerationprocedureName?(gridRefarguments?) ' procedure call208 gridvariableName? = gridRef.value()value or expression? ' assignment209 sleep_msprocedureName?(50arguments?) ' procedure call210 End While End Sub
+static void main() { 211 var gridname? = createBlockGraphics(white)value or expression?;212 fillRandomprocedureName?(gridarguments?); // procedure call213 +while (truecondition?) { 214 displayBlocksprocedureName?(gridarguments?); // procedure call215 var gridRefname? = new AsRef<List<List<int>>>(grid)value or expression?;216 nextGenerationprocedureName?(gridRefarguments?); // procedure call217 gridvariableName? = gridRef.value()value or expression?; // assignment218 sleep_msprocedureName?(50arguments?); // procedure call219 } // end while } // end main

What you need to know about a 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 Burrow example above). More commonly it will delegate to procedures and functions that you define.

Procedure

Examples of a procedure

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

+procedure moveGrowBurstname?(bubbles as List<of CircleVG>parameter definitions?) 220 +for bitem? in bubblessource? 221 +if random() < 0.05condition? then 222 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? call b.setRadiusprocedureName?(0arguments?)223 call b.setCentreYprocedureName?(75arguments?)224 else225 # bubble rises and grows slightlycomment? call b.setCentreYprocedureName?(b.centreY - 1arguments?)226 call b.setRadiusprocedureName?(b.radius + 0.2arguments?)227 end if end for call displayVectorGraphicsprocedureName?(bubblesarguments?)228 call sleep_msprocedureName?(5arguments?)229 end procedure
+def moveGrowBurstname?(bubbles: list[CircleVG]parameter definitions?) -> None: # procedure230 +for bitem? in bubblessource?: 231 +if random() < 0.05condition?: 232 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) # procedure call233 b.setCentreYprocedureName?(75arguments?) # procedure call234 else:235 # bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) # procedure call236 b.setRadiusprocedureName?(b.radius + 0.2arguments?) # procedure call237 # end if # end for displayVectorGraphicsprocedureName?(bubblesarguments?) # procedure call238 sleep_msprocedureName?(5arguments?) # procedure call239 # end procedure
+static void moveGrowBurstname?(List<CircleVG> bubblesparameter definitions?) { // procedure240 +foreach (var bitem? in bubblessource?) { 241 +if (random() < 0.05condition?) { 242 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // procedure call243 b.setCentreYprocedureName?(75arguments?); // procedure call244 } else {245 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // procedure call246 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // procedure call247 } // end if } // end foreach displayVectorGraphicsprocedureName?(bubblesarguments?); // procedure call248 sleep_msprocedureName?(5arguments?); // procedure call249 } // end procedure
+Sub moveGrowBurstname?(bubbles As List(Of CircleVG)parameter definitions?) ' procedure250 +For Each bitem? In bubblessource? 251 +If random() < 0.05condition? Then 252 ' 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) ' procedure call253 b.setCentreYprocedureName?(75arguments?) ' procedure call254 Else255 ' bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) ' procedure call256 b.setRadiusprocedureName?(b.radius + 0.2arguments?) ' procedure call257 End If Next b displayVectorGraphicsprocedureName?(bubblesarguments?) ' procedure call258 sleep_msprocedureName?(5arguments?) ' procedure call259 End Sub
+static void moveGrowBurstname?(List<CircleVG> bubblesparameter definitions?) { // procedure260 +foreach (var bitem? in bubblessource?) { 261 +if (random() < 0.05condition?) { 262 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // procedure call263 b.setCentreYprocedureName?(75arguments?); // procedure call264 } else {265 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // procedure call266 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // procedure call267 } // end if } // end foreach displayVectorGraphicsprocedureName?(bubblesarguments?); // procedure call268 sleep_msprocedureName?(5arguments?); // procedure call269 } // end procedure

It is called within the main routine by this instruction:

call moveGrowBurstprocedureName?(bubblesarguments?)0moveGrowBurstprocedureName?(bubblesarguments?) # procedure call1moveGrowBurstprocedureName?(bubblesarguments?); // procedure call2moveGrowBurstprocedureName?(bubblesarguments?) ' procedure call3moveGrowBurstprocedureName?(bubblesarguments?); // procedure call4

From the Turtle Dragon demo, this procedure defines two parameters of different types. The body of this procedure consists of a straight sequence of procedure calls, 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?) 270 call t.turnToHeadingprocedureName?(180 + order*45arguments?)271 call t.placeAtprocedureName?(-40, 20arguments?)272 call t.penColourprocedureName?(redarguments?)273 call t.penWidthprocedureName?(10.0/orderarguments?)274 call t.penDownprocedureName?(arguments?)275 call t.showprocedureName?(arguments?)276 end procedure
+def setupTurtlename?(t: Turtle, order: intparameter definitions?) -> None: # procedure277 t.turnToHeadingprocedureName?(180 + order*45arguments?) # procedure call278 t.placeAtprocedureName?(-40, 20arguments?) # procedure call279 t.penColourprocedureName?(redarguments?) # procedure call280 t.penWidthprocedureName?(10.0/orderarguments?) # procedure call281 t.penDownprocedureName?(arguments?) # procedure call282 t.showprocedureName?(arguments?) # procedure call283 # end procedure
+static void setupTurtlename?(Turtle t, int orderparameter definitions?) { // procedure284 t.turnToHeadingprocedureName?(180 + order*45arguments?); // procedure call285 t.placeAtprocedureName?(-40, 20arguments?); // procedure call286 t.penColourprocedureName?(redarguments?); // procedure call287 t.penWidthprocedureName?(10.0/orderarguments?); // procedure call288 t.penDownprocedureName?(arguments?); // procedure call289 t.showprocedureName?(arguments?); // procedure call290 } // end procedure
+Sub setupTurtlename?(t As Turtle, order As Integerparameter definitions?) ' procedure291 t.turnToHeadingprocedureName?(180 + order*45arguments?) ' procedure call292 t.placeAtprocedureName?(-40, 20arguments?) ' procedure call293 t.penColourprocedureName?(redarguments?) ' procedure call294 t.penWidthprocedureName?(10.0/orderarguments?) ' procedure call295 t.penDownprocedureName?(arguments?) ' procedure call296 t.showprocedureName?(arguments?) ' procedure call297 End Sub
+static void setupTurtlename?(Turtle t, int orderparameter definitions?) { // procedure298 t.turnToHeadingprocedureName?(180 + order*45arguments?); // procedure call299 t.placeAtprocedureName?(-40, 20arguments?); // procedure call300 t.penColourprocedureName?(redarguments?); // procedure call301 t.penWidthprocedureName?(10.0/orderarguments?); // procedure call302 t.penDownprocedureName?(arguments?); // procedure call303 t.showprocedureName?(arguments?); // procedure call304 } // end procedure

From the Life – procedural demo, this code sets each of the cells in the 40×30 gridgridgridgridgrid

+procedure fillRandomname?(grid as List<of List<of Int>>parameter definitions?) 305 +for colitem? in range(0, 40)source? 306 +for rowitem? in range(0, 30)source? 307 assign grid[col][row]variableName? to blackOrWhite(random())value or expression?308 end for end for end procedure
+def fillRandomname?(grid: list[list[int]]parameter definitions?) -> None: # procedure309 +for colitem? in range(0, 40)source?: 310 +for rowitem? in range(0, 30)source?: 311 grid[col][row]variableName? = blackOrWhite(random())value or expression? # assignment312 # end for # end for # end procedure
+static void fillRandomname?(List<List<int>> gridparameter definitions?) { // procedure313 +foreach (var colitem? in range(0, 40)source?) { 314 +foreach (var rowitem? in range(0, 30)source?) { 315 grid[col][row]variableName? = blackOrWhite(random())value or expression?; // assignment316 } // end foreach } // end foreach } // end procedure
+Sub fillRandomname?(grid As List(Of List(Of Integer))parameter definitions?) ' procedure317 +For Each colitem? In range(0, 40)source? 318 +For Each rowitem? In range(0, 30)source? 319 grid(col)(row)variableName? = blackOrWhite(random())value or expression? ' assignment320 Next row Next col End Sub
+static void fillRandomname?(List<List<int>> gridparameter definitions?) { // procedure321 +foreach (var colitem? in range(0, 40)source?) { 322 +foreach (var rowitem? in range(0, 30)source?) { 323 grid[col][row]variableName? = blackOrWhite(random())value or expression?; // assignment324 } // end foreach } // end foreach } // end procedure

What you need to know about a procedure

  • 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 (e.g. moveGrowBurst).
  • It may optionally define one or more parameters (one in moveGrowBurst, two in setupTurtle), 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 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.
  • The procedure is called from elsewhere using a procedure call, which specifies the name of the procedure to be called and includes values or expressions (known as arguments) that match the types of the parameters.
  • The procedure always finishes on the last instruction defined within it, at which point the program continues from where the call instruction is 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 argargargargarg 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 of type AsRefAsRefAsRefAsRefAsRef. This then allows use of the dot methods set and value on it to change and then read its value, as in this example where argRefargRefargRefargRefargRef is a pointer to variable argargargargarg in the main routine, so that argargargargarg can be accessed elsewhere by reference:

+main 325 variable argname? set to "abc"value or expression?326 variable argRefname? set to new AsRef<of String>(arg)value or expression?327 call changeArgprocedureName?(argRefarguments?)328 print(argRef.value()arguments?)329 end main
+def main() -> None: 330 argname? = "abc"value or expression? # variable definition331 argRefname? = AsRef[str](arg)value or expression? # variable definition332 changeArgprocedureName?(argRefarguments?) # procedure call333 print(argRef.value()arguments?)334 # end main
+static void main() { 335 var argname? = "abc"value or expression?;336 var argRefname? = new AsRef<string>(arg)value or expression?;337 changeArgprocedureName?(argRefarguments?); // procedure call338 Console.WriteLine(argRef.value()arguments?); // print statement339 } // end main
+Sub main() 340 Dim argname? = "abc"value or expression? ' variable definition341 Dim argRefname? = New AsRef(Of String)(arg)value or expression? ' variable definition342 changeArgprocedureName?(argRefarguments?) ' procedure call343 Console.WriteLine(argRef.value()arguments?) ' print statement344 End Sub
+static void main() { 345 var argname? = "abc"value or expression?;346 var argRefname? = new AsRef<String>(arg)value or expression?;347 changeArgprocedureName?(argRefarguments?); // procedure call348 System.out.println(argRef.value()arguments?); // print statement349 } // end main

Recursive procedure calls

A procedure may call itself, directly or indirectly (calling another procedure that then calls back to the first one). In 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:

  • Calls back into itself are with a smaller version of the problem – in this case one third of the lengthlengthlengthlengthlength that the procedure was given.
  • 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?) 350 +if (length > 1)condition? then 351 variable thirdname? set to length/3value or expression?352 call drawSideprocedureName?(third, targuments?)353 call t.turnprocedureName?(-60arguments?)354 call drawSideprocedureName?(third, targuments?)355 call t.turnprocedureName?(120arguments?)356 call drawSideprocedureName?(third, targuments?)357 call t.turnprocedureName?(-60arguments?)358 call drawSideprocedureName?(third, targuments?)359 else360 call t.moveprocedureName?(lengtharguments?)361 end if end procedure
+def drawSidename?(length: float, t: Turtleparameter definitions?) -> None: # procedure362 +if (length > 1)condition?: 363 thirdname? = length/3value or expression? # variable definition364 drawSideprocedureName?(third, targuments?) # procedure call365 t.turnprocedureName?(-60arguments?) # procedure call366 drawSideprocedureName?(third, targuments?) # procedure call367 t.turnprocedureName?(120arguments?) # procedure call368 drawSideprocedureName?(third, targuments?) # procedure call369 t.turnprocedureName?(-60arguments?) # procedure call370 drawSideprocedureName?(third, targuments?) # procedure call371 else:372 t.moveprocedureName?(lengtharguments?) # procedure call373 # end if # end procedure
+static void drawSidename?(double length, Turtle tparameter definitions?) { // procedure374 +if ((length > 1)condition?) { 375 var thirdname? = length/3value or expression?;376 drawSideprocedureName?(third, targuments?); // procedure call377 t.turnprocedureName?(-60arguments?); // procedure call378 drawSideprocedureName?(third, targuments?); // procedure call379 t.turnprocedureName?(120arguments?); // procedure call380 drawSideprocedureName?(third, targuments?); // procedure call381 t.turnprocedureName?(-60arguments?); // procedure call382 drawSideprocedureName?(third, targuments?); // procedure call383 } else {384 t.moveprocedureName?(lengtharguments?); // procedure call385 } // end if } // end procedure
+Sub drawSidename?(length As Double, t As Turtleparameter definitions?) ' procedure386 +If (length > 1)condition? Then 387 Dim thirdname? = length/3value or expression? ' variable definition388 drawSideprocedureName?(third, targuments?) ' procedure call389 t.turnprocedureName?(-60arguments?) ' procedure call390 drawSideprocedureName?(third, targuments?) ' procedure call391 t.turnprocedureName?(120arguments?) ' procedure call392 drawSideprocedureName?(third, targuments?) ' procedure call393 t.turnprocedureName?(-60arguments?) ' procedure call394 drawSideprocedureName?(third, targuments?) ' procedure call395 Else396 t.moveprocedureName?(lengtharguments?) ' procedure call397 End If End Sub
+static void drawSidename?(double length, Turtle tparameter definitions?) { // procedure398 +if ((length > 1)condition?) { 399 var thirdname? = length/3value or expression?;400 drawSideprocedureName?(third, targuments?); // procedure call401 t.turnprocedureName?(-60arguments?); // procedure call402 drawSideprocedureName?(third, targuments?); // procedure call403 t.turnprocedureName?(120arguments?); // procedure call404 drawSideprocedureName?(third, targuments?); // procedure call405 t.turnprocedureName?(-60arguments?); // procedure call406 drawSideprocedureName?(third, targuments?); // procedure call407 } else {408 t.moveprocedureName?(lengtharguments?); // procedure call409 } // end if } // end procedure

Function

Examples of a function

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? 410 return (headX < 0) or (headY < 0) or (headX > 39) or (headY > 29)value or expression?411 end function
+def hasHitEdgename?(headX: int, headY: intparameter definitions?) -> boolType?: # function412 return (headX < 0) or (headY < 0) or (headX > 39) or (headY > 29)value or expression?413 # end function
+static boolType? hasHitEdgename?(int headX, int headYparameter definitions?) { // function414 return (headX < 0) || (headY < 0) || (headX > 39) || (headY > 29)value or expression?;415 } // end function
+Function hasHitEdgename?(headX As Integer, headY As Integerparameter definitions?) As BooleanType? 416 Return (headX < 0) Or (headY < 0) Or (headX > 39) Or (headY > 29)value or expression?417 End Function
+static booleanType? hasHitEdgename?(int headX, int headYparameter definitions?) { // function418 return (headX < 0) || (headY < 0) || (headX > 39) || (headY > 29)value or expression?;419 } // end function

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

assign gameOnvariableName? to not hasHitEdge(head[0], head[1]) and not body.contains(head)value or expression?420
gameOnvariableName? = not hasHitEdge(head[0], head[1]) and not body.contains(head)value or expression? # assignment421
gameOnvariableName? = !hasHitEdge(head[0], head[1]) && !body.contains(head)value or expression?; // assignment422
gameOnvariableName? = Not hasHitEdge(head(0), head(1)) And Not body.contains(head)value or expression? ' assignment423
gameOnvariableName? = !hasHitEdge(head[0], head[1]) && !body.contains(head)value or expression?; // assignment424

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 items are of type IntintintIntegerint:

+function northname?(cell as (Int, Int)parameter definitions?) returns (Int, Int)Type? 435 variable xname? set to cell.item_0value or expression?436 variable yname? set to cell.item_1value or expression?437 variable y2name? set to y - 1value or expression?438 +if y2 is -1condition? then 439 assign y2variableName? to 29value or expression?440 end if return (x, y2)value or expression?441 end function
+def northname?(cell: tuple[int, int]parameter definitions?) -> tuple[int, int]Type?: # function442 xname? = cell.item_0value or expression? # variable definition443 yname? = cell.item_1value or expression? # variable definition444 y2name? = y - 1value or expression? # variable definition445 +if y2 == -1condition?: 446 y2variableName? = 29value or expression? # assignment447 # end if return (x, y2)value or expression?448 # end function
+static (int, int)Type? northname?((int, int) cellparameter definitions?) { // function449 var xname? = cell.item_0value or expression?;450 var yname? = cell.item_1value or expression?;451 var y2name? = y - 1value or expression?;452 +if (y2 == -1condition?) { 453 y2variableName? = 29value or expression?; // assignment454 } // end if return (x, y2)value or expression?;455 } // end function
+Function northname?(cell As (Integer, Integer)parameter definitions?) As (Integer, Integer)Type? 456 Dim xname? = cell.item_0value or expression? ' variable definition457 Dim yname? = cell.item_1value or expression? ' variable definition458 Dim y2name? = y - 1value or expression? ' variable definition459 +If y2 = -1condition? Then 460 y2variableName? = 29value or expression? ' assignment461 End If Return (x, y2)value or expression?462 End Function
+static (int, int)Type? northname?((int, int) cellparameter definitions?) { // function463 var xname? = cell.item_0value or expression?;464 var yname? = cell.item_1value or expression?;465 var y2name? = y - 1value or expression?;466 +if (y2 == -1condition?) { 467 y2variableName? = 29value or expression?; // assignment468 } // end if return (x, y2)value or expression?;469 } // end 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? 470 variable resultname? set to falsevalue or expression?471 +if li.length() > 0condition? then 472 variable midname? set to divAsInt(li.length(), 2)value or expression?473 variable valuename? set to li[mid]value or expression?474 +if item.equals(value)condition? then 475 assign resultvariableName? to truevalue or expression?476 elif item.isBefore(value)condition? then477 assign resultvariableName? to binarySearch(li.subList(0, mid), item)value or expression?478 else479 assign resultvariableName? to binarySearch(li.subList(mid + 1, li.length()), item)value or expression?480 end if end if return resultvalue or expression?481 end function
+def binarySearchname?(li: list[str], item: strparameter definitions?) -> boolType?: # function482 resultname? = Falsevalue or expression? # variable definition483 +if li.length() > 0condition?: 484 midname? = divAsInt(li.length(), 2)value or expression? # variable definition485 valuename? = li[mid]value or expression? # variable definition486 +if item.equals(value)condition?: 487 resultvariableName? = Truevalue or expression? # assignment488 elif item.isBefore(value)condition?: # else if489 resultvariableName? = binarySearch(li.subList(0, mid), item)value or expression? # assignment490 else:491 resultvariableName? = binarySearch(li.subList(mid + 1, li.length()), item)value or expression? # assignment492 # end if # end if return resultvalue or expression?493 # end function
+static boolType? binarySearchname?(List<string> li, string itemparameter definitions?) { // function494 var resultname? = falsevalue or expression?;495 +if (li.length() > 0condition?) { 496 var midname? = divAsInt(li.length(), 2)value or expression?;497 var valuename? = li[mid]value or expression?;498 +if (item.equals(value)condition?) { 499 resultvariableName? = truevalue or expression?; // assignment500 } else if (item.isBefore(value)condition?) {501 resultvariableName? = binarySearch(li.subList(0, mid), item)value or expression?; // assignment502 } else {503 resultvariableName? = binarySearch(li.subList(mid + 1, li.length()), item)value or expression?; // assignment504 } // end if } // end if return resultvalue or expression?;505 } // end function
+Function binarySearchname?(li As List(Of String), item As Stringparameter definitions?) As BooleanType? 506 Dim resultname? = Falsevalue or expression? ' variable definition507 +If li.length() > 0condition? Then 508 Dim midname? = divAsInt(li.length(), 2)value or expression? ' variable definition509 Dim valuename? = li(mid)value or expression? ' variable definition510 +If item.equals(value)condition? Then 511 resultvariableName? = Truevalue or expression? ' assignment512 ElseIf item.isBefore(value)condition? Then513 resultvariableName? = binarySearch(li.subList(0, mid), item)value or expression? ' assignment514 Else515 resultvariableName? = binarySearch(li.subList(mid + 1, li.length()), item)value or expression? ' assignment516 End If End If Return resultvalue or expression?517 End Function
+static booleanType? binarySearchname?(List<String> li, String itemparameter definitions?) { // function518 var resultname? = falsevalue or expression?;519 +if (li.length() > 0condition?) { 520 var midname? = divAsInt(li.length(), 2)value or expression?;521 var valuename? = li[mid]value or expression?;522 +if (item.equals(value)condition?) { 523 resultvariableName? = truevalue or expression?; // assignment524 } else if (item.isBefore(value)condition?) {525 resultvariableName? = binarySearch(li.subList(0, mid), item)value or expression?; // assignment526 } else {527 resultvariableName? = binarySearch(li.subList(mid + 1, li.length()), item)value or expression?; // assignment528 } // end if } // end if return resultvalue or expression?;529 } // end function

What you need to know about a function

  • A function defines a mechanism to transform input data (passed as parameters) into a single returned value that may be of any type including a data structure.
  • Because it generates a value, a function reference is a kind of expression – it may define a complete expression, or be 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 with a lower case letter, e.g. 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 in lower case (headXheadXheadXheadXheadX above) and a type (IntintintIntegerint above) using the syntax 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.
  • A function's first (definition) line – its signature – must also specify the type of value it will return (returnsreturnsreturnsreturnsreturns BooleanboolboolBooleanboolean above).
  • As with the parameters, 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 instruction 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 returnreturnreturnreturnreturn instruction – with the expression following returnreturnreturnreturnreturn doing all the work needed.
  • More generally, the returnreturnreturnreturnreturn instruction will be preceded by other instructions that 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 (i.e. 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.

More advanced techniques

Recursive function references

A function may reference itself, directly or indirectly (referencing another function that then calls back to the first one).

The same two principles discussed above under Recursive procedure calls apply equally to recursive functions.

In this example of factorial calculation, each call is on a smaller value of input, and the process stops when nnnnn reaches 1:

+function factorialname?(n as Intparameter definitions?) returns IntType? 530 return (if(n > 1, n*factorial(n - 1), 1))value or expression?531 end function
+def factorialname?(n: intparameter definitions?) -> intType?: # function532 return (if(n > 1, n*factorial(n - 1), 1))value or expression?533 # end function
+static intType? factorialname?(int nparameter definitions?) { // function534 return (if(n > 1, n*factorial(n - 1), 1))value or expression?;535 } // end function
+Function factorialname?(n As Integerparameter definitions?) As IntegerType? 536 Return (if(n > 1, n*factorial(n - 1), 1))value or expression?537 End Function
+static intType? factorialname?(int nparameter definitions?) { // function538 return (if(n > 1, n*factorial(n - 1), 1))value or expression?;539 } // end function

Test

Examples of a 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? 565 assert willLive(white, 0)actual (computed) value? is falseexpected value? not run566 assert willLive(white, 1)actual (computed) value? is falseexpected value? not run567 assert willLive(white, 2)actual (computed) value? is falseexpected value? not run568 assert willLive(white, 3)actual (computed) value? is trueexpected value? not run569 assert willLive(white, 4)actual (computed) value? is falseexpected value? not run570 assert willLive(white, 5)actual (computed) value? is falseexpected value? not run571 assert willLive(white, 6)actual (computed) value? is falseexpected value? not run572 assert willLive(white, 7)actual (computed) value? is falseexpected value? not run573 assert willLive(white, 8)actual (computed) value? is falseexpected value? not run574 assert willLive(black, 0)actual (computed) value? is falseexpected value? not run575 assert willLive(black, 1)actual (computed) value? is falseexpected value? not run576 assert willLive(black, 2)actual (computed) value? is trueexpected value? not run577 assert willLive(black, 3)actual (computed) value? is trueexpected value? not run578 assert willLive(black, 4)actual (computed) value? is falseexpected value? not run579 assert willLive(black, 5)actual (computed) value? is falseexpected value? not run580 assert willLive(black, 6)actual (computed) value? is falseexpected value? not run581 assert willLive(black, 7)actual (computed) value? is falseexpected value? not run582 assert willLive(black, 8)actual (computed) value? is falseexpected value? not run583 end test
+class Test_willLive(unittest.TestCase):
 def test_willLivetest_name?(self) -> None: 584
self.assertEqual(willLive(white, 0)actual (computed) value?, Falseexpected value?) not run585 self.assertEqual(willLive(white, 1)actual (computed) value?, Falseexpected value?) not run586 self.assertEqual(willLive(white, 2)actual (computed) value?, Falseexpected value?) not run587 self.assertEqual(willLive(white, 3)actual (computed) value?, Trueexpected value?) not run588 self.assertEqual(willLive(white, 4)actual (computed) value?, Falseexpected value?) not run589 self.assertEqual(willLive(white, 5)actual (computed) value?, Falseexpected value?) not run590 self.assertEqual(willLive(white, 6)actual (computed) value?, Falseexpected value?) not run591 self.assertEqual(willLive(white, 7)actual (computed) value?, Falseexpected value?) not run592 self.assertEqual(willLive(white, 8)actual (computed) value?, Falseexpected value?) not run593 self.assertEqual(willLive(black, 0)actual (computed) value?, Falseexpected value?) not run594 self.assertEqual(willLive(black, 1)actual (computed) value?, Falseexpected value?) not run595 self.assertEqual(willLive(black, 2)actual (computed) value?, Trueexpected value?) not run596 self.assertEqual(willLive(black, 3)actual (computed) value?, Trueexpected value?) not run597 self.assertEqual(willLive(black, 4)actual (computed) value?, Falseexpected value?) not run598 self.assertEqual(willLive(black, 5)actual (computed) value?, Falseexpected value?) not run599 self.assertEqual(willLive(black, 6)actual (computed) value?, Falseexpected value?) not run600 self.assertEqual(willLive(black, 7)actual (computed) value?, Falseexpected value?) not run601 self.assertEqual(willLive(black, 8)actual (computed) value?, Falseexpected value?) not run602 # end test
+[TestClass] class Test_willLive
[TestMethod] static void test_willLivetest_name?() { 603
Assert.AreEqual(falseexpected value?, willLive(white, 0)actual (computed) value?); not run604 Assert.AreEqual(falseexpected value?, willLive(white, 1)actual (computed) value?); not run605 Assert.AreEqual(falseexpected value?, willLive(white, 2)actual (computed) value?); not run606 Assert.AreEqual(trueexpected value?, willLive(white, 3)actual (computed) value?); not run607 Assert.AreEqual(falseexpected value?, willLive(white, 4)actual (computed) value?); not run608 Assert.AreEqual(falseexpected value?, willLive(white, 5)actual (computed) value?); not run609 Assert.AreEqual(falseexpected value?, willLive(white, 6)actual (computed) value?); not run610 Assert.AreEqual(falseexpected value?, willLive(white, 7)actual (computed) value?); not run611 Assert.AreEqual(falseexpected value?, willLive(white, 8)actual (computed) value?); not run612 Assert.AreEqual(falseexpected value?, willLive(black, 0)actual (computed) value?); not run613 Assert.AreEqual(falseexpected value?, willLive(black, 1)actual (computed) value?); not run614 Assert.AreEqual(trueexpected value?, willLive(black, 2)actual (computed) value?); not run615 Assert.AreEqual(trueexpected value?, willLive(black, 3)actual (computed) value?); not run616 Assert.AreEqual(falseexpected value?, willLive(black, 4)actual (computed) value?); not run617 Assert.AreEqual(falseexpected value?, willLive(black, 5)actual (computed) value?); not run618 Assert.AreEqual(falseexpected value?, willLive(black, 6)actual (computed) value?); not run619 Assert.AreEqual(falseexpected value?, willLive(black, 7)actual (computed) value?); not run620 Assert.AreEqual(falseexpected value?, willLive(black, 8)actual (computed) value?); not run621 }} // end test
+<TestClass Class Test_willLive
 <TestMethod> Sub test_willLivetest_name?() 622
Assert.AreEqual(Falseexpected value?, willLive(white, 0)actual (computed) value?) not run623 Assert.AreEqual(Falseexpected value?, willLive(white, 1)actual (computed) value?) not run624 Assert.AreEqual(Falseexpected value?, willLive(white, 2)actual (computed) value?) not run625 Assert.AreEqual(Trueexpected value?, willLive(white, 3)actual (computed) value?) not run626 Assert.AreEqual(Falseexpected value?, willLive(white, 4)actual (computed) value?) not run627 Assert.AreEqual(Falseexpected value?, willLive(white, 5)actual (computed) value?) not run628 Assert.AreEqual(Falseexpected value?, willLive(white, 6)actual (computed) value?) not run629 Assert.AreEqual(Falseexpected value?, willLive(white, 7)actual (computed) value?) not run630 Assert.AreEqual(Falseexpected value?, willLive(white, 8)actual (computed) value?) not run631 Assert.AreEqual(Falseexpected value?, willLive(black, 0)actual (computed) value?) not run632 Assert.AreEqual(Falseexpected value?, willLive(black, 1)actual (computed) value?) not run633 Assert.AreEqual(Trueexpected value?, willLive(black, 2)actual (computed) value?) not run634 Assert.AreEqual(Trueexpected value?, willLive(black, 3)actual (computed) value?) not run635 Assert.AreEqual(Falseexpected value?, willLive(black, 4)actual (computed) value?) not run636 Assert.AreEqual(Falseexpected value?, willLive(black, 5)actual (computed) value?) not run637 Assert.AreEqual(Falseexpected value?, willLive(black, 6)actual (computed) value?) not run638 Assert.AreEqual(Falseexpected value?, willLive(black, 7)actual (computed) value?) not run639 Assert.AreEqual(Falseexpected value?, willLive(black, 8)actual (computed) value?) not run640  End Sub
End Class
+class Test_willLive {
@Test static void test_willLivetest_name?() { 641
assertEquals(falseexpected value?, willLive(white, 0)actual (computed) value?); not run642 assertEquals(falseexpected value?, willLive(white, 1)actual (computed) value?); not run643 assertEquals(falseexpected value?, willLive(white, 2)actual (computed) value?); not run644 assertEquals(trueexpected value?, willLive(white, 3)actual (computed) value?); not run645 assertEquals(falseexpected value?, willLive(white, 4)actual (computed) value?); not run646 assertEquals(falseexpected value?, willLive(white, 5)actual (computed) value?); not run647 assertEquals(falseexpected value?, willLive(white, 6)actual (computed) value?); not run648 assertEquals(falseexpected value?, willLive(white, 7)actual (computed) value?); not run649 assertEquals(falseexpected value?, willLive(white, 8)actual (computed) value?); not run650 assertEquals(falseexpected value?, willLive(black, 0)actual (computed) value?); not run651 assertEquals(falseexpected value?, willLive(black, 1)actual (computed) value?); not run652 assertEquals(trueexpected value?, willLive(black, 2)actual (computed) value?); not run653 assertEquals(trueexpected value?, willLive(black, 3)actual (computed) value?); not run654 assertEquals(falseexpected value?, willLive(black, 4)actual (computed) value?); not run655 assertEquals(falseexpected value?, willLive(black, 5)actual (computed) value?); not run656 assertEquals(falseexpected value?, willLive(black, 6)actual (computed) value?); not run657 assertEquals(falseexpected value?, willLive(black, 7)actual (computed) value?); not run658 assertEquals(falseexpected value?, willLive(black, 8)actual (computed) value?); not run659 }} // end test

Example from the Snake – procedural demo, showing the use of a variable to create data to be used within multiple assertions:

+test test_getAdjacentSquaretest_name? 660 variable sqname? set to [20, 15]value or expression?661 assert getAdjacentSquare(sq, Direction.up)actual (computed) value? is [20, 14]expected value? not run662 assert getAdjacentSquare(sq, Direction.down)actual (computed) value? is [20, 16]expected value? not run663 assert getAdjacentSquare(sq, Direction.left)actual (computed) value? is [19, 15]expected value? not run664 assert getAdjacentSquare(sq, Direction.right)actual (computed) value? is [21, 15]expected value? not run665 # boundarycomment? assert getAdjacentSquare([0, 15], Direction.left)actual (computed) value? is [-1, 15]expected value? not run666 end test
+class Test_getAdjacentSquare(unittest.TestCase):
 def test_getAdjacentSquaretest_name?(self) -> None: 667
sqname? = [20, 15]value or expression? # variable definition668 self.assertEqual(getAdjacentSquare(sq, Direction.up)actual (computed) value?, [20, 14]expected value?) not run669 self.assertEqual(getAdjacentSquare(sq, Direction.down)actual (computed) value?, [20, 16]expected value?) not run670 self.assertEqual(getAdjacentSquare(sq, Direction.left)actual (computed) value?, [19, 15]expected value?) not run671 self.assertEqual(getAdjacentSquare(sq, Direction.right)actual (computed) value?, [21, 15]expected value?) not run672 # boundarycomment? self.assertEqual(getAdjacentSquare([0, 15], Direction.left)actual (computed) value?, [-1, 15]expected value?) not run673 # end test
+[TestClass] class Test_getAdjacentSquare
[TestMethod] static void test_getAdjacentSquaretest_name?() { 674
var sqname? = new [] {20, 15}value or expression?;675 Assert.AreEqual(new [] {20, 14}expected value?, getAdjacentSquare(sq, Direction.up)actual (computed) value?); not run676 Assert.AreEqual(new [] {20, 16}expected value?, getAdjacentSquare(sq, Direction.down)actual (computed) value?); not run677 Assert.AreEqual(new [] {19, 15}expected value?, getAdjacentSquare(sq, Direction.left)actual (computed) value?); not run678 Assert.AreEqual(new [] {21, 15}expected value?, getAdjacentSquare(sq, Direction.right)actual (computed) value?); not run679 // boundarycomment? Assert.AreEqual(new [] {-1, 15}expected value?, getAdjacentSquare(new [] {0, 15}, Direction.left)actual (computed) value?); not run680 }} // end test
+<TestClass Class Test_getAdjacentSquare
 <TestMethod> Sub test_getAdjacentSquaretest_name?() 681
Dim sqname? = {20, 15}value or expression? ' variable definition682 Assert.AreEqual({20, 14}expected value?, getAdjacentSquare(sq, Direction.up)actual (computed) value?) not run683 Assert.AreEqual({20, 16}expected value?, getAdjacentSquare(sq, Direction.down)actual (computed) value?) not run684 Assert.AreEqual({19, 15}expected value?, getAdjacentSquare(sq, Direction.left)actual (computed) value?) not run685 Assert.AreEqual({21, 15}expected value?, getAdjacentSquare(sq, Direction.right)actual (computed) value?) not run686 ' boundarycomment? Assert.AreEqual({-1, 15}expected value?, getAdjacentSquare({0, 15}, Direction.left)actual (computed) value?) not run687  End Sub
End Class
+class Test_getAdjacentSquare {
@Test static void test_getAdjacentSquaretest_name?() { 688
var sqname? = list(20, 15)value or expression?;689 assertEquals(list(20, 14)expected value?, getAdjacentSquare(sq, Direction.up)actual (computed) value?); not run690 assertEquals(list(20, 16)expected value?, getAdjacentSquare(sq, Direction.down)actual (computed) value?); not run691 assertEquals(list(19, 15)expected value?, getAdjacentSquare(sq, Direction.left)actual (computed) value?); not run692 assertEquals(list(21, 15)expected value?, getAdjacentSquare(sq, Direction.right)actual (computed) value?); not run693 // boundarycomment? assertEquals(list(-1, 15)expected value?, getAdjacentSquare(list(0, 15), Direction.left)actual (computed) value?); not run694 }} // end test

What you need to know about a test

  • 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 is 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 restricting them to functions.
  • A test method must be given a name that starts with the prefix `test_` (automatically supplied) 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 could write a test adjacent to the function being tested, or group all the tests at the end of the file.
  • A test method may define variables for use in the test – for example to define a data value that will be used in several assertassertassertassertassert instructions.
  • A test method must include at least one assertassertassertassertassert instruction – with each one typically covering a different use case.
  • Each assertassertassertassertassert instruction must have two fields:
    1. the actual (i.e. computed) value – which usually consists of a call to the function being tested, with specific arguments,
    2. 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 is shown alongside each assertassertassertassertassert 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: they confirm that your function implementations are correct, not only when you write them, but every time you reload the program from file or make changes.
  • Tests also encourage you to 're-factor', improve, or extend the capability of your functions without fear that you inadvertently make then incorrect for certain cases.

More advanced techniques

Ghosting tests

To prevent a particular test from running, you can use ghostingghostingghostingghostingghosting .

Even when a test or assert statement is ghostedghostedghostedghostedghosted, all the tests will be run and their status shown, but the overall test status will show the status only 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
+class Test_overApple(unittest.TestCase):
 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 # end test
+[TestClass] class Test_overApple
[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 }} // end test
+<TestClass Class Test_overApple
 <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
End Class
+class Test_overApple {
@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 }} // end test

or you can ghost individual asserts:

+test test_overAppletest_name? 695 variable g1name? set to new Game(new Random())value or expression?696 variable g2name? set to g1.withApple(new Square(23, 15))value or expression?697 assert headOverApple(g2)actual (computed) value? is trueexpected value? not run variable g3name? set to g2.withHead(new Square(23, 15))value or expression?698 assert headOverApple(g3)actual (computed) value? is trueexpected value? not run699 end test
+class Test_overApple(unittest.TestCase):
 def test_overAppletest_name?(self) -> None: 700
g1name? = Game(Random())value or expression? # variable definition701 g2name? = g1.withApple(Square(23, 15))value or expression? # variable definition702 self.assertEqual(headOverApple(g2)actual (computed) value?, Trueexpected value?) not run g3name? = g2.withHead(Square(23, 15))value or expression? # variable definition703 self.assertEqual(headOverApple(g3)actual (computed) value?, Trueexpected value?) not run704 # end test
+[TestClass] class Test_overApple
[TestMethod] static void test_overAppletest_name?() { 705
var g1name? = new Game(new Random())value or expression?;706 var g2name? = g1.withApple(new Square(23, 15))value or expression?;707 Assert.AreEqual(trueexpected value?, headOverApple(g2)actual (computed) value?); not run var g3name? = g2.withHead(new Square(23, 15))value or expression?;708 Assert.AreEqual(trueexpected value?, headOverApple(g3)actual (computed) value?); not run709 }} // end test
+<TestClass Class Test_overApple
 <TestMethod> Sub test_overAppletest_name?() 710
Dim g1name? = New Game(New Random())value or expression? ' variable definition711 Dim g2name? = g1.withApple(New Square(23, 15))value or expression? ' variable definition712 Assert.AreEqual(Trueexpected value?, headOverApple(g2)actual (computed) value?) not run Dim g3name? = g2.withHead(New Square(23, 15))value or expression? ' variable definition713 Assert.AreEqual(Trueexpected value?, headOverApple(g3)actual (computed) value?) not run714  End Sub
End Class
+class Test_overApple {
@Test static void test_overAppletest_name?() { 715
var g1name? = new Game(new Random())value or expression?;716 var g2name? = g1.withApple(new Square(23, 15))value or expression?;717 assertEquals(trueexpected value?, headOverApple(g2)actual (computed) value?); not run var g3name? = g2.withHead(new Square(23, 15))value or expression?;718 assertEquals(trueexpected value?, headOverApple(g3)actual (computed) value?); not run719 }} // end test

Testing FloatfloatdoubleDoubledouble values

When testing FloatfloatdoubleDoubledouble values it is recommend that you use method round 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? 720 assert sqrt(2).round(3)actual (computed) value? is 1.414expected value? not run721 end test
+class Test_round(unittest.TestCase):
 def test_roundtest_name?(self) -> None: 722
self.assertEqual(sqrt(2).round(3)actual (computed) value?, 1.414expected value?) not run723 # end test
+[TestClass] class Test_round
[TestMethod] static void test_roundtest_name?() { 724
Assert.AreEqual(1.414expected value?, sqrt(2).round(3)actual (computed) value?); not run725 }} // end test
+<TestClass Class Test_round
 <TestMethod> Sub test_roundtest_name?() 726
Assert.AreEqual(1.414expected value?, sqrt(2).round(3)actual (computed) value?) not run727  End Sub
End Class
+class Test_round {
@Test static void test_roundtest_name?() { 728
assertEquals(1.414expected value?, sqrt(2).round(3)actual (computed) value?); not run729 }} // end test

Testing for runtime errors

If the expression you are testing causes 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.

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

+test test_messagetest_name? 730 variable aname? set to [5, 1, 7]value or expression?731 assert a[4]actual (computed) value? is 0expected value? not run732 assert a[4]actual (computed) value? is "Out of range index: 4 size: 3"expected value? not run733 end test
+class Test_message(unittest.TestCase):
 def test_messagetest_name?(self) -> None: 734
aname? = [5, 1, 7]value or expression? # variable definition735 self.assertEqual(a[4]actual (computed) value?, 0expected value?) not run736 self.assertEqual(a[4]actual (computed) value?, "Out of range index: 4 size: 3"expected value?) not run737 # end test
+[TestClass] class Test_message
[TestMethod] static void test_messagetest_name?() { 738
var aname? = new [] {5, 1, 7}value or expression?;739 Assert.AreEqual(0expected value?, a[4]actual (computed) value?); not run740 Assert.AreEqual("Out of range index: 4 size: 3"expected value?, a[4]actual (computed) value?); not run741 }} // end test
+<TestClass Class Test_message
 <TestMethod> Sub test_messagetest_name?() 742
Dim aname? = {5, 1, 7}value or expression? ' variable definition743 Assert.AreEqual(0expected value?, a(4)actual (computed) value?) not run744 Assert.AreEqual("Out of range index: 4 size: 3"expected value?, a(4)actual (computed) value?) not run745  End Sub
End Class
+class Test_message {
@Test static void test_messagetest_name?() { 746
var aname? = list(5, 1, 7)value or expression?;747 assertEquals(0expected value?, a[4]actual (computed) value?); not run748 assertEquals("Out of range index: 4 size: 3"expected value?, a[4]actual (computed) value?); not run749 }} // end 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?750
+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? # constant751
+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?;752
+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?753
+static 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?; // constant754

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 test.

Constant

Examples of a constant

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

+constant sidename? set to 100literal value or data structure?755
+sidename? = 100literal value or data structure? # constant756
+const Int sidename? = 100literal value or data structure?;757
+Const sidename? = 100literal value or data structure?758
+static final Int sidename? = 100literal value or data structure?; // constant759

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

call drawSideprocedureName?(side, targuments?)760
drawSideprocedureName?(side, targuments?) # procedure call761
drawSideprocedureName?(side, targuments?); // procedure call762
drawSideprocedureName?(side, targuments?) ' procedure call763
drawSideprocedureName?(side, targuments?); // procedure call764

Here are some other examples of globally fixed values that can be referred to from anywhere in your program:

+constant maxHitsname? set to 10literal value or data structure?765
+maxHitsname? = 10literal value or data structure? # constant766
+const Int maxHitsname? = 10literal value or data structure?;767
+Const maxHitsname? = 10literal value or data structure?768
+static final Int maxHitsname? = 10literal value or data structure?; // constant769

What you need to know about a constant

  • 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 constants.
  • 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 assignment 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 literal value, which must be a number, string, or Boolean (i.e. true or 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.

More advanced techniques

Constant data structures

To set up a data structure that is to be a constant in your program, you define it as the value returned by a function that requires no parameters. Such a function can then be referenced from anywhere in the program, often from within an expression, remembering to add empty brackets after its name.

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 using system method createDectionary to set up the dictionary from a list of 2-tuples. In this second case, integer ouput is converted to a string of hexadecimal before printing by using example function hex:

+main 770 print($"{rainbow()[0]} (dec) is violet"arguments?)771 print($"{hex(suitColours()["hearts"])} (hex) is red"arguments?)772 end main
+def main() -> None: 773 print(f"{rainbow()[0]} (dec) is violet"arguments?)774 print(f"{hex(suitColours()["hearts"])} (hex) is red"arguments?)775 # end main
+static void main() { 776 Console.WriteLine($"{rainbow()[0]} (dec) is violet"arguments?); // print statement777 Console.WriteLine($"{hex(suitColours()["hearts"])} (hex) is red"arguments?); // print statement778 } // end main
+Sub main() 779 Console.WriteLine($"{rainbow()(0)} (dec) is violet"arguments?) ' print statement780 Console.WriteLine($"{hex(suitColours()("hearts"))} (hex) is red"arguments?) ' print statement781 End Sub
+static void main() { 782 System.out.println(String.format("% (dec) is violet", rainbow()[0])arguments?); // print statement783 System.out.println(String.format("% (hex) is red", hex(suitColours()["hearts"]))arguments?); // print statement784 } // end main

Enum

Examples of an enum

From the Snake – procedural demo, this enumenumenumenumenum is defined:

enum DirectionName? up, down, left, rightvalues?values?785
class DirectionName?(Enum):
up = 1
down = 2
left = 3
right = 4
values?
786
enum DirectionName? {up, down, left, rightvalues?values?}787
Enum DirectionName?
up = 0
down = 1
left = 2
right = 3
End Enum
values?
788
enum DirectionName? {up, down, left, rightvalues?values?}789

It 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? 790 variable newXname? set to sq[0]value or expression?791 variable newYname? set to sq[1]value or expression?792 +if dir is Direction.leftcondition? then 793 assign newXvariableName? to newX - 1value or expression?794 elif dir is Direction.rightcondition? then795 assign newXvariableName? to newX + 1value or expression?796 elif dir is Direction.upcondition? then797 assign newYvariableName? to newY - 1value or expression?798 elif dir is Direction.downcondition? then799 assign newYvariableName? to newY + 1value or expression?800 end if return [newX, newY]value or expression?801 end function
+def getAdjacentSquarename?(sq: list[int], dir: Directionparameter definitions?) -> list[int]Type?: # function802 newXname? = sq[0]value or expression? # variable definition803 newYname? = sq[1]value or expression? # variable definition804 +if dir == Direction.leftcondition?: 805 newXvariableName? = newX - 1value or expression? # assignment806 elif dir == Direction.rightcondition?: # else if807 newXvariableName? = newX + 1value or expression? # assignment808 elif dir == Direction.upcondition?: # else if809 newYvariableName? = newY - 1value or expression? # assignment810 elif dir == Direction.downcondition?: # else if811 newYvariableName? = newY + 1value or expression? # assignment812 # end if return [newX, newY]value or expression?813 # end function
+static List<int>Type? getAdjacentSquarename?(List<int> sq, Direction dirparameter definitions?) { // function814 var newXname? = sq[0]value or expression?;815 var newYname? = sq[1]value or expression?;816 +if (dir == Direction.leftcondition?) { 817 newXvariableName? = newX - 1value or expression?; // assignment818 } else if (dir == Direction.rightcondition?) {819 newXvariableName? = newX + 1value or expression?; // assignment820 } else if (dir == Direction.upcondition?) {821 newYvariableName? = newY - 1value or expression?; // assignment822 } else if (dir == Direction.downcondition?) {823 newYvariableName? = newY + 1value or expression?; // assignment824 } // end if return new [] {newX, newY}value or expression?;825 } // end function
+Function getAdjacentSquarename?(sq As List(Of Integer), dir As Directionparameter definitions?) As List(Of Integer)Type? 826 Dim newXname? = sq(0)value or expression? ' variable definition827 Dim newYname? = sq(1)value or expression? ' variable definition828 +If dir = Direction.leftcondition? Then 829 newXvariableName? = newX - 1value or expression? ' assignment830 ElseIf dir = Direction.rightcondition? Then831 newXvariableName? = newX + 1value or expression? ' assignment832 ElseIf dir = Direction.upcondition? Then833 newYvariableName? = newY - 1value or expression? ' assignment834 ElseIf dir = Direction.downcondition? Then835 newYvariableName? = newY + 1value or expression? ' assignment836 End If Return {newX, newY}value or expression?837 End Function
+static List<int>Type? getAdjacentSquarename?(List<int> sq, Direction dirparameter definitions?) { // function838 var newXname? = sq[0]value or expression?;839 var newYname? = sq[1]value or expression?;840 +if (dir == Direction.leftcondition?) { 841 newXvariableName? = newX - 1value or expression?; // assignment842 } else if (dir == Direction.rightcondition?) {843 newXvariableName? = newX + 1value or expression?; // assignment844 } else if (dir == Direction.upcondition?) {845 newYvariableName? = newY - 1value or expression?; // assignment846 } else if (dir == Direction.downcondition?) {847 newYvariableName? = newY + 1value or expression?; // assignment848 } // end if return list(newX, newY)value or expression?;849 } // end function

From the Blackjack demo:

enum SuitName? spades, hearts, diamonds, clubsvalues?values?850
class SuitName?(Enum):
spades = 1
hearts = 2
diamonds = 3
clubs = 4
values?
851
enum SuitName? {spades, hearts, diamonds, clubsvalues?values?}852
Enum SuitName?
spades = 0
hearts = 1
diamonds = 2
clubs = 3
End Enum
values?
853
enum SuitName? {spades, hearts, diamonds, clubsvalues?values?}854

What you need to know about an enum

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

  • Because it is a type, the name given to the enum (e.g. DirectionDirectionDirectionDirectionDirection above) must start with an upper case letter.
  • An enum forces any named value of that type to be one of the fixed number of named values listed in the enum definition.
  • The instruction
    variable currentDirname? set to Direction.rightvalue or expression?855
    currentDirname? = Direction.rightvalue or expression? # variable definition856
    var currentDirname? = Direction.rightvalue or expression?;857
    Dim currentDirname? = Direction.rightvalue or expression? ' variable definition858
    var currentDirname? = Direction.rightvalue or expression?;859
    initialises currentDircurrentDircurrentDircurrentDircurrentDir with the value rightrightrightrightright which is valid only because it is listed in the definition of the enum named DirectionDirectionDirectionDirectionDirection.
  • Because the variable currentDirectcurrentDirectcurrentDirectcurrentDirectcurrentDirect has the type DirectionDirectionDirectionDirectionDirection, it may only be assigned to another of the values defined in enum DirectionDirectionDirectionDirectionDirection
  • An enum value cannot be converted to anything else.
  • The advantages of using enums are:
    • readability,
    • safety, because if the four directions were defined as strings, e.g. "right""right""right""right""right" a mis-spelling might not be picked up until runtime. Whereas with the enum, writing Direction.rihgtDirection.rihgtDirection.rihgtDirection.rihgtDirection.rihgt would raise an error at compilation.

Comment

Examples of a comment

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?
// 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?) 860 +for bitem? in bubblessource? 861 +if random() < 0.05condition? then 862 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? call b.setRadiusprocedureName?(0arguments?)863 call b.setCentreYprocedureName?(75arguments?)864 else865 # bubble rises and grows slightlycomment? call b.setCentreYprocedureName?(b.centreY - 1arguments?)866 call b.setRadiusprocedureName?(b.radius + 0.2arguments?)867 end if end for call displayVectorGraphicsprocedureName?(bubblesarguments?)868 call sleep_msprocedureName?(5arguments?)869 end procedure
+def moveGrowBurstname?(bubbles: list[CircleVG]parameter definitions?) -> None: # procedure870 +for bitem? in bubblessource?: 871 +if random() < 0.05condition?: 872 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) # procedure call873 b.setCentreYprocedureName?(75arguments?) # procedure call874 else:875 # bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) # procedure call876 b.setRadiusprocedureName?(b.radius + 0.2arguments?) # procedure call877 # end if # end for displayVectorGraphicsprocedureName?(bubblesarguments?) # procedure call878 sleep_msprocedureName?(5arguments?) # procedure call879 # end procedure
+static void moveGrowBurstname?(List<CircleVG> bubblesparameter definitions?) { // procedure880 +foreach (var bitem? in bubblessource?) { 881 +if (random() < 0.05condition?) { 882 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // procedure call883 b.setCentreYprocedureName?(75arguments?); // procedure call884 } else {885 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // procedure call886 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // procedure call887 } // end if } // end foreach displayVectorGraphicsprocedureName?(bubblesarguments?); // procedure call888 sleep_msprocedureName?(5arguments?); // procedure call889 } // end procedure
+Sub moveGrowBurstname?(bubbles As List(Of CircleVG)parameter definitions?) ' procedure890 +For Each bitem? In bubblessource? 891 +If random() < 0.05condition? Then 892 ' 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) ' procedure call893 b.setCentreYprocedureName?(75arguments?) ' procedure call894 Else895 ' bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) ' procedure call896 b.setRadiusprocedureName?(b.radius + 0.2arguments?) ' procedure call897 End If Next b displayVectorGraphicsprocedureName?(bubblesarguments?) ' procedure call898 sleep_msprocedureName?(5arguments?) ' procedure call899 End Sub
+static void moveGrowBurstname?(List<CircleVG> bubblesparameter definitions?) { // procedure900 +foreach (var bitem? in bubblessource?) { 901 +if (random() < 0.05condition?) { 902 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // procedure call903 b.setCentreYprocedureName?(75arguments?); // procedure call904 } else {905 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // procedure call906 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // procedure call907 } // end if } // end foreach displayVectorGraphicsprocedureName?(bubblesarguments?); // procedure call908 sleep_msprocedureName?(5arguments?); // procedure call909 } // end procedure

What you need to know about a comment

  • 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 is always on a line of its own.
  • Comments that you see written on the same line as and 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 you. 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.

Print

Examples of a print statement

From the Date Time demo, printing the time (as a number of seconds) and then the date by calling the function getDate inline within the print instruction:

+main 910 variable replyname? set to ""value or expression?911 +while not reply.upperCase().equals("Q")condition? 912 assign replyvariableName? to input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression?913 +if reply.equals("")condition? then 914 variable nowname? set to divAsInt(clock(), 1000)value or expression?915 print(nowarguments?)916 print(getDate(now)arguments?)917 else918 +try 919 variable tdname? set to int(reply)value or expression?920 +if td >= 0condition? then 921 print(getDate(td)arguments?)922 end if catch evariableName? as ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError?923 end try end if end while end main
+def main() -> None: 924 replyname? = ""value or expression? # variable definition925 +while not reply.upperCase().equals("Q")condition?: 926 replyvariableName? = input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression? # assignment927 +if reply.equals("")condition?: 928 nowname? = divAsInt(clock(), 1000)value or expression? # variable definition929 print(nowarguments?)930 print(getDate(now)arguments?)931 else:932 +try: 933 tdname? = int(reply)value or expression? # variable definition934 +if td >= 0condition?: 935 print(getDate(td)arguments?)936 # end if except ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError? as evariableName?: # catch937 # end try # end if # end while # end main
+static void main() { 938 var replyname? = ""value or expression?;939 +while (!reply.upperCase().equals("Q")condition?) { 940 replyvariableName? = input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression?; // assignment941 +if (reply.equals("")condition?) { 942 var nowname? = divAsInt(clock(), 1000)value or expression?;943 Console.WriteLine(nowarguments?); // print statement944 Console.WriteLine(getDate(now)arguments?); // print statement945 } else {946 +try { 947 var tdname? = int(reply)value or expression?;948 +if (td >= 0condition?) { 949 Console.WriteLine(getDate(td)arguments?); // print statement950 } // end if } catch (ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError? evariableName?) {951 } // end try } // end if } // end while } // end main
+Sub main() 952 Dim replyname? = ""value or expression? ' variable definition953 +While Not reply.upperCase().equals("Q")condition? 954 replyvariableName? = input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression? ' assignment955 +If reply.equals("")condition? Then 956 Dim nowname? = divAsInt(clock(), 1000)value or expression? ' variable definition957 Console.WriteLine(nowarguments?) ' print statement958 Console.WriteLine(getDate(now)arguments?) ' print statement959 Else960 +Try 961 Dim tdname? = int(reply)value or expression? ' variable definition962 +If td >= 0condition? Then 963 Console.WriteLine(getDate(td)arguments?) ' print statement964 End If Catch evariableName? As ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError?965 End Try End If End While End Sub
+static void main() { 966 var replyname? = ""value or expression?;967 +while (!reply.upperCase().equals("Q")condition?) { 968 replyvariableName? = input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression?; // assignment969 +if (reply.equals("")condition?) { 970 var nowname? = divAsInt(clock(), 1000)value or expression?;971 System.out.println(nowarguments?); // print statement972 System.out.println(getDate(now)arguments?); // print statement973 } else {974 +try { 975 var tdname? = int(reply)value or expression?;976 +if (td >= 0condition?) { 977 System.out.println(getDate(td)arguments?); // print statement978 } // end if } catch (ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError? evariableName?) {979 } // end try } // end if } // end while } // end main

From the Ripple sort demo, printing a list:

+main 980 variable liname? set to [7, 1, 0, 4, 8, 3, 6]value or expression?981 print(liarguments?)982 call inPlaceRippleSortprocedureName?(liarguments?)983 print(liarguments?)984 end main
+def main() -> None: 985 liname? = [7, 1, 0, 4, 8, 3, 6]value or expression? # variable definition986 print(liarguments?)987 inPlaceRippleSortprocedureName?(liarguments?) # procedure call988 print(liarguments?)989 # end main
+static void main() { 990 var liname? = new [] {7, 1, 0, 4, 8, 3, 6}value or expression?;991 Console.WriteLine(liarguments?); // print statement992 inPlaceRippleSortprocedureName?(liarguments?); // procedure call993 Console.WriteLine(liarguments?); // print statement994 } // end main
+Sub main() 995 Dim liname? = {7, 1, 0, 4, 8, 3, 6}value or expression? ' variable definition996 Console.WriteLine(liarguments?) ' print statement997 inPlaceRippleSortprocedureName?(liarguments?) ' procedure call998 Console.WriteLine(liarguments?) ' print statement999 End Sub
+static void main() { 1000 var liname? = list(7, 1, 0, 4, 8, 3, 6)value or expression?;1001 System.out.println(liarguments?); // print statement1002 inPlaceRippleSortprocedureName?(liarguments?); // procedure call1003 System.out.println(liarguments?); // print statement1004 } // end main

From the Snake – procedural demo, printing an interpolated string (last line of the main routine):

+main 1005 variable blocksname? set to createBlockGraphics(white)value or expression?1006 variable headname? set to [20, 15]value or expression?1007 variable tailname? set to headvalue or expression?1008 variable bodyname? set to [head]value or expression?1009 variable currentDirname? set to Direction.rightvalue or expression?1010 variable gameOnname? set to truevalue or expression?1011 variable applename? set to [0, 0]value or expression?1012 call setAppleToRandomPositionprocedureName?(apple, bodyarguments?)1013 +while gameOncondition? 1014 call updateDisplayprocedureName?(blocks, head, tail, body, applearguments?)1015 variable currentDirRefname? set to new AsRef<of Direction>(currentDir)value or expression?1016 variable headRefname? set to new AsRef<of List<of Int>>(head)value or expression?1017 variable tailRefname? set to new AsRef<of List<of Int>>(tail)value or expression?1018 call updateSnakeprocedureName?(currentDirRef, tailRef, headRef, bodyarguments?)1019 assign headvariableName? to headRef.value()value or expression?1020 assign tailvariableName? to tailRef.value()value or expression?1021 assign currentDirvariableName? to currentDirRef.value()value or expression?1022 assign gameOnvariableName? to not hasHitEdge(head[0], head[1]) and not body.contains(head)value or expression?1023 +if head.equals(apple)condition? then 1024 call setAppleToRandomPositionprocedureName?(apple, bodyarguments?)1025 else1026 call body.removeAtprocedureName?(0arguments?)1027 end if call sleep_msprocedureName?(150arguments?)1028 end while print($"Game Over! Score: {body.length() - 1}"arguments?)1029 end main
+def main() -> None: 1030 blocksname? = createBlockGraphics(white)value or expression? # variable definition1031 headname? = [20, 15]value or expression? # variable definition1032 tailname? = headvalue or expression? # variable definition1033 bodyname? = [head]value or expression? # variable definition1034 currentDirname? = Direction.rightvalue or expression? # variable definition1035 gameOnname? = Truevalue or expression? # variable definition1036 applename? = [0, 0]value or expression? # variable definition1037 setAppleToRandomPositionprocedureName?(apple, bodyarguments?) # procedure call1038 +while gameOncondition?: 1039 updateDisplayprocedureName?(blocks, head, tail, body, applearguments?) # procedure call1040 currentDirRefname? = AsRef[Direction](currentDir)value or expression? # variable definition1041 headRefname? = AsRef[list[int]](head)value or expression? # variable definition1042 tailRefname? = AsRef[list[int]](tail)value or expression? # variable definition1043 updateSnakeprocedureName?(currentDirRef, tailRef, headRef, bodyarguments?) # procedure call1044 headvariableName? = headRef.value()value or expression? # assignment1045 tailvariableName? = tailRef.value()value or expression? # assignment1046 currentDirvariableName? = currentDirRef.value()value or expression? # assignment1047 gameOnvariableName? = not hasHitEdge(head[0], head[1]) and not body.contains(head)value or expression? # assignment1048 +if head.equals(apple)condition?: 1049 setAppleToRandomPositionprocedureName?(apple, bodyarguments?) # procedure call1050 else:1051 body.removeAtprocedureName?(0arguments?) # procedure call1052 # end if sleep_msprocedureName?(150arguments?) # procedure call1053 # end while print(f"Game Over! Score: {body.length() - 1}"arguments?)1054 # end main
+static void main() { 1055 var blocksname? = createBlockGraphics(white)value or expression?;1056 var headname? = new [] {20, 15}value or expression?;1057 var tailname? = headvalue or expression?;1058 var bodyname? = new [] {head}value or expression?;1059 var currentDirname? = Direction.rightvalue or expression?;1060 var gameOnname? = truevalue or expression?;1061 var applename? = new [] {0, 0}value or expression?;1062 setAppleToRandomPositionprocedureName?(apple, bodyarguments?); // procedure call1063 +while (gameOncondition?) { 1064 updateDisplayprocedureName?(blocks, head, tail, body, applearguments?); // procedure call1065 var currentDirRefname? = new AsRef<Direction>(currentDir)value or expression?;1066 var headRefname? = new AsRef<List<int>>(head)value or expression?;1067 var tailRefname? = new AsRef<List<int>>(tail)value or expression?;1068 updateSnakeprocedureName?(currentDirRef, tailRef, headRef, bodyarguments?); // procedure call1069 headvariableName? = headRef.value()value or expression?; // assignment1070 tailvariableName? = tailRef.value()value or expression?; // assignment1071 currentDirvariableName? = currentDirRef.value()value or expression?; // assignment1072 gameOnvariableName? = !hasHitEdge(head[0], head[1]) && !body.contains(head)value or expression?; // assignment1073 +if (head.equals(apple)condition?) { 1074 setAppleToRandomPositionprocedureName?(apple, bodyarguments?); // procedure call1075 } else {1076 body.removeAtprocedureName?(0arguments?); // procedure call1077 } // end if sleep_msprocedureName?(150arguments?); // procedure call1078 } // end while Console.WriteLine($"Game Over! Score: {body.length() - 1}"arguments?); // print statement1079 } // end main
+Sub main() 1080 Dim blocksname? = createBlockGraphics(white)value or expression? ' variable definition1081 Dim headname? = {20, 15}value or expression? ' variable definition1082 Dim tailname? = headvalue or expression? ' variable definition1083 Dim bodyname? = {head}value or expression? ' variable definition1084 Dim currentDirname? = Direction.rightvalue or expression? ' variable definition1085 Dim gameOnname? = Truevalue or expression? ' variable definition1086 Dim applename? = {0, 0}value or expression? ' variable definition1087 setAppleToRandomPositionprocedureName?(apple, bodyarguments?) ' procedure call1088 +While gameOncondition? 1089 updateDisplayprocedureName?(blocks, head, tail, body, applearguments?) ' procedure call1090 Dim currentDirRefname? = New AsRef(Of Direction)(currentDir)value or expression? ' variable definition1091 Dim headRefname? = New AsRef(Of List(Of Integer))(head)value or expression? ' variable definition1092 Dim tailRefname? = New AsRef(Of List(Of Integer))(tail)value or expression? ' variable definition1093 updateSnakeprocedureName?(currentDirRef, tailRef, headRef, bodyarguments?) ' procedure call1094 headvariableName? = headRef.value()value or expression? ' assignment1095 tailvariableName? = tailRef.value()value or expression? ' assignment1096 currentDirvariableName? = currentDirRef.value()value or expression? ' assignment1097 gameOnvariableName? = Not hasHitEdge(head(0), head(1)) And Not body.contains(head)value or expression? ' assignment1098 +If head.equals(apple)condition? Then 1099 setAppleToRandomPositionprocedureName?(apple, bodyarguments?) ' procedure call1100 Else1101 body.removeAtprocedureName?(0arguments?) ' procedure call1102 End If sleep_msprocedureName?(150arguments?) ' procedure call1103 End While Console.WriteLine($"Game Over! Score: {body.length() - 1}"arguments?) ' print statement1104 End Sub
+static void main() { 1105 var blocksname? = createBlockGraphics(white)value or expression?;1106 var headname? = list(20, 15)value or expression?;1107 var tailname? = headvalue or expression?;1108 var bodyname? = list(head)value or expression?;1109 var currentDirname? = Direction.rightvalue or expression?;1110 var gameOnname? = truevalue or expression?;1111 var applename? = list(0, 0)value or expression?;1112 setAppleToRandomPositionprocedureName?(apple, bodyarguments?); // procedure call1113 +while (gameOncondition?) { 1114 updateDisplayprocedureName?(blocks, head, tail, body, applearguments?); // procedure call1115 var currentDirRefname? = new AsRef<Direction>(currentDir)value or expression?;1116 var headRefname? = new AsRef<List<int>>(head)value or expression?;1117 var tailRefname? = new AsRef<List<int>>(tail)value or expression?;1118 updateSnakeprocedureName?(currentDirRef, tailRef, headRef, bodyarguments?); // procedure call1119 headvariableName? = headRef.value()value or expression?; // assignment1120 tailvariableName? = tailRef.value()value or expression?; // assignment1121 currentDirvariableName? = currentDirRef.value()value or expression?; // assignment1122 gameOnvariableName? = !hasHitEdge(head[0], head[1]) && !body.contains(head)value or expression?; // assignment1123 +if (head.equals(apple)condition?) { 1124 setAppleToRandomPositionprocedureName?(apple, bodyarguments?); // procedure call1125 } else {1126 body.removeAtprocedureName?(0arguments?); // procedure call1127 } // end if sleep_msprocedureName?(150arguments?); // procedure call1128 } // end while System.out.println(String.format("Game Over! Score: %", body.length() - 1)arguments?); // print statement1129 } // end main

What you need to know about a print statement

  • The print statement writes a value to display.
  • It automatically prints a new line after the value.
  • The value can be any expression.
  • You must provide a value; if you just want to print a blank line, just set the value to "".
  • The recommended way to print multiple values is to use an interpolated string (see third example above)

More advanced techniques

As an alternative to using the print statement you call one of these standard library procedures using procedure call instruction:

  • printNoLine
  • printWithTab

which offer more flexible options. These library methods work when running any of the supported languages within Elan but, unlike when using the normal the print statement, the library procedures do not correspond directly to print methods in the native libraries for those languages.

Variable definition

Examples of a variable definition

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?1130
xname? = 20value or expression? # variable definition1131
var xname? = 20value or expression?;1132
Dim xname? = 20value or expression? ' variable definition1133
var xname? = 20value or expression?;1134
variable yname? set to 15value or expression?1135
yname? = 15value or expression? # variable definition1136
var yname? = 15value or expression?;1137
Dim yname? = 15value or expression? ' variable definition1138
var yname? = 15value or expression?;1139

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

assign blocks[x][y]variableName? to redvalue or expression?1140
blocks[x][y]variableName? = redvalue or expression? # assignment1141
blocks[x][y]variableName? = redvalue or expression?; // assignment1142
blocks(x)(y)variableName? = redvalue or expression? ' assignment1143
blocks[x][y]variableName? = redvalue or expression?; // assignment1144

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?1145
gridname? = createBlockGraphics(white)value or expression? # variable definition1146
var gridname? = createBlockGraphics(white)value or expression?;1147
Dim gridname? = createBlockGraphics(white)value or expression? ' variable definition1148
var gridname? = createBlockGraphics(white)value or expression?;1149

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

variable liname? set to [7, 1, 0, 4, 8, 3, 6]value or expression?1150
liname? = [7, 1, 0, 4, 8, 3, 6]value or expression? # variable definition1151
var liname? = new [] {7, 1, 0, 4, 8, 3, 6}value or expression?;1152
Dim liname? = {7, 1, 0, 4, 8, 3, 6}value or expression? ' variable definition1153
var liname? = list(7, 1, 0, 4, 8, 3, 6)value or expression?;1154

What you need to know about a variable definition

  • 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.

Assigment

Examples of an assignment

From the Ripple sort demo, where the hasChangedhasChangedhasChangedhasChangedhasChanged variable, having been defined higher up, is assigned 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?) 1155 variable hasChangedname? set to truevalue or expression?1156 variable lastCompname? set to li.length() - 2value or expression?1157 +while hasChanged is truecondition? 1158 assign hasChangedvariableName? to falsevalue or expression?1159 +for iitem? in range(0, lastComp + 1)source? 1160 +if li[i] > li[i + 1]condition? then 1161 variable tempname? set to li[i]value or expression?1162 assign li[i]variableName? to li[i + 1]value or expression?1163 assign li[i + 1]variableName? to tempvalue or expression?1164 assign hasChangedvariableName? to truevalue or expression?1165 end if end for assign lastCompvariableName? to lastComp - 1value or expression?1166 end while end procedure
+def inPlaceRippleSortname?(li: list[int]parameter definitions?) -> None: # procedure1167 hasChangedname? = Truevalue or expression? # variable definition1168 lastCompname? = li.length() - 2value or expression? # variable definition1169 +while hasChanged == Truecondition?: 1170 hasChangedvariableName? = Falsevalue or expression? # assignment1171 +for iitem? in range(0, lastComp + 1)source?: 1172 +if li[i] > li[i + 1]condition?: 1173 tempname? = li[i]value or expression? # variable definition1174 li[i]variableName? = li[i + 1]value or expression? # assignment1175 li[i + 1]variableName? = tempvalue or expression? # assignment1176 hasChangedvariableName? = Truevalue or expression? # assignment1177 # end if # end for lastCompvariableName? = lastComp - 1value or expression? # assignment1178 # end while # end procedure
+static void inPlaceRippleSortname?(List<int> liparameter definitions?) { // procedure1179 var hasChangedname? = truevalue or expression?;1180 var lastCompname? = li.length() - 2value or expression?;1181 +while (hasChanged == truecondition?) { 1182 hasChangedvariableName? = falsevalue or expression?; // assignment1183 +foreach (var iitem? in range(0, lastComp + 1)source?) { 1184 +if (li[i] > li[i + 1]condition?) { 1185 var tempname? = li[i]value or expression?;1186 li[i]variableName? = li[i + 1]value or expression?; // assignment1187 li[i + 1]variableName? = tempvalue or expression?; // assignment1188 hasChangedvariableName? = truevalue or expression?; // assignment1189 } // end if } // end foreach lastCompvariableName? = lastComp - 1value or expression?; // assignment1190 } // end while } // end procedure
+Sub inPlaceRippleSortname?(li As List(Of Integer)parameter definitions?) ' procedure1191 Dim hasChangedname? = Truevalue or expression? ' variable definition1192 Dim lastCompname? = li.length() - 2value or expression? ' variable definition1193 +While hasChanged = Truecondition? 1194 hasChangedvariableName? = Falsevalue or expression? ' assignment1195 +For Each iitem? In range(0, lastComp + 1)source? 1196 +If li(i) > li(i + 1)condition? Then 1197 Dim tempname? = li(i)value or expression? ' variable definition1198 li(i)variableName? = li(i + 1)value or expression? ' assignment1199 li(i + 1)variableName? = tempvalue or expression? ' assignment1200 hasChangedvariableName? = Truevalue or expression? ' assignment1201 End If Next i lastCompvariableName? = lastComp - 1value or expression? ' assignment1202 End While End Sub
+static void inPlaceRippleSortname?(List<int> liparameter definitions?) { // procedure1203 var hasChangedname? = truevalue or expression?;1204 var lastCompname? = li.length() - 2value or expression?;1205 +while (hasChanged == truecondition?) { 1206 hasChangedvariableName? = falsevalue or expression?; // assignment1207 +foreach (var iitem? in range(0, lastComp + 1)source?) { 1208 +if (li[i] > li[i + 1]condition?) { 1209 var tempname? = li[i]value or expression?;1210 li[i]variableName? = li[i + 1]value or expression?; // assignment1211 li[i + 1]variableName? = tempvalue or expression?; // assignment1212 hasChangedvariableName? = truevalue or expression?; // assignment1213 } // end if } // end foreach lastCompvariableName? = lastComp - 1value or expression?; // assignment1214 } // end while } // end procedure

From the Snake -procedural demo, where the variable currentDefcurrentDefcurrentDefcurrentDefcurrentDef is assigned 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:

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.

What you need to know about an assignment

  • An assignment 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 statement

Examples of an input statement

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

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

What you need to know about an input statement

  • The input statement 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 an assignment on the next line.
  • Also in a separate instruction, you can parse the string into a numeric type by applying the asInt()asInt()asInt()asInt()asInt() or asFloat()asFloat()asFloat()asFloat()asFloat() functions to the input variable, but note that these functions will throw an exception if the input string does not parse to the required numeric type.
  • 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 statement 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 an if statement

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? 1220 variable resultname? set to blackvalue or expression?1221 +if random > 0.5condition? then 1222 assign resultvariableName? to whitevalue or expression?1223 end if return resultvalue or expression?1224 end function
+def blackOrWhitename?(random: floatparameter definitions?) -> intType?: # function1225 resultname? = blackvalue or expression? # variable definition1226 +if random > 0.5condition?: 1227 resultvariableName? = whitevalue or expression? # assignment1228 # end if return resultvalue or expression?1229 # end function
+static intType? blackOrWhitename?(double randomparameter definitions?) { // function1230 var resultname? = blackvalue or expression?;1231 +if (random > 0.5condition?) { 1232 resultvariableName? = whitevalue or expression?; // assignment1233 } // end if return resultvalue or expression?;1234 } // end function
+Function blackOrWhitename?(random As Doubleparameter definitions?) As IntegerType? 1235 Dim resultname? = blackvalue or expression? ' variable definition1236 +If random > 0.5condition? Then 1237 resultvariableName? = whitevalue or expression? ' assignment1238 End If Return resultvalue or expression?1239 End Function
+static intType? blackOrWhitename?(double randomparameter definitions?) { // function1240 var resultname? = blackvalue or expression?;1241 +if (random > 0.5condition?) { 1242 resultvariableName? = whitevalue or expression?; // assignment1243 } // end if return resultvalue or expression?;1244 } // end function

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

+if random() < 0.05condition? then 1245 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? call b.setRadiusprocedureName?(0arguments?)1246 call b.setCentreYprocedureName?(75arguments?)1247 else1248 # bubble rises and grows slightlycomment? call b.setCentreYprocedureName?(b.centreY - 1arguments?)1249 call b.setRadiusprocedureName?(b.radius + 0.2arguments?)1250 end if
+if random() < 0.05condition?: 1251 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) # procedure call1252 b.setCentreYprocedureName?(75arguments?) # procedure call1253 else:1254 # bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) # procedure call1255 b.setRadiusprocedureName?(b.radius + 0.2arguments?) # procedure call1256 # end if
+if (random() < 0.05condition?) { 1257 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // procedure call1258 b.setCentreYprocedureName?(75arguments?); // procedure call1259 } else {1260 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // procedure call1261 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // procedure call1262 } // end if
+If random() < 0.05condition? Then 1263 ' 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) ' procedure call1264 b.setCentreYprocedureName?(75arguments?) ' procedure call1265 Else1266 ' bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) ' procedure call1267 b.setRadiusprocedureName?(b.radius + 0.2arguments?) ' procedure call1268 End If
+if (random() < 0.05condition?) { 1269 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // procedure call1270 b.setCentreYprocedureName?(75arguments?); // procedure call1271 } else {1272 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // procedure call1273 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // procedure call1274 } // end if

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

+if direction is 0condition? then 1275 assign xvariableName? to min([x + 1, 39])value or expression?1276 elif direction is 1condition? then1277 assign xvariableName? to max([x - 1, 0])value or expression?1278 elif direction is 2condition? then1279 assign yvariableName? to min([y + 1, 29])value or expression?1280 elif direction is 3condition? then1281 assign yvariableName? to max([y - 1, 0])value or expression?1282 end if
+if direction == 0condition?: 1283 xvariableName? = min([x + 1, 39])value or expression? # assignment1284 elif direction == 1condition?: # else if1285 xvariableName? = max([x - 1, 0])value or expression? # assignment1286 elif direction == 2condition?: # else if1287 yvariableName? = min([y + 1, 29])value or expression? # assignment1288 elif direction == 3condition?: # else if1289 yvariableName? = max([y - 1, 0])value or expression? # assignment1290 # end if
+if (direction == 0condition?) { 1291 xvariableName? = min(new [] {x + 1, 39})value or expression?; // assignment1292 } else if (direction == 1condition?) {1293 xvariableName? = max(new [] {x - 1, 0})value or expression?; // assignment1294 } else if (direction == 2condition?) {1295 yvariableName? = min(new [] {y + 1, 29})value or expression?; // assignment1296 } else if (direction == 3condition?) {1297 yvariableName? = max(new [] {y - 1, 0})value or expression?; // assignment1298 } // end if
+If direction = 0condition? Then 1299 xvariableName? = min({x + 1, 39})value or expression? ' assignment1300 ElseIf direction = 1condition? Then1301 xvariableName? = max({x - 1, 0})value or expression? ' assignment1302 ElseIf direction = 2condition? Then1303 yvariableName? = min({y + 1, 29})value or expression? ' assignment1304 ElseIf direction = 3condition? Then1305 yvariableName? = max({y - 1, 0})value or expression? ' assignment1306 End If
+if (direction == 0condition?) { 1307 xvariableName? = min(list(x + 1, 39))value or expression?; // assignment1308 } else if (direction == 1condition?) {1309 xvariableName? = max(list(x - 1, 0))value or expression?; // assignment1310 } else if (direction == 2condition?) {1311 yvariableName? = min(list(y + 1, 29))value or expression?; // assignment1312 } else if (direction == 3condition?) {1313 yvariableName? = max(list(y - 1, 0))value or expression?; // assignment1314 } // end if

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

What you need to know about an if statement

  • The if statement 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 statement 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

See also

if_ function. This is an alternative way to implement conditions. Although that is introduced as part of functional programming', you may use it within the procedural paradigm.

While loop

Examples of a while loop

From the Ripple Sort demo, where the loop is executed until a pass right through the loop has left the hasChangedhasChangedhasChangedhasChangedhasChanged variable as 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.

+while hasChanged is truecondition? 1315 assign hasChangedvariableName? to falsevalue or expression?1316 +for iitem? in range(0, lastComp + 1)source? 1317 +if li[i] > li[i + 1]condition? then 1318 variable tempname? set to li[i]value or expression?1319 assign li[i]variableName? to li[i + 1]value or expression?1320 assign li[i + 1]variableName? to tempvalue or expression?1321 assign hasChangedvariableName? to truevalue or expression?1322 end if end for assign lastCompvariableName? to lastComp - 1value or expression?1323 end while
+while hasChanged == Truecondition?: 1324 hasChangedvariableName? = Falsevalue or expression? # assignment1325 +for iitem? in range(0, lastComp + 1)source?: 1326 +if li[i] > li[i + 1]condition?: 1327 tempname? = li[i]value or expression? # variable definition1328 li[i]variableName? = li[i + 1]value or expression? # assignment1329 li[i + 1]variableName? = tempvalue or expression? # assignment1330 hasChangedvariableName? = Truevalue or expression? # assignment1331 # end if # end for lastCompvariableName? = lastComp - 1value or expression? # assignment1332 # end while
+while (hasChanged == truecondition?) { 1333 hasChangedvariableName? = falsevalue or expression?; // assignment1334 +foreach (var iitem? in range(0, lastComp + 1)source?) { 1335 +if (li[i] > li[i + 1]condition?) { 1336 var tempname? = li[i]value or expression?;1337 li[i]variableName? = li[i + 1]value or expression?; // assignment1338 li[i + 1]variableName? = tempvalue or expression?; // assignment1339 hasChangedvariableName? = truevalue or expression?; // assignment1340 } // end if } // end foreach lastCompvariableName? = lastComp - 1value or expression?; // assignment1341 } // end while
+While hasChanged = Truecondition? 1342 hasChangedvariableName? = Falsevalue or expression? ' assignment1343 +For Each iitem? In range(0, lastComp + 1)source? 1344 +If li(i) > li(i + 1)condition? Then 1345 Dim tempname? = li(i)value or expression? ' variable definition1346 li(i)variableName? = li(i + 1)value or expression? ' assignment1347 li(i + 1)variableName? = tempvalue or expression? ' assignment1348 hasChangedvariableName? = Truevalue or expression? ' assignment1349 End If Next i lastCompvariableName? = lastComp - 1value or expression? ' assignment1350 End While
+while (hasChanged == truecondition?) { 1351 hasChangedvariableName? = falsevalue or expression?; // assignment1352 +foreach (var iitem? in range(0, lastComp + 1)source?) { 1353 +if (li[i] > li[i + 1]condition?) { 1354 var tempname? = li[i]value or expression?;1355 li[i]variableName? = li[i + 1]value or expression?; // assignment1356 li[i + 1]variableName? = tempvalue or expression?; // assignment1357 hasChangedvariableName? = truevalue or expression?; // assignment1358 } // end if } // end foreach lastCompvariableName? = lastComp - 1value or expression?; // assignment1359 } // end 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? 1360 call moveGrowBurstprocedureName?(bubblesarguments?)1361 end while
+while Truecondition?: 1362 moveGrowBurstprocedureName?(bubblesarguments?) # procedure call1363 # end while
+while (truecondition?) { 1364 moveGrowBurstprocedureName?(bubblesarguments?); // procedure call1365 } // end while
+While Truecondition? 1366 moveGrowBurstprocedureName?(bubblesarguments?) ' procedure call1367 End While
+while (truecondition?) { 1368 moveGrowBurstprocedureName?(bubblesarguments?); // procedure call1369 } // end 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? 1370 +if (stacks[0].length() mod 2) is 0condition? then 1371 call moveBetweenprocedureName?(stacks, 0, 1arguments?)1372 call moveBetweenprocedureName?(stacks, 0, 2arguments?)1373 call moveBetweenprocedureName?(stacks, 1, 2arguments?)1374 else1375 call moveBetweenprocedureName?(stacks, 0, 2arguments?)1376 call moveBetweenprocedureName?(stacks, 0, 1arguments?)1377 call moveBetweenprocedureName?(stacks, 1, 2arguments?)1378 end if end while
+while stacks[2].length() != nDiscscondition?: 1379 +if (stacks[0].length() % 2) == 0condition?: 1380 moveBetweenprocedureName?(stacks, 0, 1arguments?) # procedure call1381 moveBetweenprocedureName?(stacks, 0, 2arguments?) # procedure call1382 moveBetweenprocedureName?(stacks, 1, 2arguments?) # procedure call1383 else:1384 moveBetweenprocedureName?(stacks, 0, 2arguments?) # procedure call1385 moveBetweenprocedureName?(stacks, 0, 1arguments?) # procedure call1386 moveBetweenprocedureName?(stacks, 1, 2arguments?) # procedure call1387 # end if # end while
+while (stacks[2].length() != nDiscscondition?) { 1388 +if ((stacks[0].length() % 2) == 0condition?) { 1389 moveBetweenprocedureName?(stacks, 0, 1arguments?); // procedure call1390 moveBetweenprocedureName?(stacks, 0, 2arguments?); // procedure call1391 moveBetweenprocedureName?(stacks, 1, 2arguments?); // procedure call1392 } else {1393 moveBetweenprocedureName?(stacks, 0, 2arguments?); // procedure call1394 moveBetweenprocedureName?(stacks, 0, 1arguments?); // procedure call1395 moveBetweenprocedureName?(stacks, 1, 2arguments?); // procedure call1396 } // end if } // end while
+While stacks(2).length() <> nDiscscondition? 1397 +If (stacks(0).length() Mod 2) = 0condition? Then 1398 moveBetweenprocedureName?(stacks, 0, 1arguments?) ' procedure call1399 moveBetweenprocedureName?(stacks, 0, 2arguments?) ' procedure call1400 moveBetweenprocedureName?(stacks, 1, 2arguments?) ' procedure call1401 Else1402 moveBetweenprocedureName?(stacks, 0, 2arguments?) ' procedure call1403 moveBetweenprocedureName?(stacks, 0, 1arguments?) ' procedure call1404 moveBetweenprocedureName?(stacks, 1, 2arguments?) ' procedure call1405 End If End While
+while (stacks[2].length() != nDiscscondition?) { 1406 +if ((stacks[0].length() % 2) == 0condition?) { 1407 moveBetweenprocedureName?(stacks, 0, 1arguments?); // procedure call1408 moveBetweenprocedureName?(stacks, 0, 2arguments?); // procedure call1409 moveBetweenprocedureName?(stacks, 1, 2arguments?); // procedure call1410 } else {1411 moveBetweenprocedureName?(stacks, 0, 2arguments?); // procedure call1412 moveBetweenprocedureName?(stacks, 0, 1arguments?); // procedure call1413 moveBetweenprocedureName?(stacks, 1, 2arguments?); // procedure call1414 } // end if } // end while

What you need to know about a while loop

  • The while loop specifies that a block of instructions 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 variables 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 a for loop

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? 1415 variable bname? set to (new CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression?1416 call bubbles.appendprocedureName?(barguments?)1417 end for
+for iitem? in range(1, 21)source?: 1418 bname? = (CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression? # variable definition1419 bubbles.appendprocedureName?(barguments?) # procedure call1420 # end for
+foreach (var iitem? in range(1, 21)source?) { 1421 var bname? = (new CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression?;1422 bubbles.appendprocedureName?(barguments?); // procedure call1423 } // end foreach
+For Each iitem? In range(1, 21)source? 1424 Dim bname? = (New CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression? ' variable definition1425 bubbles.appendprocedureName?(barguments?) ' procedure call1426 Next i
+foreach (var iitem? in range(1, 21)source?) { 1427 var bname? = (new CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression?;1428 bubbles.appendprocedureName?(barguments?); // procedure call1429 } // end 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?) 1430 variable pname? set to (200.0/order).floor()value or expression?1431 variable turnIname? set to 0value or expression?1432 +for turnitem? in turnssource? 1433 assign turnIvariableName? to (if_(turn.equals(left), 1, -1))value or expression?1434 call t.turnprocedureName?(-45*turnIarguments?)1435 call t.moveprocedureName?(cornerarguments?)1436 call t.turnprocedureName?(-45*turnIarguments?)1437 call t.moveprocedureName?(sidearguments?)1438 call sleep_msprocedureName?(parguments?)1439 end for call t.penUpprocedureName?(arguments?)1440 call t.hideprocedureName?(arguments?)1441 end procedure
+def drawDragonname?(t: Turtle, order: int, turns: str, side: float, corner: floatparameter definitions?) -> None: # procedure1442 pname? = (200.0/order).floor()value or expression? # variable definition1443 turnIname? = 0value or expression? # variable definition1444 +for turnitem? in turnssource?: 1445 turnIvariableName? = (if_(turn.equals(left), 1, -1))value or expression? # assignment1446 t.turnprocedureName?(-45*turnIarguments?) # procedure call1447 t.moveprocedureName?(cornerarguments?) # procedure call1448 t.turnprocedureName?(-45*turnIarguments?) # procedure call1449 t.moveprocedureName?(sidearguments?) # procedure call1450 sleep_msprocedureName?(parguments?) # procedure call1451 # end for t.penUpprocedureName?(arguments?) # procedure call1452 t.hideprocedureName?(arguments?) # procedure call1453 # end procedure
+static void drawDragonname?(Turtle t, int order, string turns, double side, double cornerparameter definitions?) { // procedure1454 var pname? = (200.0/order).floor()value or expression?;1455 var turnIname? = 0value or expression?;1456 +foreach (var turnitem? in turnssource?) { 1457 turnIvariableName? = (if_(turn.equals(left), 1, -1))value or expression?; // assignment1458 t.turnprocedureName?(-45*turnIarguments?); // procedure call1459 t.moveprocedureName?(cornerarguments?); // procedure call1460 t.turnprocedureName?(-45*turnIarguments?); // procedure call1461 t.moveprocedureName?(sidearguments?); // procedure call1462 sleep_msprocedureName?(parguments?); // procedure call1463 } // end foreach t.penUpprocedureName?(arguments?); // procedure call1464 t.hideprocedureName?(arguments?); // procedure call1465 } // end procedure
+Sub drawDragonname?(t As Turtle, order As Integer, turns As String, side As Double, corner As Doubleparameter definitions?) ' procedure1466 Dim pname? = (200.0/order).floor()value or expression? ' variable definition1467 Dim turnIname? = 0value or expression? ' variable definition1468 +For Each turnitem? In turnssource? 1469 turnIvariableName? = (if_(turn.equals(left), 1, -1))value or expression? ' assignment1470 t.turnprocedureName?(-45*turnIarguments?) ' procedure call1471 t.moveprocedureName?(cornerarguments?) ' procedure call1472 t.turnprocedureName?(-45*turnIarguments?) ' procedure call1473 t.moveprocedureName?(sidearguments?) ' procedure call1474 sleep_msprocedureName?(parguments?) ' procedure call1475 Next turn t.penUpprocedureName?(arguments?) ' procedure call1476 t.hideprocedureName?(arguments?) ' procedure call1477 End Sub
+static void drawDragonname?(Turtle t, int order, String turns, double side, double cornerparameter definitions?) { // procedure1478 var pname? = (200.0/order).floor()value or expression?;1479 var turnIname? = 0value or expression?;1480 +foreach (var turnitem? in turnssource?) { 1481 turnIvariableName? = (if_(turn.equals(left), 1, -1))value or expression?; // assignment1482 t.turnprocedureName?(-45*turnIarguments?); // procedure call1483 t.moveprocedureName?(cornerarguments?); // procedure call1484 t.turnprocedureName?(-45*turnIarguments?); // procedure call1485 t.moveprocedureName?(sidearguments?); // procedure call1486 sleep_msprocedureName?(parguments?); // procedure call1487 } // end foreach t.penUpprocedureName?(arguments?); // procedure call1488 t.hideprocedureName?(arguments?); // procedure call1489 } // end 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? 1490 +for yitem? in range(0, 30)source? 1491 variable colourname? set to nextCellValue(grid, x, y)value or expression?1492 assign nextGen[x][y]variableName? to colourvalue or expression?1493 end for end for
+for xitem? in range(0, 40)source?: 1494 +for yitem? in range(0, 30)source?: 1495 colourname? = nextCellValue(grid, x, y)value or expression? # variable definition1496 nextGen[x][y]variableName? = colourvalue or expression? # assignment1497 # end for # end for
+foreach (var xitem? in range(0, 40)source?) { 1498 +foreach (var yitem? in range(0, 30)source?) { 1499 var colourname? = nextCellValue(grid, x, y)value or expression?;1500 nextGen[x][y]variableName? = colourvalue or expression?; // assignment1501 } // end foreach } // end foreach
+For Each xitem? In range(0, 40)source? 1502 +For Each yitem? In range(0, 30)source? 1503 Dim colourname? = nextCellValue(grid, x, y)value or expression? ' variable definition1504 nextGen(x)(y)variableName? = colourvalue or expression? ' assignment1505 Next y Next x
+foreach (var xitem? in range(0, 40)source?) { 1506 +foreach (var yitem? in range(0, 30)source?) { 1507 var colourname? = nextCellValue(grid, x, y)value or expression?;1508 nextGen[x][y]variableName? = colourvalue or expression?; // assignment1509 } // end foreach } // end foreach

What you need to know about a for loop

  • The Elan concept of a for loop is equivalent to a 'for each' loop in C# and VB.
  • 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 instructions 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

Procedure call

Examples of a procedure call

From the Burrow demo: a call to the library procedure named displayBlocks, passing in the data structure held in variable 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.

call displayBlocksprocedureName?(blocksarguments?)1510
displayBlocksprocedureName?(blocksarguments?) # procedure call1511
displayBlocksprocedureName?(blocksarguments?); // procedure call1512
displayBlocksprocedureName?(blocksarguments?) ' procedure call1513
displayBlocksprocedureName?(blocksarguments?); // procedure call1514

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

call moveGrowBurstprocedureName?(bubblesarguments?)1515
moveGrowBurstprocedureName?(bubblesarguments?) # procedure call1516
moveGrowBurstprocedureName?(bubblesarguments?); // procedure call1517
moveGrowBurstprocedureName?(bubblesarguments?) ' procedure call1518
moveGrowBurstprocedureName?(bubblesarguments?); // procedure call1519
which is defined immediately 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?) 1520 variable headname? set to headRef.value()value or expression?1521 variable tailname? set to tailRef.value()value or expression?1522 variable currentDirname? set to currentDirRef.value()value or expression?1523 assign currentDirvariableName? to directionByKey(currentDir, getKey())value or expression?1524 call tailRef.setprocedureName?(body[0]arguments?)1525 call body.appendprocedureName?(headarguments?)1526 call headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?)1527 call currentDirRef.setprocedureName?(currentDirarguments?)1528 end procedure
+def updateSnakename?(currentDirRef: AsRef[Direction], tailRef: AsRef[list[int]], headRef: AsRef[list[int]], body: list[list[int]]parameter definitions?) -> None: # procedure1529 headname? = headRef.value()value or expression? # variable definition1530 tailname? = tailRef.value()value or expression? # variable definition1531 currentDirname? = currentDirRef.value()value or expression? # variable definition1532 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression? # assignment1533 tailRef.setprocedureName?(body[0]arguments?) # procedure call1534 body.appendprocedureName?(headarguments?) # procedure call1535 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?) # procedure call1536 currentDirRef.setprocedureName?(currentDirarguments?) # procedure call1537 # end procedure
+static void updateSnakename?(AsRef<Direction> currentDirRef, AsRef<List<int>> tailRef, AsRef<List<int>> headRef, List<List<int>> bodyparameter definitions?) { // procedure1538 var headname? = headRef.value()value or expression?;1539 var tailname? = tailRef.value()value or expression?;1540 var currentDirname? = currentDirRef.value()value or expression?;1541 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression?; // assignment1542 tailRef.setprocedureName?(body[0]arguments?); // procedure call1543 body.appendprocedureName?(headarguments?); // procedure call1544 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?); // procedure call1545 currentDirRef.setprocedureName?(currentDirarguments?); // procedure call1546 } // end 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?) ' procedure1547 Dim headname? = headRef.value()value or expression? ' variable definition1548 Dim tailname? = tailRef.value()value or expression? ' variable definition1549 Dim currentDirname? = currentDirRef.value()value or expression? ' variable definition1550 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression? ' assignment1551 tailRef.setprocedureName?(body(0)arguments?) ' procedure call1552 body.appendprocedureName?(headarguments?) ' procedure call1553 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?) ' procedure call1554 currentDirRef.setprocedureName?(currentDirarguments?) ' procedure call1555 End Sub
+static void updateSnakename?(AsRef<Direction> currentDirRef, AsRef<List<int>> tailRef, AsRef<List<int>> headRef, List<List<int>> bodyparameter definitions?) { // procedure1556 var headname? = headRef.value()value or expression?;1557 var tailname? = tailRef.value()value or expression?;1558 var currentDirname? = currentDirRef.value()value or expression?;1559 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression?; // assignment1560 tailRef.setprocedureName?(body[0]arguments?); // procedure call1561 body.appendprocedureName?(headarguments?); // procedure call1562 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?); // procedure call1563 currentDirRef.setprocedureName?(currentDirarguments?); // procedure call1564 } // end 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 a procedure call

  • The procedure call 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, procedure calls are not allowed in functions.
  • There is a limit to the complexity allowed in a procedure call 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 statement

Examples of a try statement

What you need to know about a try statement

  • The try statement 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 statement instruction is sometimes 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 statement 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 statement 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 statement in your own code (see below).
  • Currently Elan currently permits only one catch clause to be associated with each try statement

You can enclose code in a try statement 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 statement 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 statement.

Throw statement

Examples of a throw statement

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? 1635 variable resultname? set to 1value or expression?1636 +if n > 1condition? then 1637 assign resultvariableName? to n*factorial(n - 1)value or expression?1638 elif n < 0condition? then1639 throw CustomErrorexception type? "Factorial cannot be called with a negative argument"message?1640 end if return resultvalue or expression?1641 end function
+def factorialname?(n: intparameter definitions?) -> intType?: # function1642 resultname? = 1value or expression? # variable definition1643 +if n > 1condition?: 1644 resultvariableName? = n*factorial(n - 1)value or expression? # assignment1645 elif n < 0condition?: # else if1646 raise CustomErrorexception type?("Factorial cannot be called with a negative argument"message?)1647 # end if return resultvalue or expression?1648 # end function
+static intType? factorialname?(int nparameter definitions?) { // function1649 var resultname? = 1value or expression?;1650 +if (n > 1condition?) { 1651 resultvariableName? = n*factorial(n - 1)value or expression?; // assignment1652 } else if (n < 0condition?) {1653 throw new CustomErrorexception type?("Factorial cannot be called with a negative argument"message?);1654 } // end if return resultvalue or expression?;1655 } // end function
+Function factorialname?(n As Integerparameter definitions?) As IntegerType? 1656 Dim resultname? = 1value or expression? ' variable definition1657 +If n > 1condition? Then 1658 resultvariableName? = n*factorial(n - 1)value or expression? ' assignment1659 ElseIf n < 0condition? Then1660 Throw New CustomErrorexception type?("Factorial cannot be called with a negative argument"message?)1661 End If Return resultvalue or expression?1662 End Function
+static intType? factorialname?(int nparameter definitions?) { // function1663 var resultname? = 1value or expression?;1664 +if (n > 1condition?) { 1665 resultvariableName? = n*factorial(n - 1)value or expression?; // assignment1666 } else if (n < 0condition?) {1667 throw new CustomErrorexception type?("Factorial cannot be called with a negative argument"message?);1668 } // end if return resultvalue or expression?;1669 } // end function

What you need to know about a throw statement

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

Assert statement

Examples of an assert statement

From the Tower of Hanoi

demo, showing an assert for an error case:
+test test_colourtest_name? 1695 # Normal casescomment? assert colour(5)actual (computed) value? is greenexpected value? not run1696 # Edge casescomment? assert colour(1)actual (computed) value? is redexpected value? not run1697 assert colour(10)actual (computed) value? is 0xFF99CCexpected value? not run1698 # Error casescomment? assert colour(11)actual (computed) value? is "Out of range index: 10 size: 10"expected value? not run1699 end test
+class Test_colour(unittest.TestCase):
 def test_colourtest_name?(self) -> None: 1700
# Normal casescomment? self.assertEqual(colour(5)actual (computed) value?, greenexpected value?) not run1701 # Edge casescomment? self.assertEqual(colour(1)actual (computed) value?, redexpected value?) not run1702 self.assertEqual(colour(10)actual (computed) value?, 0xFF99CCexpected value?) not run1703 # Error casescomment? self.assertEqual(colour(11)actual (computed) value?, "Out of range index: 10 size: 10"expected value?) not run1704 # end test
+[TestClass] class Test_colour
[TestMethod] static void test_colourtest_name?() { 1705
// Normal casescomment? Assert.AreEqual(greenexpected value?, colour(5)actual (computed) value?); not run1706 // Edge casescomment? Assert.AreEqual(redexpected value?, colour(1)actual (computed) value?); not run1707 Assert.AreEqual(0xFF99CCexpected value?, colour(10)actual (computed) value?); not run1708 // Error casescomment? Assert.AreEqual("Out of range index: 10 size: 10"expected value?, colour(11)actual (computed) value?); not run1709 }} // end test
+<TestClass Class Test_colour
 <TestMethod> Sub test_colourtest_name?() 1710
' Normal casescomment? Assert.AreEqual(greenexpected value?, colour(5)actual (computed) value?) not run1711 ' Edge casescomment? Assert.AreEqual(redexpected value?, colour(1)actual (computed) value?) not run1712 Assert.AreEqual(&HFF99CCexpected value?, colour(10)actual (computed) value?) not run1713 ' Error casescomment? Assert.AreEqual("Out of range index: 10 size: 10"expected value?, colour(11)actual (computed) value?) not run1714  End Sub
End Class
+class Test_colour {
@Test static void test_colourtest_name?() { 1715
// Normal casescomment? assertEquals(greenexpected value?, colour(5)actual (computed) value?); not run1716 // Edge casescomment? assertEquals(redexpected value?, colour(1)actual (computed) value?); not run1717 assertEquals(0xFF99CCexpected value?, colour(10)actual (computed) value?); not run1718 // Error casescomment? assertEquals("Out of range index: 10 size: 10"expected value?, colour(11)actual (computed) value?); not run1719 }} // end test

What you need to know about an assert statement

  • 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

Comments can be inserted adjacent to statement instructions just like those at global level: see Comment

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 trueTruetrueTruetrue
String "Hello""Hello""Hello""Hello""Hello"
Tuple (3, "banana")(3, "banana")(3, "banana")(3, "banana")(3, "banana")
List ["lemon", "lime", "orange"]["lemon", "lime", "orange"]new [] {"lemon", "lime", "orange"}{"lemon", "lime", "orange"}list("lemon", "lime", "orange")

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 1720 variable aname? set to "Hello world!"value or expression?1721 print(a[4]arguments?)1722 print(a.subString(4, a.length())arguments?)1723 print(a.subString(0, 7)arguments?)1724 end main
+def main() -> None: 1725 aname? = "Hello world!"value or expression? # variable definition1726 print(a[4]arguments?)1727 print(a.subString(4, a.length())arguments?)1728 print(a.subString(0, 7)arguments?)1729 # end main
+static void main() { 1730 var aname? = "Hello world!"value or expression?;1731 Console.WriteLine(a[4]arguments?); // print statement1732 Console.WriteLine(a.subString(4, a.length())arguments?); // print statement1733 Console.WriteLine(a.subString(0, 7)arguments?); // print statement1734 } // end main
+Sub main() 1735 Dim aname? = "Hello world!"value or expression? ' variable definition1736 Console.WriteLine(a(4)arguments?) ' print statement1737 Console.WriteLine(a.subString(4, a.length())arguments?) ' print statement1738 Console.WriteLine(a.subString(0, 7)arguments?) ' print statement1739 End Sub
+static void main() { 1740 var aname? = "Hello world!"value or expression?;1741 System.out.println(a[4]arguments?); // print statement1742 System.out.println(a.subString(4, a.length())arguments?); // print statement1743 System.out.println(a.subString(0, 7)arguments?); // print statement1744 } // end main






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

And on a list:

+main 1745 variable liname? set to new List<of Int>()value or expression?1746 assign livariableName? to [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]value or expression?1747 print(li[4]arguments?)1748 print(li.subList(4, li.length())arguments?)1749 print(li.subList(0, 7)arguments?)1750 end main
+def main() -> None: 1751 liname? = list[int]()value or expression? # variable definition1752 livariableName? = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]value or expression? # assignment1753 print(li[4]arguments?)1754 print(li.subList(4, li.length())arguments?)1755 print(li.subList(0, 7)arguments?)1756 # end main
+static void main() { 1757 var liname? = new List<int>()value or expression?;1758 livariableName? = new [] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}value or expression?; // assignment1759 Console.WriteLine(li[4]arguments?); // print statement1760 Console.WriteLine(li.subList(4, li.length())arguments?); // print statement1761 Console.WriteLine(li.subList(0, 7)arguments?); // print statement1762 } // end main
+Sub main() 1763 Dim liname? = New List(Of Integer)()value or expression? ' variable definition1764 livariableName? = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}value or expression? ' assignment1765 Console.WriteLine(li(4)arguments?) ' print statement1766 Console.WriteLine(li.subList(4, li.length())arguments?) ' print statement1767 Console.WriteLine(li.subList(0, 7)arguments?) ' print statement1768 End Sub
+static void main() { 1769 var liname? = new List<int>()value or expression?;1770 livariableName? = list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)value or expression?; // assignment1771 System.out.println(li[4]arguments?); // print statement1772 System.out.println(li.subList(4, li.length())arguments?); // print statement1773 System.out.println(li.subList(0, 7)arguments?); // print statement1774 } // end 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 the library function pow :

print(pow(4, 4)arguments?)5print(pow(4, 4)arguments?)6Console.WriteLine(pow(4, 4)arguments?); // print statement7Console.WriteLine(pow(4, 4)arguments?) ' print statement8System.out.println(pow(4, 4)arguments?); // print statement9
variable cname? set to sqrt(pow(a, 2) + pow(b, 2))value or expression?10cname? = sqrt(pow(a, 2) + pow(b, 2))value or expression? # variable definition11var cname? = sqrt(pow(a, 2) + pow(b, 2))value or expression?;12Dim cname? = sqrt(pow(a, 2) + pow(b, 2))value or expression? ' variable definition13var cname? = sqrt(pow(a, 2) + pow(b, 2))value or expression?;14

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 BooleanboolboolBooleanboolean arguments and return a BooleanboolboolBooleanboolean 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? 1775 return a and not b or b and not avalue or expression?1776 end function
+def exOrname?(a: bool, b: boolparameter definitions?) -> boolType?: # function1777 return a and not b or b and not avalue or expression?1778 # end function
+static boolType? exOrname?(bool a, bool bparameter definitions?) { // function1779 return a && !b || b && !avalue or expression?;1780 } // end function
+Function exOrname?(a As Boolean, b As Booleanparameter definitions?) As BooleanType? 1781 Return a And Not b Or b And Not avalue or expression?1782 End Function
+static booleanType? exOrname?(boolean a, boolean bparameter definitions?) { // function1783 return a && !b || b && !avalue or expression?;1784 } // end function

String operator

The + operator is also used for concatenating StringstrstringStringString values.

Equality testing

Testing equality of IntintintIntegerint, FloatfloatdoubleDoubledouble or BooleanboolboolBooleanboolean values

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

  • isnt returns the opposite of is.
  • (a is b)(a == b)(a == b)(a = b)(a == b) returns trueTruetrueTruetrue, if aaaaa and b are both of the same type and their values are equal. The only exception is that if one argument is of type FloatfloatdoubleDoubledouble and the other is of type IntintintIntegerint, then is will return trueTruetrueTruetrue (and isnt will return falseFalsefalseFalsefalse) if their values are the same, i.e. they are the same whole number.
  • It is also possible to use methods equals and notEqualTo described below.

Testing equality of StringstrstringStringString values and of all Reference Types

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

  • +if listA.equals(listB)condition? then 1785 new code end if
    +if listA.equals(listB)condition?: 1786 new code # end if
    +if (listA.equals(listB)condition?) { 1787 new code } // end if
    +If listA.equals(listB)condition? Then 1788 new code End If
    +if (listA.equals(listB)condition?) { 1789 new code } // end if
  • assign differsvariableName? to set1.notEqualTo(set2)value or expression?15differsvariableName? = set1.notEqualTo(set2)value or expression? # assignment16differsvariableName? = set1.notEqualTo(set2)value or expression?; // assignment17differsvariableName? = set1.notEqualTo(set2)value or expression? ' assignment18differsvariableName? = set1.notEqualTo(set2)value or expression?; // assignment19

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:

assign xvariableName? to (a > b) and (b < c)value or expression?20xvariableName? = (a > b) and (b < c)value or expression? # assignment21xvariableName? = (a > b) && (b < c)value or expression?; // assignment22xvariableName? = (a > b) And (b < c)value or expression? ' assignment23xvariableName? = (a > b) && (b < c)value or expression?; // assignment24
assign xvariableName? to (a + b) > (c - d)value or expression?25xvariableName? = (a + b) > (c - d)value or expression? # assignment26xvariableName? = (a + b) > (c - d)value or expression?; // assignment27xvariableName? = (a + b) > (c - d)value or expression? ' assignment28xvariableName? = (a + b) > (c - d)value or expression?; // assignment29

Function reference

An expression may simply be a reference to a function, or it may include several function references within it. Examples:
+main 1790 print(sin(radians(30))arguments?)1791 variable xname? set to pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression?1792 variable namename? set to input("Your name")value or expression?1793 print(name.upperCase()arguments?)1794 end main
+def main() -> None: 1795 print(sin(radians(30))arguments?)1796 xname? = pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression? # variable definition1797 namename? = input("Your name")value or expression? # variable definition1798 print(name.upperCase()arguments?)1799 # end main
+static void main() { 1800 Console.WriteLine(sin(radians(30))arguments?); // print statement1801 var xname? = pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression?;1802 var namename? = input("Your name")value or expression?;1803 Console.WriteLine(name.upperCase()arguments?); // print statement1804 } // end main
+Sub main() 1805 Console.WriteLine(sin(radians(30))arguments?) ' print statement1806 Dim xname? = pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression? ' variable definition1807 Dim namename? = input("Your name")value or expression? ' variable definition1808 Console.WriteLine(name.upperCase()arguments?) ' print statement1809 End Sub
+static void main() { 1810 System.out.println(sin(radians(30))arguments?); // print statement1811 var xname? = pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression?;1812 var namename? = input("Your name")value or expression?;1813 System.out.println(name.upperCase()arguments?); // print statement1814 } // end main

Notes

  • The third example above is not strictly a function call, but is a 'system method' call. System methods may be used only within the main routine or a procedure, because they have external dependencies or side effects.
  • In the fourth example, upperCaseupperCaseupperCaseupperCaseupperCase is a dot method that may be applied to any instance (variable or literal) of type StringstrstringStringString. See Dot methods on a String .

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.

A lambda may be thought of as a one-line function that is defined where it is used. Like a function it defines one or more parameters, and an expression that depends on the parameter value(s). Thus, the lambda:

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.

is equivalent to defining a function:

+function someNamename?(x as Floatparameter definitions?) returns FloatType? 1815 return x.round(8)value or expression?1816 end function
+def someNamename?(x: floatparameter definitions?) -> floatType?: # function1817 return x.round(8)value or expression?1818 # end function
+static doubleType? someNamename?(double xparameter definitions?) { // function1819 return x.round(8)value or expression?;1820 } // end function
+Function someNamename?(x As Doubleparameter definitions?) As DoubleType? 1821 Return x.round(8)value or expression?1822 End Function
+static doubleType? someNamename?(double xparameter definitions?) { // function1823 return x.round(8)value or expression?;1824 } // end function

The lambda above is usually verbalised as 'lambda x returns x.round(8)`

The advantages of using a lambda (where one is allowed) instead of defining a function are:

  • it is more succinct,
  • it is defined exactly where it is used, so you don't need to navigate to the function to see what it does,

Here are some more examples of the syntax for a lambda:

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.
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.

Note that currently it is necessary to specify the type of each parameter in a lambda explicitly. We hope to remove this requirement in a future version – to make the syntax for a lambda cleaner.

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

if expression

An 'if expression' has some similarity with an if statement, but with important differences:

  • An 'if expression' is written within a single line.
  • An 'if expression' always returns a value
  • An 'if expression' may be used where an expression is expected in code, for example to define the value to be assigned to a variable.
  • An 'if expression' may form part of a more complex expression.

All modern languages, including Python, VB, C#, and Java, support this concept, though sometimes by another name such as 'conditional expression', or even 'ternary operator' (because it has three terms). The syntax varies significantly between those languages. When coding with Elan in any of the supported languages, this functionality is provided by a 'pseudo function' named if_, The reason for the underscore is to distinguish the name from the if keyword used by all the languages as the start of an if statement. (This is consistent the standard advice that if you define your own identifier that classes with a reserved word, the simplest fix is to add an underscore on the end.)

The if_ function returns one of two values dependent upon the evaulation of a Boolean value:

Its syntax is shown here:

variable isBiggername? set to if_(a >= b, true, false)value or expression?30isBiggername? = if_(a >= b, True, False)value or expression? # variable definition31var isBiggername? = if_(a >= b, true, false)value or expression?;32Dim isBiggername? = if_(a >= b, True, False)value or expression? ' variable definition33var isBiggername? = if_(a >= b, true, false)value or expression?;34

Similarly, but returning an evaluated expression:

variable dname? set to if_(c < 580, c - 40, c + 60)value or expression?35dname? = if_(c < 580, c - 40, c + 60)value or expression? # variable definition36var dname? = if_(c < 580, c - 40, c + 60)value or expression?;37Dim dname? = if_(c < 580, c - 40, c + 60)value or expression? ' variable definition38var dname? = if_(c < 580, c - 40, c + 60)value or expression?;39

in which variable ddddd 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_ function may be used within an if_ function but may be hard to read and understand.

Why do we refer to if_ as a 'pseudo function'. At first glance it looks to operate like any function: it requires three arguments and returns a result that depends on them. But it does one thing very differently: if either or both of the second and third arguments are themselves defined by expressions, they are evaluated lazily – only the one that is is needed (depending on the evaluation of the condition argument) will be evaluated. It turns out that this is very important – without it can be very easy to end up with lots of 'Stack overflow errors'. So though if_ looks like any other library function, it is actually special syntax. And that is why you don't see if_ offered in the drop-down list of library function names.

Examples using if_ function:

   ● Choice in a return instruction:

+function fooname?(parameter definitionsparameter definitions?) returns IntType? 1825 return if_(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?1826 end function
+def fooname?(parameter definitionsparameter definitions?) -> intType?: # function1827 return if_(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?1828 # end function
+static intType? fooname?(parameter definitionsparameter definitions?) { // function1829 return if_(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?;1830 } // end function
+Function fooname?(parameter definitionsparameter definitions?) As IntegerType? 1831 Return if_(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?1832 End Function
+static intType? fooname?(parameter definitionsparameter definitions?) { // function1833 return if_(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?;1834 } // end function

   ● Using an if_ function in a procedure call print:

+main 1835 variable pname? set to unicode(0x03c0)value or expression?1836 call testPiprocedureName?(p, 3arguments?)1837 call testPiprocedureName?(p, 4arguments?)1838 end main
+def main() -> None: 1839 pname? = unicode(0x03c0)value or expression? # variable definition1840 testPiprocedureName?(p, 3arguments?) # procedure call1841 testPiprocedureName?(p, 4arguments?) # procedure call1842 # end main
+static void main() { 1843 var pname? = unicode(0x03c0)value or expression?;1844 testPiprocedureName?(p, 3arguments?); // procedure call1845 testPiprocedureName?(p, 4arguments?); // procedure call1846 } // end main
+Sub main() 1847 Dim pname? = unicode(&H03c0)value or expression? ' variable definition1848 testPiprocedureName?(p, 3arguments?) ' procedure call1849 testPiprocedureName?(p, 4arguments?) ' procedure call1850 End Sub
+static void main() { 1851 var pname? = unicode(0x03c0)value or expression?;1852 testPiprocedureName?(p, 3arguments?); // procedure call1853 testPiprocedureName?(p, 4arguments?); // procedure call1854 } // end main





π > 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 – procedural:

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

Miscellaneous

Dot syntax

Reference to a named value is in many cases made with dot syntax, also referred to as applying a dot method. Here are its uses:

  • To apply a function method to a named value, e.g. to obtain the length of StringstrstringStringString sssss using a library method:
    variable nname? set to s.length()value or expression?45nname? = s.length()value or expression? # variable definition46var nname? = s.length()value or expression?;47Dim nname? = s.length()value or expression? ' variable definition48var nname? = s.length()value or expression?;49
  • To apply a procedure to a named value, e.g. to initialise ListlistListListList lilililili using a library procedure:
    call li.initialiseprocedureName?(10, 0arguments?)50li.initialiseprocedureName?(10, 0arguments?) # procedure call51li.initialiseprocedureName?(10, 0arguments?); // procedure call52li.initialiseprocedureName?(10, 0arguments?) ' procedure call53li.initialiseprocedureName?(10, 0arguments?); // procedure call54
  • To refer to the property (bodybodybodybodybody) of an instance (gamegamegamegamegame) of a class and, in this example to get the length of the property's current string value, using library methods:
    variable bodyGLname? set to game.body.toString().length()value or expression?55bodyGLname? = game.body.toString().length()value or expression? # variable definition56var bodyGLname? = game.body.toString().length()value or expression?;57Dim bodyGLname? = game.body.toString().length()value or expression? ' variable definition58var bodyGLname? = game.body.toString().length()value or expression?;59
  • To extract an item from a Tuple using its special properties item_N, e.g. to get the first value from a 2-Tuple:
    variable pointname? set to (3, 7)value or expression?1855
    pointname? = (3, 7)value or expression? # variable definition1856
    var pointname? = (3, 7)value or expression?;1857
    Dim pointname? = (3, 7)value or expression? ' variable definition1858
    var pointname? = (3, 7)value or expression?;1859
  • To refer to a 'value' in an enum, e.g.to pick one of the identifiers in the enum SuitSuitSuitSuitSuit:
    enum SuitName? spades, hearts, diamonds, clubsvalues?values?1860
    class SuitName?(Enum):
    spades = 1
    hearts = 2
    diamonds = 3
    clubs = 4
    values?
    1861
    enum SuitName? {spades, hearts, diamonds, clubsvalues?values?}1862
    Enum SuitName?
    spades = 0
    hearts = 1
    diamonds = 2
    clubs = 3
    End Enum
    values?
    1863
    enum SuitName? {spades, hearts, diamonds, clubsvalues?values?}1864
    variable suitname? set to Suit.spadesvalue or expression?1865
    suitname? = Suit.spadesvalue or expression? # variable definition1866
    var suitname? = Suit.spadesvalue or expression?;1867
    Dim suitname? = Suit.spadesvalue or expression? ' variable definition1868
    var suitname? = Suit.spadesvalue or expression?;1869

You cannot use dot syntax with functions and procedures that you define at the global level, nor with some system functions (e.g. abs) and procedures (e.g. clearPrintedText).

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:

  • A literal value
  • A named value
  • An expression

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 statement equal instruction. Some more complex expressions are permissible, but these two restrictions apply:

  • Any expression involving a binary operator such as +, isnt, etc., must have brackets around it.
  • You may not use the is operator within the 'actual' field, because the parser will confuse this with the is keyword that is part of the assert statement equal instruction.

'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 procedure call instruction

Valid forms for a procedure call are

  • call procedureNameprocedureName?(arguments?)60procedureNameprocedureName?(arguments?) # procedure call61procedureNameprocedureName?(arguments?); // procedure call62procedureNameprocedureName?(arguments?) ' procedure call63procedureNameprocedureName?(arguments?); // procedure call64 to call a library procedure, or user-defined procedure.
  • call instanceName.procedureMethodNameprocedureName?(arguments?)65instanceName.procedureMethodNameprocedureName?(arguments?) # procedure call66instanceName.procedureMethodNameprocedureName?(arguments?); // procedure call67instanceName.procedureMethodNameprocedureName?(arguments?) ' procedure call68instanceName.procedureMethodNameprocedureName?(arguments?); // procedure call69 to call a procedure method associated with a specific type using 'dot-syntax'.

'Type' field in a function or property definition

For certain data structure types the name may be followed by a 'generic' clause, which specifies the types of the items that make up the data structure, for example:

List<of Int>list[int]List<int>List(Of Integer)List<int>
Dictionary<of String, Int>Dictionary[str, int]Dictionary<string, int>Dictionary(Of String, Integer)Dictionary<String, int> specifies that the keys are of type StingStingStingStingSting and the values of type 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 .