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 11 variable blocksname? set to createBlockGraphics(white)value or expression?12 variable xname? set to 20value or expression?13 variable yname? set to 15value or expression?14 +while truecondition? 15 assign blocks[x][y]variableName? to redvalue or expression?16 call displayBlocksprocedureName?(blocksarguments?)17 assign blocks[x][y]variableName? to blackvalue or expression?18 variable directionname? set to randint(0, 3)value or expression?19 +if direction is 0condition? then 20 assign xvariableName? to min([x + 1, 39])value or expression?21 elif direction is 1condition? then22 assign xvariableName? to max([x - 1, 0])value or expression?23 elif direction is 2condition? then24 assign yvariableName? to min([y + 1, 29])value or expression?25 elif direction is 3condition? then26 assign yvariableName? to max([y - 1, 0])value or expression?27 end if end while end main
+def main() -> None: 28 blocksname? = createBlockGraphics(white)value or expression? # variable definition29 xname? = 20value or expression? # variable definition30 yname? = 15value or expression? # variable definition31 +while Truecondition?: 32 blocks[x][y]variableName? = redvalue or expression? # assignment33 displayBlocksprocedureName?(blocksarguments?) # procedure call34 blocks[x][y]variableName? = blackvalue or expression? # assignment35 directionname? = randint(0, 3)value or expression? # variable definition36 +if direction == 0condition?: 37 xvariableName? = min([x + 1, 39])value or expression? # assignment38 elif direction == 1condition?: # else if39 xvariableName? = max([x - 1, 0])value or expression? # assignment40 elif direction == 2condition?: # else if41 yvariableName? = min([y + 1, 29])value or expression? # assignment42 elif direction == 3condition?: # else if43 yvariableName? = max([y - 1, 0])value or expression? # assignment44 # end if # end while # end main
+static void main() { 45 var blocksname? = createBlockGraphics(white)value or expression?;46 var xname? = 20value or expression?;47 var yname? = 15value or expression?;48 +while (truecondition?) { 49 blocks[x][y]variableName? = redvalue or expression?; // assignment50 displayBlocksprocedureName?(blocksarguments?); // procedure call51 blocks[x][y]variableName? = blackvalue or expression?; // assignment52 var directionname? = randint(0, 3)value or expression?;53 +if (direction == 0condition?) { 54 xvariableName? = min(new [] {x + 1, 39})value or expression?; // assignment55 } else if (direction == 1condition?) {56 xvariableName? = max(new [] {x - 1, 0})value or expression?; // assignment57 } else if (direction == 2condition?) {58 yvariableName? = min(new [] {y + 1, 29})value or expression?; // assignment59 } else if (direction == 3condition?) {60 yvariableName? = max(new [] {y - 1, 0})value or expression?; // assignment61 } // end if } // end while } // end main
+Sub main() 62 Dim blocksname? = createBlockGraphics(white)value or expression? ' variable definition63 Dim xname? = 20value or expression? ' variable definition64 Dim yname? = 15value or expression? ' variable definition65 +While Truecondition? 66 blocks(x)(y)variableName? = redvalue or expression? ' assignment67 displayBlocksprocedureName?(blocksarguments?) ' procedure call68 blocks(x)(y)variableName? = blackvalue or expression? ' assignment69 Dim directionname? = randint(0, 3)value or expression? ' variable definition70 +If direction = 0condition? Then 71 xvariableName? = min({x + 1, 39})value or expression? ' assignment72 ElseIf direction = 1condition? Then73 xvariableName? = max({x - 1, 0})value or expression? ' assignment74 ElseIf direction = 2condition? Then75 yvariableName? = min({y + 1, 29})value or expression? ' assignment76 ElseIf direction = 3condition? Then77 yvariableName? = max({y - 1, 0})value or expression? ' assignment78 End If End While End Sub
+static void main() { 79 var blocksname? = createBlockGraphics(white)value or expression?;80 var xname? = 20value or expression?;81 var yname? = 15value or expression?;82 +while (truecondition?) { 83 blocks[x][y]variableName? = redvalue or expression?; // assignment84 displayBlocksprocedureName?(blocksarguments?); // procedure call85 blocks[x][y]variableName? = blackvalue or expression?; // assignment86 var directionname? = randint(0, 3)value or expression?;87 +if (direction == 0condition?) { 88 xvariableName? = min(list(x + 1, 39))value or expression?; // assignment89 } else if (direction == 1condition?) {90 xvariableName? = max(list(x - 1, 0))value or expression?; // assignment91 } else if (direction == 2condition?) {92 yvariableName? = min(list(y + 1, 29))value or expression?; // assignment93 } else if (direction == 3condition?) {94 yvariableName? = max(list(y - 1, 0))value or expression?; // assignment95 } // end if } // end while } // end main

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

+main 96 variable liname? set to [3, 6, 1, 0, 99, 4, 67]value or expression?97 call inPlaceRippleSortprocedureName?(liarguments?)98 print(livalue or expression?)99 end main
+def main() -> None: 100 liname? = [3, 6, 1, 0, 99, 4, 67]value or expression? # variable definition101 inPlaceRippleSortprocedureName?(liarguments?) # procedure call102 print(livalue or expression?)103 # end main
+static void main() { 104 var liname? = new [] {3, 6, 1, 0, 99, 4, 67}value or expression?;105 inPlaceRippleSortprocedureName?(liarguments?); // procedure call106 Console.WriteLine(livalue or expression?); // print statement107 } // end main
+Sub main() 108 Dim liname? = {3, 6, 1, 0, 99, 4, 67}value or expression? ' variable definition109 inPlaceRippleSortprocedureName?(liarguments?) ' procedure call110 Console.WriteLine(livalue or expression?) ' print statement111 End Sub
+static void main() { 112 var liname? = list(3, 6, 1, 0, 99, 4, 67)value or expression?;113 inPlaceRippleSortprocedureName?(liarguments?); // procedure call114 System.out.println(livalue or expression?); // print statement115 } // 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 116 variable gridname? set to createBlockGraphics(white)value or expression?117 call fillRandomprocedureName?(gridarguments?)118 +while truecondition? 119 call displayBlocksprocedureName?(gridarguments?)120 variable gridRefname? set to new AsRef<of List<of List<of Int>>>(grid)value or expression?121 call nextGenerationprocedureName?(gridRefarguments?)122 assign gridvariableName? to gridRef.value()value or expression?123 call sleep_msprocedureName?(50arguments?)124 end while end main
+def main() -> None: 125 gridname? = createBlockGraphics(white)value or expression? # variable definition126 fillRandomprocedureName?(gridarguments?) # procedure call127 +while Truecondition?: 128 displayBlocksprocedureName?(gridarguments?) # procedure call129 gridRefname? = AsRef[list[list[int]]](grid)value or expression? # variable definition130 nextGenerationprocedureName?(gridRefarguments?) # procedure call131 gridvariableName? = gridRef.value()value or expression? # assignment132 sleep_msprocedureName?(50arguments?) # procedure call133 # end while # end main
+static void main() { 134 var gridname? = createBlockGraphics(white)value or expression?;135 fillRandomprocedureName?(gridarguments?); // procedure call136 +while (truecondition?) { 137 displayBlocksprocedureName?(gridarguments?); // procedure call138 var gridRefname? = new AsRef<List<List<int>>>(grid)value or expression?;139 nextGenerationprocedureName?(gridRefarguments?); // procedure call140 gridvariableName? = gridRef.value()value or expression?; // assignment141 sleep_msprocedureName?(50arguments?); // procedure call142 } // end while } // end main
+Sub main() 143 Dim gridname? = createBlockGraphics(white)value or expression? ' variable definition144 fillRandomprocedureName?(gridarguments?) ' procedure call145 +While Truecondition? 146 displayBlocksprocedureName?(gridarguments?) ' procedure call147 Dim gridRefname? = New AsRef(Of List(Of List(Of Integer)))(grid)value or expression? ' variable definition148 nextGenerationprocedureName?(gridRefarguments?) ' procedure call149 gridvariableName? = gridRef.value()value or expression? ' assignment150 sleep_msprocedureName?(50arguments?) ' procedure call151 End While End Sub
+static void main() { 152 var gridname? = createBlockGraphics(white)value or expression?;153 fillRandomprocedureName?(gridarguments?); // procedure call154 +while (truecondition?) { 155 displayBlocksprocedureName?(gridarguments?); // procedure call156 var gridRefname? = new AsRef<List<List<int>>>(grid)value or expression?;157 nextGenerationprocedureName?(gridRefarguments?); // procedure call158 gridvariableName? = gridRef.value()value or expression?; // assignment159 sleep_msprocedureName?(50arguments?); // procedure call160 } // 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 ListlistListListList:

+procedure moveGrowBurstname?(bubbles as List<of CircleVG>parameter definitions?) 161 +for bitem? in bubblessource? 162 +if random() < 0.05condition? then 163 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? call b.setRadiusprocedureName?(0arguments?)164 call b.setCentreYprocedureName?(75arguments?)165 else166 # bubble rises and grows slightlycomment? call b.setCentreYprocedureName?(b.centreY - 1arguments?)167 call b.setRadiusprocedureName?(b.radius + 0.2arguments?)168 end if end for call displayVectorGraphicsprocedureName?(bubblesarguments?)169 call sleep_msprocedureName?(5arguments?)170 end procedure
+def moveGrowBurstname?(bubbles: list[CircleVG]parameter definitions?) -> None: # procedure171 +for bitem? in bubblessource?: 172 +if random() < 0.05condition?: 173 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) # procedure call174 b.setCentreYprocedureName?(75arguments?) # procedure call175 else:176 # bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) # procedure call177 b.setRadiusprocedureName?(b.radius + 0.2arguments?) # procedure call178 # end if # end for displayVectorGraphicsprocedureName?(bubblesarguments?) # procedure call179 sleep_msprocedureName?(5arguments?) # procedure call180 # end procedure
+static void moveGrowBurstname?(List<CircleVG> bubblesparameter definitions?) { // procedure181 +foreach (var bitem? in bubblessource?) { 182 +if (random() < 0.05condition?) { 183 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // procedure call184 b.setCentreYprocedureName?(75arguments?); // procedure call185 } else {186 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // procedure call187 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // procedure call188 } // end if } // end foreach displayVectorGraphicsprocedureName?(bubblesarguments?); // procedure call189 sleep_msprocedureName?(5arguments?); // procedure call190 } // end procedure
+Sub moveGrowBurstname?(bubbles As List(Of CircleVG)parameter definitions?) ' procedure191 +For Each bitem? In bubblessource? 192 +If random() < 0.05condition? Then 193 ' 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) ' procedure call194 b.setCentreYprocedureName?(75arguments?) ' procedure call195 Else196 ' bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) ' procedure call197 b.setRadiusprocedureName?(b.radius + 0.2arguments?) ' procedure call198 End If Next b displayVectorGraphicsprocedureName?(bubblesarguments?) ' procedure call199 sleep_msprocedureName?(5arguments?) ' procedure call200 End Sub
+static void moveGrowBurstname?(List<CircleVG> bubblesparameter definitions?) { // procedure201 +foreach (var bitem? in bubblessource?) { 202 +if (random() < 0.05condition?) { 203 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // procedure call204 b.setCentreYprocedureName?(75arguments?); // procedure call205 } else {206 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // procedure call207 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // procedure call208 } // end if } // end foreach displayVectorGraphicsprocedureName?(bubblesarguments?); // procedure call209 sleep_msprocedureName?(5arguments?); // procedure call210 } // 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?) 211 call t.turnToHeadingprocedureName?(180 + order*45arguments?)212 call t.placeAtprocedureName?(-40, 20arguments?)213 call t.penColourprocedureName?(redarguments?)214 call t.penWidthprocedureName?(10.0/orderarguments?)215 call t.penDownprocedureName?(arguments?)216 call t.showprocedureName?(arguments?)217 end procedure
+def setupTurtlename?(t: Turtle, order: intparameter definitions?) -> None: # procedure218 t.turnToHeadingprocedureName?(180 + order*45arguments?) # procedure call219 t.placeAtprocedureName?(-40, 20arguments?) # procedure call220 t.penColourprocedureName?(redarguments?) # procedure call221 t.penWidthprocedureName?(10.0/orderarguments?) # procedure call222 t.penDownprocedureName?(arguments?) # procedure call223 t.showprocedureName?(arguments?) # procedure call224 # end procedure
+static void setupTurtlename?(Turtle t, int orderparameter definitions?) { // procedure225 t.turnToHeadingprocedureName?(180 + order*45arguments?); // procedure call226 t.placeAtprocedureName?(-40, 20arguments?); // procedure call227 t.penColourprocedureName?(redarguments?); // procedure call228 t.penWidthprocedureName?(10.0/orderarguments?); // procedure call229 t.penDownprocedureName?(arguments?); // procedure call230 t.showprocedureName?(arguments?); // procedure call231 } // end procedure
+Sub setupTurtlename?(t As Turtle, order As Integerparameter definitions?) ' procedure232 t.turnToHeadingprocedureName?(180 + order*45arguments?) ' procedure call233 t.placeAtprocedureName?(-40, 20arguments?) ' procedure call234 t.penColourprocedureName?(redarguments?) ' procedure call235 t.penWidthprocedureName?(10.0/orderarguments?) ' procedure call236 t.penDownprocedureName?(arguments?) ' procedure call237 t.showprocedureName?(arguments?) ' procedure call238 End Sub
+static void setupTurtlename?(Turtle t, int orderparameter definitions?) { // procedure239 t.turnToHeadingprocedureName?(180 + order*45arguments?); // procedure call240 t.placeAtprocedureName?(-40, 20arguments?); // procedure call241 t.penColourprocedureName?(redarguments?); // procedure call242 t.penWidthprocedureName?(10.0/orderarguments?); // procedure call243 t.penDownprocedureName?(arguments?); // procedure call244 t.showprocedureName?(arguments?); // procedure call245 } // 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?) 246 +for colitem? in range(0, 40)source? 247 +for rowitem? in range(0, 30)source? 248 assign grid[col][row]variableName? to blackOrWhite(random())value or expression?249 end for end for end procedure
+def fillRandomname?(grid: list[list[int]]parameter definitions?) -> None: # procedure250 +for colitem? in range(0, 40)source?: 251 +for rowitem? in range(0, 30)source?: 252 grid[col][row]variableName? = blackOrWhite(random())value or expression? # assignment253 # end for # end for # end procedure
+static void fillRandomname?(List<List<int>> gridparameter definitions?) { // procedure254 +foreach (var colitem? in range(0, 40)source?) { 255 +foreach (var rowitem? in range(0, 30)source?) { 256 grid[col][row]variableName? = blackOrWhite(random())value or expression?; // assignment257 } // end foreach } // end foreach } // end procedure
+Sub fillRandomname?(grid As List(Of List(Of Integer))parameter definitions?) ' procedure258 +For Each colitem? In range(0, 40)source? 259 +For Each rowitem? In range(0, 30)source? 260 grid(col)(row)variableName? = blackOrWhite(random())value or expression? ' assignment261 Next row Next col End Sub
+static void fillRandomname?(List<List<int>> gridparameter definitions?) { // procedure262 +foreach (var colitem? in range(0, 40)source?) { 263 +foreach (var rowitem? in range(0, 30)source?) { 264 grid[col][row]variableName? = blackOrWhite(random())value or expression?; // assignment265 } // 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 bubbles as List<of CircleVG>bubbles: list[CircleVG]List<CircleVG> bubblesbubbles As List(Of CircleVG)List<CircleVG> bubbles
  • 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 ListlistListListList 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 put (supersedes deprecated 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 1 variable argname? set to "abc"value or expression?2 variable argRefname? set to new AsRef<of String>(arg)value or expression?3 call changeArgprocedureName?(argRefarguments?)4 print(argRef.value()value or expression?)5 end main +procedure changeArgname?(pointer as AsRef<of String>parameter definitions?) 6 variable rname? set to pointer.value()value or expression?7 call pointer.setprocedureName?(r.upperCase()arguments?)8 end procedure
+def main() -> None: 1 argname? = "abc"value or expression? # variable definition2 argRefname? = AsRef[str](arg)value or expression? # variable definition3 changeArgprocedureName?(argRefarguments?) # procedure call4 print(argRef.value()value or expression?)5 # end main +def changeArgname?(pointer: AsRef[str]parameter definitions?) -> None: # procedure6 rname? = pointer.value()value or expression? # variable definition7 pointer.setprocedureName?(r.upperCase()arguments?) # procedure call8 # end procedure main()
+static void main() { 1 var argname? = "abc"value or expression?;2 var argRefname? = new AsRef<string>(arg)value or expression?;3 changeArgprocedureName?(argRefarguments?); // procedure call4 Console.WriteLine(argRef.value()value or expression?); // print statement5 } // end main +static void changeArgname?(AsRef<string> pointerparameter definitions?) { // procedure6 var rname? = pointer.value()value or expression?;7 pointer.setprocedureName?(r.upperCase()arguments?); // procedure call8 } // end procedure
+Sub main() 1 Dim argname? = "abc"value or expression? ' variable definition2 Dim argRefname? = New AsRef(Of String)(arg)value or expression? ' variable definition3 changeArgprocedureName?(argRefarguments?) ' procedure call4 Console.WriteLine(argRef.value()value or expression?) ' print statement5 End Sub +Sub changeArgname?(pointer As AsRef(Of String)parameter definitions?) ' procedure6 Dim rname? = pointer.value()value or expression? ' variable definition7 pointer.setprocedureName?(r.upperCase()arguments?) ' procedure call8 End Sub
public class Global {

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

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?) 9 +if (length > 1)condition? then 10 variable thirdname? set to length/3value or expression?11 call drawSideprocedureName?(third, targuments?)12 call t.turnprocedureName?(-60arguments?)13 call drawSideprocedureName?(third, targuments?)14 call t.turnprocedureName?(120arguments?)15 call drawSideprocedureName?(third, targuments?)16 call t.turnprocedureName?(-60arguments?)17 call drawSideprocedureName?(third, targuments?)18 else19 call t.moveprocedureName?(lengtharguments?)20 end if end procedure
+def drawSidename?(length: float, t: Turtleparameter definitions?) -> None: # procedure21 +if (length > 1)condition?: 22 thirdname? = length/3value or expression? # variable definition23 drawSideprocedureName?(third, targuments?) # procedure call24 t.turnprocedureName?(-60arguments?) # procedure call25 drawSideprocedureName?(third, targuments?) # procedure call26 t.turnprocedureName?(120arguments?) # procedure call27 drawSideprocedureName?(third, targuments?) # procedure call28 t.turnprocedureName?(-60arguments?) # procedure call29 drawSideprocedureName?(third, targuments?) # procedure call30 else:31 t.moveprocedureName?(lengtharguments?) # procedure call32 # end if # end procedure
+static void drawSidename?(double length, Turtle tparameter definitions?) { // procedure33 +if ((length > 1)condition?) { 34 var thirdname? = length/3value or expression?;35 drawSideprocedureName?(third, targuments?); // procedure call36 t.turnprocedureName?(-60arguments?); // procedure call37 drawSideprocedureName?(third, targuments?); // procedure call38 t.turnprocedureName?(120arguments?); // procedure call39 drawSideprocedureName?(third, targuments?); // procedure call40 t.turnprocedureName?(-60arguments?); // procedure call41 drawSideprocedureName?(third, targuments?); // procedure call42 } else {43 t.moveprocedureName?(lengtharguments?); // procedure call44 } // end if } // end procedure
+Sub drawSidename?(length As Double, t As Turtleparameter definitions?) ' procedure45 +If (length > 1)condition? Then 46 Dim thirdname? = length/3value or expression? ' variable definition47 drawSideprocedureName?(third, targuments?) ' procedure call48 t.turnprocedureName?(-60arguments?) ' procedure call49 drawSideprocedureName?(third, targuments?) ' procedure call50 t.turnprocedureName?(120arguments?) ' procedure call51 drawSideprocedureName?(third, targuments?) ' procedure call52 t.turnprocedureName?(-60arguments?) ' procedure call53 drawSideprocedureName?(third, targuments?) ' procedure call54 Else55 t.moveprocedureName?(lengtharguments?) ' procedure call56 End If End Sub
+static void drawSidename?(double length, Turtle tparameter definitions?) { // procedure57 +if ((length > 1)condition?) { 58 var thirdname? = length/3value or expression?;59 drawSideprocedureName?(third, targuments?); // procedure call60 t.turnprocedureName?(-60arguments?); // procedure call61 drawSideprocedureName?(third, targuments?); // procedure call62 t.turnprocedureName?(120arguments?); // procedure call63 drawSideprocedureName?(third, targuments?); // procedure call64 t.turnprocedureName?(-60arguments?); // procedure call65 drawSideprocedureName?(third, targuments?); // procedure call66 } else {67 t.moveprocedureName?(lengtharguments?); // procedure call68 } // 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? 69 return (headX < 0) or (headY < 0) or (headX > 39) or (headY > 29)value or expression?70 end function
+def hasHitEdgename?(headX: int, headY: intparameter definitions?) -> boolType?: # function71 return (headX < 0) or (headY < 0) or (headX > 39) or (headY > 29)value or expression?72 # end function
+static boolType? hasHitEdgename?(int headX, int headYparameter definitions?) { // function73 return (headX < 0) || (headY < 0) || (headX > 39) || (headY > 29)value or expression?;74 } // end function
+Function hasHitEdgename?(headX As Integer, headY As Integerparameter definitions?) As BooleanType? 75 Return (headX < 0) Or (headY < 0) Or (headX > 39) Or (headY > 29)value or expression?76 End Function
+static booleanType? hasHitEdgename?(int headX, int headYparameter definitions?) { // function77 return (headX < 0) || (headY < 0) || (headX > 39) || (headY > 29)value or expression?;78 } // 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?79
gameOnvariableName? = not hasHitEdge(head[0], head[1]) and not body.contains(head)value or expression? # assignment80
gameOnvariableName? = !hasHitEdge(head[0], head[1]) && !body.contains(head)value or expression?; // assignment81
gameOnvariableName? = Not hasHitEdge(head(0), head(1)) And Not body.contains(head)value or expression? ' assignment82
gameOnvariableName? = !hasHitEdge(head[0], head[1]) && !body.contains(head)value or expression?; // assignment83

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? 94 variable xname? set to cell.item_0value or expression?95 variable yname? set to cell.item_1value or expression?96 variable y2name? set to y - 1value or expression?97 +if y2 is -1condition? then 98 assign y2variableName? to 29value or expression?99 end if return (x, y2)value or expression?100 end function
+def northname?(cell: tuple[int, int]parameter definitions?) -> tuple[int, int]Type?: # function101 xname? = cell.item_0value or expression? # variable definition102 yname? = cell.item_1value or expression? # variable definition103 y2name? = y - 1value or expression? # variable definition104 +if y2 == -1condition?: 105 y2variableName? = 29value or expression? # assignment106 # end if return (x, y2)value or expression?107 # end function
+static (int, int)Type? northname?((int, int) cellparameter definitions?) { // function108 var xname? = cell.item_0value or expression?;109 var yname? = cell.item_1value or expression?;110 var y2name? = y - 1value or expression?;111 +if (y2 == -1condition?) { 112 y2variableName? = 29value or expression?; // assignment113 } // end if return (x, y2)value or expression?;114 } // end function
+Function northname?(cell As (Integer, Integer)parameter definitions?) As (Integer, Integer)Type? 115 Dim xname? = cell.item_0value or expression? ' variable definition116 Dim yname? = cell.item_1value or expression? ' variable definition117 Dim y2name? = y - 1value or expression? ' variable definition118 +If y2 = -1condition? Then 119 y2variableName? = 29value or expression? ' assignment120 End If Return (x, y2)value or expression?121 End Function
+static (int, int)Type? northname?((int, int) cellparameter definitions?) { // function122 var xname? = cell.item_0value or expression?;123 var yname? = cell.item_1value or expression?;124 var y2name? = y - 1value or expression?;125 +if (y2 == -1condition?) { 126 y2variableName? = 29value or expression?; // assignment127 } // end if return (x, y2)value or expression?;128 } // end function

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

+function binarySearchname?(li as List<of String>, item as Stringparameter definitions?) returns BooleanType? 129 variable resultname? set to falsevalue or expression?130 +if li.length() > 0condition? then 131 variable midname? set to divAsInt(li.length(), 2)value or expression?132 variable valuename? set to li[mid]value or expression?133 +if item.equals(value)condition? then 134 assign resultvariableName? to truevalue or expression?135 elif item.isBefore(value)condition? then136 assign resultvariableName? to binarySearch(li.subList(0, mid), item)value or expression?137 else138 assign resultvariableName? to binarySearch(li.subList(mid + 1, li.length()), item)value or expression?139 end if end if return resultvalue or expression?140 end function
+def binarySearchname?(li: list[str], item: strparameter definitions?) -> boolType?: # function141 resultname? = Falsevalue or expression? # variable definition142 +if li.length() > 0condition?: 143 midname? = divAsInt(li.length(), 2)value or expression? # variable definition144 valuename? = li[mid]value or expression? # variable definition145 +if item.equals(value)condition?: 146 resultvariableName? = Truevalue or expression? # assignment147 elif item.isBefore(value)condition?: # else if148 resultvariableName? = binarySearch(li.subList(0, mid), item)value or expression? # assignment149 else:150 resultvariableName? = binarySearch(li.subList(mid + 1, li.length()), item)value or expression? # assignment151 # end if # end if return resultvalue or expression?152 # end function
+static boolType? binarySearchname?(List<string> li, string itemparameter definitions?) { // function153 var resultname? = falsevalue or expression?;154 +if (li.length() > 0condition?) { 155 var midname? = divAsInt(li.length(), 2)value or expression?;156 var valuename? = li[mid]value or expression?;157 +if (item.equals(value)condition?) { 158 resultvariableName? = truevalue or expression?; // assignment159 } else if (item.isBefore(value)condition?) {160 resultvariableName? = binarySearch(li.subList(0, mid), item)value or expression?; // assignment161 } else {162 resultvariableName? = binarySearch(li.subList(mid + 1, li.length()), item)value or expression?; // assignment163 } // end if } // end if return resultvalue or expression?;164 } // end function
+Function binarySearchname?(li As List(Of String), item As Stringparameter definitions?) As BooleanType? 165 Dim resultname? = Falsevalue or expression? ' variable definition166 +If li.length() > 0condition? Then 167 Dim midname? = divAsInt(li.length(), 2)value or expression? ' variable definition168 Dim valuename? = li(mid)value or expression? ' variable definition169 +If item.equals(value)condition? Then 170 resultvariableName? = Truevalue or expression? ' assignment171 ElseIf item.isBefore(value)condition? Then172 resultvariableName? = binarySearch(li.subList(0, mid), item)value or expression? ' assignment173 Else174 resultvariableName? = binarySearch(li.subList(mid + 1, li.length()), item)value or expression? ' assignment175 End If End If Return resultvalue or expression?176 End Function
+static booleanType? binarySearchname?(List<String> li, String itemparameter definitions?) { // function177 var resultname? = falsevalue or expression?;178 +if (li.length() > 0condition?) { 179 var midname? = divAsInt(li.length(), 2)value or expression?;180 var valuename? = li[mid]value or expression?;181 +if (item.equals(value)condition?) { 182 resultvariableName? = truevalue or expression?; // assignment183 } else if (item.isBefore(value)condition?) {184 resultvariableName? = binarySearch(li.subList(0, mid), item)value or expression?; // assignment185 } else {186 resultvariableName? = binarySearch(li.subList(mid + 1, li.length()), item)value or expression?; // assignment187 } // end if } // end if return resultvalue or expression?;188 } // 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 headX as IntheadX: intint headXheadX As Integerint headX
  • 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? 189 return if_(n > 1, n*factorial(n - 1), 1)value or expression?190 end function
+def factorialname?(n: intparameter definitions?) -> intType?: # function191 return if_(n > 1, n*factorial(n - 1), 1)value or expression?192 # end function
+static intType? factorialname?(int nparameter definitions?) { // function193 return if_(n > 1, n*factorial(n - 1), 1)value or expression?;194 } // end function
+Function factorialname?(n As Integerparameter definitions?) As IntegerType? 195 Return if_(n > 1, n*factorial(n - 1), 1)value or expression?196 End Function
+static intType? factorialname?(int nparameter definitions?) { // function197 return if_(n > 1, n*factorial(n - 1), 1)value or expression?;198 } // 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? 224 assert willLive(white, 0)actual (computed) value? evaluates to falseexpected value? not run225 assert willLive(white, 1)actual (computed) value? evaluates to falseexpected value? not run226 assert willLive(white, 2)actual (computed) value? evaluates to falseexpected value? not run227 assert willLive(white, 3)actual (computed) value? evaluates to trueexpected value? not run228 assert willLive(white, 4)actual (computed) value? evaluates to falseexpected value? not run229 assert willLive(white, 5)actual (computed) value? evaluates to falseexpected value? not run230 assert willLive(white, 6)actual (computed) value? evaluates to falseexpected value? not run231 assert willLive(white, 7)actual (computed) value? evaluates to falseexpected value? not run232 assert willLive(white, 8)actual (computed) value? evaluates to falseexpected value? not run233 assert willLive(black, 0)actual (computed) value? evaluates to falseexpected value? not run234 assert willLive(black, 1)actual (computed) value? evaluates to falseexpected value? not run235 assert willLive(black, 2)actual (computed) value? evaluates to trueexpected value? not run236 assert willLive(black, 3)actual (computed) value? evaluates to trueexpected value? not run237 assert willLive(black, 4)actual (computed) value? evaluates to falseexpected value? not run238 assert willLive(black, 5)actual (computed) value? evaluates to falseexpected value? not run239 assert willLive(black, 6)actual (computed) value? evaluates to falseexpected value? not run240 assert willLive(black, 7)actual (computed) value? evaluates to falseexpected value? not run241 assert willLive(black, 8)actual (computed) value? evaluates to falseexpected value? not run242 end test
+class Test_willLive(unittest.TestCase):
 def test_willLivetest_name?(self) -> None: 243
self.assertEqual(willLive(white, 0)actual (computed) value?, Falseexpected value?) not run244 self.assertEqual(willLive(white, 1)actual (computed) value?, Falseexpected value?) not run245 self.assertEqual(willLive(white, 2)actual (computed) value?, Falseexpected value?) not run246 self.assertEqual(willLive(white, 3)actual (computed) value?, Trueexpected value?) not run247 self.assertEqual(willLive(white, 4)actual (computed) value?, Falseexpected value?) not run248 self.assertEqual(willLive(white, 5)actual (computed) value?, Falseexpected value?) not run249 self.assertEqual(willLive(white, 6)actual (computed) value?, Falseexpected value?) not run250 self.assertEqual(willLive(white, 7)actual (computed) value?, Falseexpected value?) not run251 self.assertEqual(willLive(white, 8)actual (computed) value?, Falseexpected value?) not run252 self.assertEqual(willLive(black, 0)actual (computed) value?, Falseexpected value?) not run253 self.assertEqual(willLive(black, 1)actual (computed) value?, Falseexpected value?) not run254 self.assertEqual(willLive(black, 2)actual (computed) value?, Trueexpected value?) not run255 self.assertEqual(willLive(black, 3)actual (computed) value?, Trueexpected value?) not run256 self.assertEqual(willLive(black, 4)actual (computed) value?, Falseexpected value?) not run257 self.assertEqual(willLive(black, 5)actual (computed) value?, Falseexpected value?) not run258 self.assertEqual(willLive(black, 6)actual (computed) value?, Falseexpected value?) not run259 self.assertEqual(willLive(black, 7)actual (computed) value?, Falseexpected value?) not run260 self.assertEqual(willLive(black, 8)actual (computed) value?, Falseexpected value?) not run261 # end test
+[TestClass] class Test_willLive
[TestMethod] static void test_willLivetest_name?() { 262
Assert.AreEqual(falseexpected value?, willLive(white, 0)actual (computed) value?); not run263 Assert.AreEqual(falseexpected value?, willLive(white, 1)actual (computed) value?); not run264 Assert.AreEqual(falseexpected value?, willLive(white, 2)actual (computed) value?); not run265 Assert.AreEqual(trueexpected value?, willLive(white, 3)actual (computed) value?); not run266 Assert.AreEqual(falseexpected value?, willLive(white, 4)actual (computed) value?); not run267 Assert.AreEqual(falseexpected value?, willLive(white, 5)actual (computed) value?); not run268 Assert.AreEqual(falseexpected value?, willLive(white, 6)actual (computed) value?); not run269 Assert.AreEqual(falseexpected value?, willLive(white, 7)actual (computed) value?); not run270 Assert.AreEqual(falseexpected value?, willLive(white, 8)actual (computed) value?); not run271 Assert.AreEqual(falseexpected value?, willLive(black, 0)actual (computed) value?); not run272 Assert.AreEqual(falseexpected value?, willLive(black, 1)actual (computed) value?); not run273 Assert.AreEqual(trueexpected value?, willLive(black, 2)actual (computed) value?); not run274 Assert.AreEqual(trueexpected value?, willLive(black, 3)actual (computed) value?); not run275 Assert.AreEqual(falseexpected value?, willLive(black, 4)actual (computed) value?); not run276 Assert.AreEqual(falseexpected value?, willLive(black, 5)actual (computed) value?); not run277 Assert.AreEqual(falseexpected value?, willLive(black, 6)actual (computed) value?); not run278 Assert.AreEqual(falseexpected value?, willLive(black, 7)actual (computed) value?); not run279 Assert.AreEqual(falseexpected value?, willLive(black, 8)actual (computed) value?); not run280 }} // end test
+<TestClass Class Test_willLive
 <TestMethod> Sub test_willLivetest_name?() 281
Assert.AreEqual(Falseexpected value?, willLive(white, 0)actual (computed) value?) not run282 Assert.AreEqual(Falseexpected value?, willLive(white, 1)actual (computed) value?) not run283 Assert.AreEqual(Falseexpected value?, willLive(white, 2)actual (computed) value?) not run284 Assert.AreEqual(Trueexpected value?, willLive(white, 3)actual (computed) value?) not run285 Assert.AreEqual(Falseexpected value?, willLive(white, 4)actual (computed) value?) not run286 Assert.AreEqual(Falseexpected value?, willLive(white, 5)actual (computed) value?) not run287 Assert.AreEqual(Falseexpected value?, willLive(white, 6)actual (computed) value?) not run288 Assert.AreEqual(Falseexpected value?, willLive(white, 7)actual (computed) value?) not run289 Assert.AreEqual(Falseexpected value?, willLive(white, 8)actual (computed) value?) not run290 Assert.AreEqual(Falseexpected value?, willLive(black, 0)actual (computed) value?) not run291 Assert.AreEqual(Falseexpected value?, willLive(black, 1)actual (computed) value?) not run292 Assert.AreEqual(Trueexpected value?, willLive(black, 2)actual (computed) value?) not run293 Assert.AreEqual(Trueexpected value?, willLive(black, 3)actual (computed) value?) not run294 Assert.AreEqual(Falseexpected value?, willLive(black, 4)actual (computed) value?) not run295 Assert.AreEqual(Falseexpected value?, willLive(black, 5)actual (computed) value?) not run296 Assert.AreEqual(Falseexpected value?, willLive(black, 6)actual (computed) value?) not run297 Assert.AreEqual(Falseexpected value?, willLive(black, 7)actual (computed) value?) not run298 Assert.AreEqual(Falseexpected value?, willLive(black, 8)actual (computed) value?) not run299  End Sub
End Class
+class Test_willLive {
@Test static void test_willLivetest_name?() { 300
assertEquals(falseexpected value?, willLive(white, 0)actual (computed) value?); not run301 assertEquals(falseexpected value?, willLive(white, 1)actual (computed) value?); not run302 assertEquals(falseexpected value?, willLive(white, 2)actual (computed) value?); not run303 assertEquals(trueexpected value?, willLive(white, 3)actual (computed) value?); not run304 assertEquals(falseexpected value?, willLive(white, 4)actual (computed) value?); not run305 assertEquals(falseexpected value?, willLive(white, 5)actual (computed) value?); not run306 assertEquals(falseexpected value?, willLive(white, 6)actual (computed) value?); not run307 assertEquals(falseexpected value?, willLive(white, 7)actual (computed) value?); not run308 assertEquals(falseexpected value?, willLive(white, 8)actual (computed) value?); not run309 assertEquals(falseexpected value?, willLive(black, 0)actual (computed) value?); not run310 assertEquals(falseexpected value?, willLive(black, 1)actual (computed) value?); not run311 assertEquals(trueexpected value?, willLive(black, 2)actual (computed) value?); not run312 assertEquals(trueexpected value?, willLive(black, 3)actual (computed) value?); not run313 assertEquals(falseexpected value?, willLive(black, 4)actual (computed) value?); not run314 assertEquals(falseexpected value?, willLive(black, 5)actual (computed) value?); not run315 assertEquals(falseexpected value?, willLive(black, 6)actual (computed) value?); not run316 assertEquals(falseexpected value?, willLive(black, 7)actual (computed) value?); not run317 assertEquals(falseexpected value?, willLive(black, 8)actual (computed) value?); not run318 }} // 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? 319 variable sqname? set to [20, 15]value or expression?320 assert getAdjacentSquare(sq, Direction.up)actual (computed) value? evaluates to [20, 14]expected value? not run321 assert getAdjacentSquare(sq, Direction.down)actual (computed) value? evaluates to [20, 16]expected value? not run322 assert getAdjacentSquare(sq, Direction.left)actual (computed) value? evaluates to [19, 15]expected value? not run323 assert getAdjacentSquare(sq, Direction.right)actual (computed) value? evaluates to [21, 15]expected value? not run324 # boundarycomment? assert getAdjacentSquare([0, 15], Direction.left)actual (computed) value? evaluates to [-1, 15]expected value? not run325 end test
+class Test_getAdjacentSquare(unittest.TestCase):
 def test_getAdjacentSquaretest_name?(self) -> None: 326
sqname? = [20, 15]value or expression? # variable definition327 self.assertEqual(getAdjacentSquare(sq, Direction.up)actual (computed) value?, [20, 14]expected value?) not run328 self.assertEqual(getAdjacentSquare(sq, Direction.down)actual (computed) value?, [20, 16]expected value?) not run329 self.assertEqual(getAdjacentSquare(sq, Direction.left)actual (computed) value?, [19, 15]expected value?) not run330 self.assertEqual(getAdjacentSquare(sq, Direction.right)actual (computed) value?, [21, 15]expected value?) not run331 # boundarycomment? self.assertEqual(getAdjacentSquare([0, 15], Direction.left)actual (computed) value?, [-1, 15]expected value?) not run332 # end test
+[TestClass] class Test_getAdjacentSquare
[TestMethod] static void test_getAdjacentSquaretest_name?() { 333
var sqname? = new [] {20, 15}value or expression?;334 Assert.AreEqual(new [] {20, 14}expected value?, getAdjacentSquare(sq, Direction.up)actual (computed) value?); not run335 Assert.AreEqual(new [] {20, 16}expected value?, getAdjacentSquare(sq, Direction.down)actual (computed) value?); not run336 Assert.AreEqual(new [] {19, 15}expected value?, getAdjacentSquare(sq, Direction.left)actual (computed) value?); not run337 Assert.AreEqual(new [] {21, 15}expected value?, getAdjacentSquare(sq, Direction.right)actual (computed) value?); not run338 // boundarycomment? Assert.AreEqual(new [] {-1, 15}expected value?, getAdjacentSquare(new [] {0, 15}, Direction.left)actual (computed) value?); not run339 }} // end test
+<TestClass Class Test_getAdjacentSquare
 <TestMethod> Sub test_getAdjacentSquaretest_name?() 340
Dim sqname? = {20, 15}value or expression? ' variable definition341 Assert.AreEqual({20, 14}expected value?, getAdjacentSquare(sq, Direction.up)actual (computed) value?) not run342 Assert.AreEqual({20, 16}expected value?, getAdjacentSquare(sq, Direction.down)actual (computed) value?) not run343 Assert.AreEqual({19, 15}expected value?, getAdjacentSquare(sq, Direction.left)actual (computed) value?) not run344 Assert.AreEqual({21, 15}expected value?, getAdjacentSquare(sq, Direction.right)actual (computed) value?) not run345 ' boundarycomment? Assert.AreEqual({-1, 15}expected value?, getAdjacentSquare({0, 15}, Direction.left)actual (computed) value?) not run346  End Sub
End Class
+class Test_getAdjacentSquare {
@Test static void test_getAdjacentSquaretest_name?() { 347
var sqname? = list(20, 15)value or expression?;348 assertEquals(list(20, 14)expected value?, getAdjacentSquare(sq, Direction.up)actual (computed) value?); not run349 assertEquals(list(20, 16)expected value?, getAdjacentSquare(sq, Direction.down)actual (computed) value?); not run350 assertEquals(list(19, 15)expected value?, getAdjacentSquare(sq, Direction.left)actual (computed) value?); not run351 assertEquals(list(21, 15)expected value?, getAdjacentSquare(sq, Direction.right)actual (computed) value?); not run352 // boundarycomment? assertEquals(list(-1, 15)expected value?, getAdjacentSquare(list(0, 15), Direction.left)actual (computed) value?); not run353 }} // 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? evaluates to falseexpected value? not run variable g3name? set to g2.withHead(new Square(23, 15))value or expression? assert headOverApple(g3)actual (computed) value? evaluates to 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? 354 variable g1name? set to new Game(new Random())value or expression?355 variable g2name? set to g1.withApple(new Square(23, 15))value or expression?356 assert headOverApple(g2)actual (computed) value? evaluates to trueexpected value? not run variable g3name? set to g2.withHead(new Square(23, 15))value or expression?357 assert headOverApple(g3)actual (computed) value? evaluates to trueexpected value? not run358 end test
+class Test_overApple(unittest.TestCase):
 def test_overAppletest_name?(self) -> None: 359
g1name? = Game(Random())value or expression? # variable definition360 g2name? = g1.withApple(Square(23, 15))value or expression? # variable definition361 self.assertEqual(headOverApple(g2)actual (computed) value?, Trueexpected value?) not run g3name? = g2.withHead(Square(23, 15))value or expression? # variable definition362 self.assertEqual(headOverApple(g3)actual (computed) value?, Trueexpected value?) not run363 # end test
+[TestClass] class Test_overApple
[TestMethod] static void test_overAppletest_name?() { 364
var g1name? = new Game(new Random())value or expression?;365 var g2name? = g1.withApple(new Square(23, 15))value or expression?;366 Assert.AreEqual(trueexpected value?, headOverApple(g2)actual (computed) value?); not run var g3name? = g2.withHead(new Square(23, 15))value or expression?;367 Assert.AreEqual(trueexpected value?, headOverApple(g3)actual (computed) value?); not run368 }} // end test
+<TestClass Class Test_overApple
 <TestMethod> Sub test_overAppletest_name?() 369
Dim g1name? = New Game(New Random())value or expression? ' variable definition370 Dim g2name? = g1.withApple(New Square(23, 15))value or expression? ' variable definition371 Assert.AreEqual(Trueexpected value?, headOverApple(g2)actual (computed) value?) not run Dim g3name? = g2.withHead(New Square(23, 15))value or expression? ' variable definition372 Assert.AreEqual(Trueexpected value?, headOverApple(g3)actual (computed) value?) not run373  End Sub
End Class
+class Test_overApple {
@Test static void test_overAppletest_name?() { 374
var g1name? = new Game(new Random())value or expression?;375 var g2name? = g1.withApple(new Square(23, 15))value or expression?;376 assertEquals(trueexpected value?, headOverApple(g2)actual (computed) value?); not run var g3name? = g2.withHead(new Square(23, 15))value or expression?;377 assertEquals(trueexpected value?, headOverApple(g3)actual (computed) value?); not run378 }} // 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? 379 assert sqrt(2).round(3)actual (computed) value? evaluates to 1.414expected value? not run380 end test
+class Test_round(unittest.TestCase):
 def test_roundtest_name?(self) -> None: 381
self.assertEqual(sqrt(2).round(3)actual (computed) value?, 1.414expected value?) not run382 # end test
+[TestClass] class Test_round
[TestMethod] static void test_roundtest_name?() { 383
Assert.AreEqual(1.414expected value?, sqrt(2).round(3)actual (computed) value?); not run384 }} // end test
+<TestClass Class Test_round
 <TestMethod> Sub test_roundtest_name?() 385
Assert.AreEqual(1.414expected value?, sqrt(2).round(3)actual (computed) value?) not run386  End Sub
End Class
+class Test_round {
@Test static void test_roundtest_name?() { 387
assertEquals(1.414expected value?, sqrt(2).round(3)actual (computed) value?); not run388 }} // 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? 389 variable aname? set to [5, 1, 7]value or expression?390 assert a[4]actual (computed) value? evaluates to 0expected value? not run391 assert a[4]actual (computed) value? evaluates to "Out of range index: 4 size: 3"expected value? not run392 end test
+class Test_message(unittest.TestCase):
 def test_messagetest_name?(self) -> None: 393
aname? = [5, 1, 7]value or expression? # variable definition394 self.assertEqual(a[4]actual (computed) value?, 0expected value?) not run395 self.assertEqual(a[4]actual (computed) value?, "Out of range index: 4 size: 3"expected value?) not run396 # end test
+[TestClass] class Test_message
[TestMethod] static void test_messagetest_name?() { 397
var aname? = new [] {5, 1, 7}value or expression?;398 Assert.AreEqual(0expected value?, a[4]actual (computed) value?); not run399 Assert.AreEqual("Out of range index: 4 size: 3"expected value?, a[4]actual (computed) value?); not run400 }} // end test
+<TestClass Class Test_message
 <TestMethod> Sub test_messagetest_name?() 401
Dim aname? = {5, 1, 7}value or expression? ' variable definition402 Assert.AreEqual(0expected value?, a(4)actual (computed) value?) not run403 Assert.AreEqual("Out of range index: 4 size: 3"expected value?, a(4)actual (computed) value?) not run404  End Sub
End Class
+class Test_message {
@Test static void test_messagetest_name?() { 405
var aname? = list(5, 1, 7)value or expression?;406 assertEquals(0expected value?, a[4]actual (computed) value?); not run407 assertEquals("Out of range index: 4 size: 3"expected value?, a[4]actual (computed) value?); not run408 }} // 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?1 +function setInStylename?(s as Stringparameter definitions?) returns StringType? 2 return "<style>" + s + "<style>"value or expression?3 end function +test test_setInStyletest_name? 4 assert setInStyle(sGW)actual (computed) value? evaluates to "<style>" + sGW + "<style>"expected value? not run5 # assert fail message will be "s found at [146] expected: )"comment? end test
+sGWname? = "grid { display: flex; flex-direction: column; margin-top: 40px; width: 500px; } word { display: flex; flex-direction: row; margin: auto; }"literal value or data structure? # constant1 +def setInStylename?(s: strparameter definitions?) -> strType?: # function2 return "<style>" + s + "<style>"value or expression?3 # end function +class Test_setInStyle(unittest.TestCase):
 def test_setInStyletest_name?(self) -> None: 4
self.assertEqual(setInStyle(sGW)actual (computed) value?, "<style>" + sGW + "<style>"expected value?) not run5 # assert fail message will be "s found at [146] expected: )"comment? # end test
+const String sGWname? = "grid { display: flex; flex-direction: column; margin-top: 40px; width: 500px; } word { display: flex; flex-direction: row; margin: auto; }"literal value or data structure?;1 +static stringType? setInStylename?(string sparameter definitions?) { // function2 return "<style>" + s + "<style>"value or expression?;3 } // end function +[TestClass] class Test_setInStyle
[TestMethod] static void test_setInStyletest_name?() { 4
Assert.AreEqual("<style>" + sGW + "<style>"expected value?, setInStyle(sGW)actual (computed) value?); not run5 // assert fail message will be "s found at [146] expected: )"comment? }} // end test
+Const sGWname? = "grid { display: flex; flex-direction: column; margin-top: 40px; width: 500px; } word { display: flex; flex-direction: row; margin: auto; }"literal value or data structure?1 +Function setInStylename?(s As Stringparameter definitions?) As StringType? 2 Return "<style>" + s + "<style>"value or expression?3 End Function +<TestClass Class Test_setInStyle
 <TestMethod> Sub test_setInStyletest_name?() 4
Assert.AreEqual("<style>" + sGW + "<style>"expected value?, setInStyle(sGW)actual (computed) value?) not run5 ' assert fail message will be "s found at [146] expected: )"comment?  End Sub
End Class
public class Global {

+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?; // constant1 +static StringType? setInStylename?(String sparameter definitions?) { // function2 return "<style>" + s + "<style>"value or expression?;3 } // end function +class Test_setInStyle {
@Test static void test_setInStyletest_name?() { 4
assertEquals("<style>" + sGW + "<style>"expected value?, setInStyle(sGW)actual (computed) value?); not run5 // assert fail message will be "s found at [146] expected: )"comment? }} // end test
} // end Global

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?6
+sidename? = 100literal value or data structure? # constant7
+const Int sidename? = 100literal value or data structure?;8
+Const sidename? = 100literal value or data structure?9
+static final Int sidename? = 100literal value or data structure?; // constant10

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

call drawSideprocedureName?(side, targuments?)11
drawSideprocedureName?(side, targuments?) # procedure call12
drawSideprocedureName?(side, targuments?); // procedure call13
drawSideprocedureName?(side, targuments?) ' procedure call14
drawSideprocedureName?(side, targuments?); // procedure call15

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?1 +constant turquoisename? set to 0x00ced1literal value or data structure?2 +constant liveCellname? set to blackliteral value or data structure?3 +constant speedOfLightname? set to 299792.458literal value or data structure?4 +constant gameOvername? set to trueliteral value or data structure?5 +constant euroname? set to 0x20acliteral value or data structure?6 +constant warningMsgname? set to "Limit reached"literal value or data structure?7
+maxHitsname? = 10literal value or data structure? # constant1 +turquoisename? = 0x00ced1literal value or data structure? # constant2 +liveCellname? = blackliteral value or data structure? # constant3 +speedOfLightname? = 299792.458literal value or data structure? # constant4 +gameOvername? = Trueliteral value or data structure? # constant5 +euroname? = 0x20acliteral value or data structure? # constant6 +warningMsgname? = "Limit reached"literal value or data structure? # constant7
+const Int maxHitsname? = 10literal value or data structure?;1 +const Int turquoisename? = 0x00ced1literal value or data structure?;2 +const Int liveCellname? = blackliteral value or data structure?;3 +const Float speedOfLightname? = 299792.458literal value or data structure?;4 +const Boolean gameOvername? = trueliteral value or data structure?;5 +const Int euroname? = 0x20acliteral value or data structure?;6 +const String warningMsgname? = "Limit reached"literal value or data structure?;7
+Const maxHitsname? = 10literal value or data structure?1 +Const turquoisename? = &H00ced1literal value or data structure?2 +Const liveCellname? = blackliteral value or data structure?3 +Const speedOfLightname? = 299792.458literal value or data structure?4 +Const gameOvername? = Trueliteral value or data structure?5 +Const euroname? = &H20acliteral value or data structure?6 +Const warningMsgname? = "Limit reached"literal value or data structure?7
public class Global {

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

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 ListlistListListList 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 ListlistListListList of 2-tuples. In this second case, integer output is converted to a string of hexadecimal before printing by using example function hex:

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

+static void main() { 1 System.out.println(String.format("% (dec) is violet", rainbow()[0])value or expression?); // print statement2 System.out.println(String.format("% (hex) is red", hex(suitColours()["hearts"]))value or expression?); // print statement3 } // end main +static List<int>Type? rainbowname?(parameter definitionsparameter definitions?) { // function4 return list(0x9400D3, 0x4B0082, 0x0000CD, 0x008000, 0xFFFF00, 0xFFA500, 0xFF0000)value or expression?;5 } // end function +static Dictionary<String, int>Type? suitColoursname?(parameter definitionsparameter definitions?) { // function6 return createDictionary(list(("spades", black), ("hearts", red), ("diamonds", red), ("clubs", black)))value or expression?;7 } // end function
} // end Global

Enum

Examples of an enum

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

enum DirectionName? up, down, left, rightvalues?values?8
class DirectionName?(Enum):
up = 1
down = 2
left = 3
right = 4
values?
9
enum DirectionName? {up, down, left, rightvalues?values?}10
Enum DirectionName?
up = 0
down = 1
left = 2
right = 3
End Enum
values?
11
enum DirectionName? {up, down, left, rightvalues?values?}12

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? 13 variable newXname? set to sq[0]value or expression?14 variable newYname? set to sq[1]value or expression?15 +if dir is Direction.leftcondition? then 16 assign newXvariableName? to newX - 1value or expression?17 elif dir is Direction.rightcondition? then18 assign newXvariableName? to newX + 1value or expression?19 elif dir is Direction.upcondition? then20 assign newYvariableName? to newY - 1value or expression?21 elif dir is Direction.downcondition? then22 assign newYvariableName? to newY + 1value or expression?23 end if return [newX, newY]value or expression?24 end function
+def getAdjacentSquarename?(sq: list[int], dir: Directionparameter definitions?) -> list[int]Type?: # function25 newXname? = sq[0]value or expression? # variable definition26 newYname? = sq[1]value or expression? # variable definition27 +if dir == Direction.leftcondition?: 28 newXvariableName? = newX - 1value or expression? # assignment29 elif dir == Direction.rightcondition?: # else if30 newXvariableName? = newX + 1value or expression? # assignment31 elif dir == Direction.upcondition?: # else if32 newYvariableName? = newY - 1value or expression? # assignment33 elif dir == Direction.downcondition?: # else if34 newYvariableName? = newY + 1value or expression? # assignment35 # end if return [newX, newY]value or expression?36 # end function
+static List<int>Type? getAdjacentSquarename?(List<int> sq, Direction dirparameter definitions?) { // function37 var newXname? = sq[0]value or expression?;38 var newYname? = sq[1]value or expression?;39 +if (dir == Direction.leftcondition?) { 40 newXvariableName? = newX - 1value or expression?; // assignment41 } else if (dir == Direction.rightcondition?) {42 newXvariableName? = newX + 1value or expression?; // assignment43 } else if (dir == Direction.upcondition?) {44 newYvariableName? = newY - 1value or expression?; // assignment45 } else if (dir == Direction.downcondition?) {46 newYvariableName? = newY + 1value or expression?; // assignment47 } // end if return new [] {newX, newY}value or expression?;48 } // end function
+Function getAdjacentSquarename?(sq As List(Of Integer), dir As Directionparameter definitions?) As List(Of Integer)Type? 49 Dim newXname? = sq(0)value or expression? ' variable definition50 Dim newYname? = sq(1)value or expression? ' variable definition51 +If dir = Direction.leftcondition? Then 52 newXvariableName? = newX - 1value or expression? ' assignment53 ElseIf dir = Direction.rightcondition? Then54 newXvariableName? = newX + 1value or expression? ' assignment55 ElseIf dir = Direction.upcondition? Then56 newYvariableName? = newY - 1value or expression? ' assignment57 ElseIf dir = Direction.downcondition? Then58 newYvariableName? = newY + 1value or expression? ' assignment59 End If Return {newX, newY}value or expression?60 End Function
+static List<int>Type? getAdjacentSquarename?(List<int> sq, Direction dirparameter definitions?) { // function61 var newXname? = sq[0]value or expression?;62 var newYname? = sq[1]value or expression?;63 +if (dir == Direction.leftcondition?) { 64 newXvariableName? = newX - 1value or expression?; // assignment65 } else if (dir == Direction.rightcondition?) {66 newXvariableName? = newX + 1value or expression?; // assignment67 } else if (dir == Direction.upcondition?) {68 newYvariableName? = newY - 1value or expression?; // assignment69 } else if (dir == Direction.downcondition?) {70 newYvariableName? = newY + 1value or expression?; // assignment71 } // end if return list(newX, newY)value or expression?;72 } // end function

From the Blackjack demo:

enum SuitName? spades, hearts, diamonds, clubsvalues?values?1 enum OutcomeName? undecided, win, lose, draw, winDoublevalues?values?2
class SuitName?(Enum):
spades = 1
hearts = 2
diamonds = 3
clubs = 4
values?
1
class OutcomeName?(Enum):
undecided = 1
win = 2
lose = 3
draw = 4
winDouble = 5
values?
2
enum SuitName? {spades, hearts, diamonds, clubsvalues?values?}1 enum OutcomeName? {undecided, win, lose, draw, winDoublevalues?values?}2
Enum SuitName?
spades = 0
hearts = 1
diamonds = 2
clubs = 3
End Enum
values?
1
Enum OutcomeName?
undecided = 0
win = 1
lose = 2
draw = 3
winDouble = 4
End Enum
values?
2
public class Global {

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

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?3
    currentDirname? = Direction.rightvalue or expression? # variable definition4
    var currentDirname? = Direction.rightvalue or expression?;5
    Dim currentDirname? = Direction.rightvalue or expression? ' variable definition6
    var currentDirname? = Direction.rightvalue or expression?;7
    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.rightDirection.rightDirection.rightDirection.rightDirection.right would raise an error at compilation.
  • In order to print an enum value, or incorporate it into an interpolated string, you should first convert the value to a string using the enumToString function, for example enumToString(Suit.hearts)enumToString(Suit.hearts)enumToString(Suit.hearts)enumToString(Suit.hearts)enumToString(Suit.hearts) will convert the enum value to the string "hearts""hearts""hearts""hearts""hearts".

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?) 8 +for bitem? in bubblessource? 9 +if random() < 0.05condition? then 10 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? call b.setRadiusprocedureName?(0arguments?)11 call b.setCentreYprocedureName?(75arguments?)12 else13 # bubble rises and grows slightlycomment? call b.setCentreYprocedureName?(b.centreY - 1arguments?)14 call b.setRadiusprocedureName?(b.radius + 0.2arguments?)15 end if end for call displayVectorGraphicsprocedureName?(bubblesarguments?)16 call sleep_msprocedureName?(5arguments?)17 end procedure
+def moveGrowBurstname?(bubbles: list[CircleVG]parameter definitions?) -> None: # procedure18 +for bitem? in bubblessource?: 19 +if random() < 0.05condition?: 20 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) # procedure call21 b.setCentreYprocedureName?(75arguments?) # procedure call22 else:23 # bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) # procedure call24 b.setRadiusprocedureName?(b.radius + 0.2arguments?) # procedure call25 # end if # end for displayVectorGraphicsprocedureName?(bubblesarguments?) # procedure call26 sleep_msprocedureName?(5arguments?) # procedure call27 # end procedure
+static void moveGrowBurstname?(List<CircleVG> bubblesparameter definitions?) { // procedure28 +foreach (var bitem? in bubblessource?) { 29 +if (random() < 0.05condition?) { 30 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // procedure call31 b.setCentreYprocedureName?(75arguments?); // procedure call32 } else {33 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // procedure call34 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // procedure call35 } // end if } // end foreach displayVectorGraphicsprocedureName?(bubblesarguments?); // procedure call36 sleep_msprocedureName?(5arguments?); // procedure call37 } // end procedure
+Sub moveGrowBurstname?(bubbles As List(Of CircleVG)parameter definitions?) ' procedure38 +For Each bitem? In bubblessource? 39 +If random() < 0.05condition? Then 40 ' 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) ' procedure call41 b.setCentreYprocedureName?(75arguments?) ' procedure call42 Else43 ' bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) ' procedure call44 b.setRadiusprocedureName?(b.radius + 0.2arguments?) ' procedure call45 End If Next b displayVectorGraphicsprocedureName?(bubblesarguments?) ' procedure call46 sleep_msprocedureName?(5arguments?) ' procedure call47 End Sub
+static void moveGrowBurstname?(List<CircleVG> bubblesparameter definitions?) { // procedure48 +foreach (var bitem? in bubblessource?) { 49 +if (random() < 0.05condition?) { 50 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // procedure call51 b.setCentreYprocedureName?(75arguments?); // procedure call52 } else {53 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // procedure call54 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // procedure call55 } // end if } // end foreach displayVectorGraphicsprocedureName?(bubblesarguments?); // procedure call56 sleep_msprocedureName?(5arguments?); // procedure call57 } // 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 #  comment?#  comment?//  comment?'  comment?//  comment? 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 58 variable replyname? set to ""value or expression?59 +while not reply.upperCase().equals("Q")condition? 60 assign replyvariableName? to input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression?61 +if reply.equals("")condition? then 62 variable nowname? set to divAsInt(clock(), 1000)value or expression?63 print(nowvalue or expression?)64 print(getDate(now)value or expression?)65 else66 +try 67 variable tdname? set to int(reply)value or expression?68 +if td >= 0condition? then 69 print(getDate(td)value or expression?)70 end if catch evariableName? as ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError?71 end try end if end while end main
+def main() -> None: 72 replyname? = ""value or expression? # variable definition73 +while not reply.upperCase().equals("Q")condition?: 74 replyvariableName? = input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression? # assignment75 +if reply.equals("")condition?: 76 nowname? = divAsInt(clock(), 1000)value or expression? # variable definition77 print(nowvalue or expression?)78 print(getDate(now)value or expression?)79 else:80 +try: 81 tdname? = int(reply)value or expression? # variable definition82 +if td >= 0condition?: 83 print(getDate(td)value or expression?)84 # end if except ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError? as evariableName?: # catch85 # end try # end if # end while # end main
+static void main() { 86 var replyname? = ""value or expression?;87 +while (!reply.upperCase().equals("Q")condition?) { 88 replyvariableName? = input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression?; // assignment89 +if (reply.equals("")condition?) { 90 var nowname? = divAsInt(clock(), 1000)value or expression?;91 Console.WriteLine(nowvalue or expression?); // print statement92 Console.WriteLine(getDate(now)value or expression?); // print statement93 } else {94 +try { 95 var tdname? = int(reply)value or expression?;96 +if (td >= 0condition?) { 97 Console.WriteLine(getDate(td)value or expression?); // print statement98 } // end if } catch (ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError? evariableName?) {99 } // end try } // end if } // end while } // end main
+Sub main() 100 Dim replyname? = ""value or expression? ' variable definition101 +While Not reply.upperCase().equals("Q")condition? 102 replyvariableName? = input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression? ' assignment103 +If reply.equals("")condition? Then 104 Dim nowname? = divAsInt(clock(), 1000)value or expression? ' variable definition105 Console.WriteLine(nowvalue or expression?) ' print statement106 Console.WriteLine(getDate(now)value or expression?) ' print statement107 Else108 +Try 109 Dim tdname? = int(reply)value or expression? ' variable definition110 +If td >= 0condition? Then 111 Console.WriteLine(getDate(td)value or expression?) ' print statement112 End If Catch evariableName? As ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError?113 End Try End If End While End Sub
+static void main() { 114 var replyname? = ""value or expression?;115 +while (!reply.upperCase().equals("Q")condition?) { 116 replyvariableName? = input("RETURN for time now or Unix time (positive integer) or Q to quit")value or expression?; // assignment117 +if (reply.equals("")condition?) { 118 var nowname? = divAsInt(clock(), 1000)value or expression?;119 System.out.println(nowvalue or expression?); // print statement120 System.out.println(getDate(now)value or expression?); // print statement121 } else {122 +try { 123 var tdname? = int(reply)value or expression?;124 +if (td >= 0condition?) { 125 System.out.println(getDate(td)value or expression?); // print statement126 } // end if } catch (ElanRuntimeErrortype e.g. ElanRuntimeError or CustomError? evariableName?) {127 } // end try } // end if } // end while } // end main

From the Ripple sort demo, printing a ListlistListListList:

+main 128 variable liname? set to [7, 1, 0, 4, 8, 3, 6]value or expression?129 print(livalue or expression?)130 call inPlaceRippleSortprocedureName?(liarguments?)131 print(livalue or expression?)132 end main
+def main() -> None: 133 liname? = [7, 1, 0, 4, 8, 3, 6]value or expression? # variable definition134 print(livalue or expression?)135 inPlaceRippleSortprocedureName?(liarguments?) # procedure call136 print(livalue or expression?)137 # end main
+static void main() { 138 var liname? = new [] {7, 1, 0, 4, 8, 3, 6}value or expression?;139 Console.WriteLine(livalue or expression?); // print statement140 inPlaceRippleSortprocedureName?(liarguments?); // procedure call141 Console.WriteLine(livalue or expression?); // print statement142 } // end main
+Sub main() 143 Dim liname? = {7, 1, 0, 4, 8, 3, 6}value or expression? ' variable definition144 Console.WriteLine(livalue or expression?) ' print statement145 inPlaceRippleSortprocedureName?(liarguments?) ' procedure call146 Console.WriteLine(livalue or expression?) ' print statement147 End Sub
+static void main() { 148 var liname? = list(7, 1, 0, 4, 8, 3, 6)value or expression?;149 System.out.println(livalue or expression?); // print statement150 inPlaceRippleSortprocedureName?(liarguments?); // procedure call151 System.out.println(livalue or expression?); // print statement152 } // end main

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

+main 153 variable blocksname? set to createBlockGraphics(white)value or expression?154 variable headname? set to [20, 15]value or expression?155 variable tailname? set to headvalue or expression?156 variable bodyname? set to [head]value or expression?157 variable currentDirname? set to Direction.rightvalue or expression?158 variable gameOnname? set to truevalue or expression?159 variable applename? set to [0, 0]value or expression?160 call setAppleToRandomPositionprocedureName?(apple, bodyarguments?)161 +while gameOncondition? 162 call updateDisplayprocedureName?(blocks, head, tail, body, applearguments?)163 variable currentDirRefname? set to new AsRef<of Direction>(currentDir)value or expression?164 variable headRefname? set to new AsRef<of List<of Int>>(head)value or expression?165 variable tailRefname? set to new AsRef<of List<of Int>>(tail)value or expression?166 call updateSnakeprocedureName?(currentDirRef, tailRef, headRef, bodyarguments?)167 assign headvariableName? to headRef.value()value or expression?168 assign tailvariableName? to tailRef.value()value or expression?169 assign currentDirvariableName? to currentDirRef.value()value or expression?170 assign gameOnvariableName? to not hasHitEdge(head[0], head[1]) and not body.contains(head)value or expression?171 +if head.equals(apple)condition? then 172 call setAppleToRandomPositionprocedureName?(apple, bodyarguments?)173 else174 call body.removeAtprocedureName?(0arguments?)175 end if call sleep_msprocedureName?(150arguments?)176 end while print($"Game Over! Score: {body.length() - 1}"value or expression?)177 end main
+def main() -> None: 178 blocksname? = createBlockGraphics(white)value or expression? # variable definition179 headname? = [20, 15]value or expression? # variable definition180 tailname? = headvalue or expression? # variable definition181 bodyname? = [head]value or expression? # variable definition182 currentDirname? = Direction.rightvalue or expression? # variable definition183 gameOnname? = Truevalue or expression? # variable definition184 applename? = [0, 0]value or expression? # variable definition185 setAppleToRandomPositionprocedureName?(apple, bodyarguments?) # procedure call186 +while gameOncondition?: 187 updateDisplayprocedureName?(blocks, head, tail, body, applearguments?) # procedure call188 currentDirRefname? = AsRef[Direction](currentDir)value or expression? # variable definition189 headRefname? = AsRef[list[int]](head)value or expression? # variable definition190 tailRefname? = AsRef[list[int]](tail)value or expression? # variable definition191 updateSnakeprocedureName?(currentDirRef, tailRef, headRef, bodyarguments?) # procedure call192 headvariableName? = headRef.value()value or expression? # assignment193 tailvariableName? = tailRef.value()value or expression? # assignment194 currentDirvariableName? = currentDirRef.value()value or expression? # assignment195 gameOnvariableName? = not hasHitEdge(head[0], head[1]) and not body.contains(head)value or expression? # assignment196 +if head.equals(apple)condition?: 197 setAppleToRandomPositionprocedureName?(apple, bodyarguments?) # procedure call198 else:199 body.removeAtprocedureName?(0arguments?) # procedure call200 # end if sleep_msprocedureName?(150arguments?) # procedure call201 # end while print(f"Game Over! Score: {body.length() - 1}"value or expression?)202 # end main
+static void main() { 203 var blocksname? = createBlockGraphics(white)value or expression?;204 var headname? = new [] {20, 15}value or expression?;205 var tailname? = headvalue or expression?;206 var bodyname? = new [] {head}value or expression?;207 var currentDirname? = Direction.rightvalue or expression?;208 var gameOnname? = truevalue or expression?;209 var applename? = new [] {0, 0}value or expression?;210 setAppleToRandomPositionprocedureName?(apple, bodyarguments?); // procedure call211 +while (gameOncondition?) { 212 updateDisplayprocedureName?(blocks, head, tail, body, applearguments?); // procedure call213 var currentDirRefname? = new AsRef<Direction>(currentDir)value or expression?;214 var headRefname? = new AsRef<List<int>>(head)value or expression?;215 var tailRefname? = new AsRef<List<int>>(tail)value or expression?;216 updateSnakeprocedureName?(currentDirRef, tailRef, headRef, bodyarguments?); // procedure call217 headvariableName? = headRef.value()value or expression?; // assignment218 tailvariableName? = tailRef.value()value or expression?; // assignment219 currentDirvariableName? = currentDirRef.value()value or expression?; // assignment220 gameOnvariableName? = !hasHitEdge(head[0], head[1]) && !body.contains(head)value or expression?; // assignment221 +if (head.equals(apple)condition?) { 222 setAppleToRandomPositionprocedureName?(apple, bodyarguments?); // procedure call223 } else {224 body.removeAtprocedureName?(0arguments?); // procedure call225 } // end if sleep_msprocedureName?(150arguments?); // procedure call226 } // end while Console.WriteLine($"Game Over! Score: {body.length() - 1}"value or expression?); // print statement227 } // end main
+Sub main() 228 Dim blocksname? = createBlockGraphics(white)value or expression? ' variable definition229 Dim headname? = {20, 15}value or expression? ' variable definition230 Dim tailname? = headvalue or expression? ' variable definition231 Dim bodyname? = {head}value or expression? ' variable definition232 Dim currentDirname? = Direction.rightvalue or expression? ' variable definition233 Dim gameOnname? = Truevalue or expression? ' variable definition234 Dim applename? = {0, 0}value or expression? ' variable definition235 setAppleToRandomPositionprocedureName?(apple, bodyarguments?) ' procedure call236 +While gameOncondition? 237 updateDisplayprocedureName?(blocks, head, tail, body, applearguments?) ' procedure call238 Dim currentDirRefname? = New AsRef(Of Direction)(currentDir)value or expression? ' variable definition239 Dim headRefname? = New AsRef(Of List(Of Integer))(head)value or expression? ' variable definition240 Dim tailRefname? = New AsRef(Of List(Of Integer))(tail)value or expression? ' variable definition241 updateSnakeprocedureName?(currentDirRef, tailRef, headRef, bodyarguments?) ' procedure call242 headvariableName? = headRef.value()value or expression? ' assignment243 tailvariableName? = tailRef.value()value or expression? ' assignment244 currentDirvariableName? = currentDirRef.value()value or expression? ' assignment245 gameOnvariableName? = Not hasHitEdge(head(0), head(1)) And Not body.contains(head)value or expression? ' assignment246 +If head.equals(apple)condition? Then 247 setAppleToRandomPositionprocedureName?(apple, bodyarguments?) ' procedure call248 Else249 body.removeAtprocedureName?(0arguments?) ' procedure call250 End If sleep_msprocedureName?(150arguments?) ' procedure call251 End While Console.WriteLine($"Game Over! Score: {body.length() - 1}"value or expression?) ' print statement252 End Sub
+static void main() { 253 var blocksname? = createBlockGraphics(white)value or expression?;254 var headname? = list(20, 15)value or expression?;255 var tailname? = headvalue or expression?;256 var bodyname? = list(head)value or expression?;257 var currentDirname? = Direction.rightvalue or expression?;258 var gameOnname? = truevalue or expression?;259 var applename? = list(0, 0)value or expression?;260 setAppleToRandomPositionprocedureName?(apple, bodyarguments?); // procedure call261 +while (gameOncondition?) { 262 updateDisplayprocedureName?(blocks, head, tail, body, applearguments?); // procedure call263 var currentDirRefname? = new AsRef<Direction>(currentDir)value or expression?;264 var headRefname? = new AsRef<List<int>>(head)value or expression?;265 var tailRefname? = new AsRef<List<int>>(tail)value or expression?;266 updateSnakeprocedureName?(currentDirRef, tailRef, headRef, bodyarguments?); // procedure call267 headvariableName? = headRef.value()value or expression?; // assignment268 tailvariableName? = tailRef.value()value or expression?; // assignment269 currentDirvariableName? = currentDirRef.value()value or expression?; // assignment270 gameOnvariableName? = !hasHitEdge(head[0], head[1]) && !body.contains(head)value or expression?; // assignment271 +if (head.equals(apple)condition?) { 272 setAppleToRandomPositionprocedureName?(apple, bodyarguments?); // procedure call273 } else {274 body.removeAtprocedureName?(0arguments?); // procedure call275 } // end if sleep_msprocedureName?(150arguments?); // procedure call276 } // end while System.out.println(String.format("Game Over! Score: %", body.length() - 1)value or expression?); // print statement277 } // 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?278
xname? = 20value or expression? # variable definition279
var xname? = 20value or expression?;280
Dim xname? = 20value or expression? ' variable definition281
var xname? = 20value or expression?;282
variable yname? set to 15value or expression?283
yname? = 15value or expression? # variable definition284
var yname? = 15value or expression?;285
Dim yname? = 15value or expression? ' variable definition286
var yname? = 15value or expression?;287

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?288
blocks[x][y]variableName? = redvalue or expression? # assignment289
blocks[x][y]variableName? = redvalue or expression?; // assignment290
blocks(x)(y)variableName? = redvalue or expression? ' assignment291
blocks[x][y]variableName? = redvalue or expression?; // assignment292

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?293
gridname? = createBlockGraphics(white)value or expression? # variable definition294
var gridname? = createBlockGraphics(white)value or expression?;295
Dim gridname? = createBlockGraphics(white)value or expression? ' variable definition296
var gridname? = createBlockGraphics(white)value or expression?;297

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

variable liname? set to [7, 1, 0, 4, 8, 3, 6]value or expression?298
liname? = [7, 1, 0, 4, 8, 3, 6]value or expression? # variable definition299
var liname? = new [] {7, 1, 0, 4, 8, 3, 6}value or expression?;300
Dim liname? = {7, 1, 0, 4, 8, 3, 6}value or expression? ' variable definition301
var liname? = list(7, 1, 0, 4, 8, 3, 6)value or expression?;302

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.

Assignment

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?) 303 variable hasChangedname? set to truevalue or expression?304 variable lastCompname? set to li.length() - 2value or expression?305 +while hasChanged is truecondition? 306 assign hasChangedvariableName? to falsevalue or expression?307 +for iitem? in range(0, lastComp + 1)source? 308 +if li[i] > li[i + 1]condition? then 309 variable tempname? set to li[i]value or expression?310 assign li[i]variableName? to li[i + 1]value or expression?311 assign li[i + 1]variableName? to tempvalue or expression?312 assign hasChangedvariableName? to truevalue or expression?313 end if end for assign lastCompvariableName? to lastComp - 1value or expression?314 end while end procedure
+def inPlaceRippleSortname?(li: list[int]parameter definitions?) -> None: # procedure315 hasChangedname? = Truevalue or expression? # variable definition316 lastCompname? = li.length() - 2value or expression? # variable definition317 +while hasChanged == Truecondition?: 318 hasChangedvariableName? = Falsevalue or expression? # assignment319 +for iitem? in range(0, lastComp + 1)source?: 320 +if li[i] > li[i + 1]condition?: 321 tempname? = li[i]value or expression? # variable definition322 li[i]variableName? = li[i + 1]value or expression? # assignment323 li[i + 1]variableName? = tempvalue or expression? # assignment324 hasChangedvariableName? = Truevalue or expression? # assignment325 # end if # end for lastCompvariableName? = lastComp - 1value or expression? # assignment326 # end while # end procedure
+static void inPlaceRippleSortname?(List<int> liparameter definitions?) { // procedure327 var hasChangedname? = truevalue or expression?;328 var lastCompname? = li.length() - 2value or expression?;329 +while (hasChanged == truecondition?) { 330 hasChangedvariableName? = falsevalue or expression?; // assignment331 +foreach (var iitem? in range(0, lastComp + 1)source?) { 332 +if (li[i] > li[i + 1]condition?) { 333 var tempname? = li[i]value or expression?;334 li[i]variableName? = li[i + 1]value or expression?; // assignment335 li[i + 1]variableName? = tempvalue or expression?; // assignment336 hasChangedvariableName? = truevalue or expression?; // assignment337 } // end if } // end foreach lastCompvariableName? = lastComp - 1value or expression?; // assignment338 } // end while } // end procedure
+Sub inPlaceRippleSortname?(li As List(Of Integer)parameter definitions?) ' procedure339 Dim hasChangedname? = Truevalue or expression? ' variable definition340 Dim lastCompname? = li.length() - 2value or expression? ' variable definition341 +While hasChanged = Truecondition? 342 hasChangedvariableName? = Falsevalue or expression? ' assignment343 +For Each iitem? In range(0, lastComp + 1)source? 344 +If li(i) > li(i + 1)condition? Then 345 Dim tempname? = li(i)value or expression? ' variable definition346 li(i)variableName? = li(i + 1)value or expression? ' assignment347 li(i + 1)variableName? = tempvalue or expression? ' assignment348 hasChangedvariableName? = Truevalue or expression? ' assignment349 End If Next i lastCompvariableName? = lastComp - 1value or expression? ' assignment350 End While End Sub
+static void inPlaceRippleSortname?(List<int> liparameter definitions?) { // procedure351 var hasChangedname? = truevalue or expression?;352 var lastCompname? = li.length() - 2value or expression?;353 +while (hasChanged == truecondition?) { 354 hasChangedvariableName? = falsevalue or expression?; // assignment355 +foreach (var iitem? in range(0, lastComp + 1)source?) { 356 +if (li[i] > li[i + 1]condition?) { 357 var tempname? = li[i]value or expression?;358 li[i]variableName? = li[i + 1]value or expression?; // assignment359 li[i + 1]variableName? = tempvalue or expression?; // assignment360 hasChangedvariableName? = truevalue or expression?; // assignment361 } // end if } // end foreach lastCompvariableName? = lastComp - 1value or expression?; // assignment362 } // 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:

+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?) 363 variable headname? set to headRef.value()value or expression?364 variable tailname? set to tailRef.value()value or expression?365 variable currentDirname? set to currentDirRef.value()value or expression?366 assign currentDirvariableName? to directionByKey(currentDir, getKey())value or expression?367 call tailRef.setprocedureName?(body[0]arguments?)368 call body.appendprocedureName?(headarguments?)369 call headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?)370 call currentDirRef.setprocedureName?(currentDirarguments?)371 end procedure
+def updateSnakename?(currentDirRef: AsRef[Direction], tailRef: AsRef[list[int]], headRef: AsRef[list[int]], body: list[list[int]]parameter definitions?) -> None: # procedure372 headname? = headRef.value()value or expression? # variable definition373 tailname? = tailRef.value()value or expression? # variable definition374 currentDirname? = currentDirRef.value()value or expression? # variable definition375 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression? # assignment376 tailRef.setprocedureName?(body[0]arguments?) # procedure call377 body.appendprocedureName?(headarguments?) # procedure call378 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?) # procedure call379 currentDirRef.setprocedureName?(currentDirarguments?) # procedure call380 # end procedure
+static void updateSnakename?(AsRef<Direction> currentDirRef, AsRef<List<int>> tailRef, AsRef<List<int>> headRef, List<List<int>> bodyparameter definitions?) { // procedure381 var headname? = headRef.value()value or expression?;382 var tailname? = tailRef.value()value or expression?;383 var currentDirname? = currentDirRef.value()value or expression?;384 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression?; // assignment385 tailRef.setprocedureName?(body[0]arguments?); // procedure call386 body.appendprocedureName?(headarguments?); // procedure call387 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?); // procedure call388 currentDirRef.setprocedureName?(currentDirarguments?); // procedure call389 } // 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?) ' procedure390 Dim headname? = headRef.value()value or expression? ' variable definition391 Dim tailname? = tailRef.value()value or expression? ' variable definition392 Dim currentDirname? = currentDirRef.value()value or expression? ' variable definition393 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression? ' assignment394 tailRef.setprocedureName?(body(0)arguments?) ' procedure call395 body.appendprocedureName?(headarguments?) ' procedure call396 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?) ' procedure call397 currentDirRef.setprocedureName?(currentDirarguments?) ' procedure call398 End Sub
+static void updateSnakename?(AsRef<Direction> currentDirRef, AsRef<List<int>> tailRef, AsRef<List<int>> headRef, List<List<int>> bodyparameter definitions?) { // procedure399 var headname? = headRef.value()value or expression?;400 var tailname? = tailRef.value()value or expression?;401 var currentDirname? = currentDirRef.value()value or expression?;402 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression?; // assignment403 tailRef.setprocedureName?(body[0]arguments?); // procedure call404 body.appendprocedureName?(headarguments?); // procedure call405 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?); // procedure call406 currentDirRef.setprocedureName?(currentDirarguments?); // procedure call407 } // end procedure

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?)408
wantedname? = input("What type of fruit do you want ('x' to exit)? "prompt message?) # input statement409
Console.WriteLine("What type of fruit do you want ('x' to exit)? "prompt message?);
var wantedname? = Console.ReadLine();
// input statement410
Console.WriteLine("What type of fruit do you want ('x' to exit)? "prompt message?)
Dim wantedname? = Console.ReadLine()
' input statement411
var wantedname? = Console.ReadLine("What type of fruit do you want ('x' to exit)? "prompt message?); // input statement412

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

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? 413 variable resultname? set to blackvalue or expression?414 +if random > 0.5condition? then 415 assign resultvariableName? to whitevalue or expression?416 end if return resultvalue or expression?417 end function
+def blackOrWhitename?(random: floatparameter definitions?) -> intType?: # function418 resultname? = blackvalue or expression? # variable definition419 +if random > 0.5condition?: 420 resultvariableName? = whitevalue or expression? # assignment421 # end if return resultvalue or expression?422 # end function
+static intType? blackOrWhitename?(double randomparameter definitions?) { // function423 var resultname? = blackvalue or expression?;424 +if (random > 0.5condition?) { 425 resultvariableName? = whitevalue or expression?; // assignment426 } // end if return resultvalue or expression?;427 } // end function
+Function blackOrWhitename?(random As Doubleparameter definitions?) As IntegerType? 428 Dim resultname? = blackvalue or expression? ' variable definition429 +If random > 0.5condition? Then 430 resultvariableName? = whitevalue or expression? ' assignment431 End If Return resultvalue or expression?432 End Function
+static intType? blackOrWhitename?(double randomparameter definitions?) { // function433 var resultname? = blackvalue or expression?;434 +if (random > 0.5condition?) { 435 resultvariableName? = whitevalue or expression?; // assignment436 } // end if return resultvalue or expression?;437 } // 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 438 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? call b.setRadiusprocedureName?(0arguments?)439 call b.setCentreYprocedureName?(75arguments?)440 else441 # bubble rises and grows slightlycomment? call b.setCentreYprocedureName?(b.centreY - 1arguments?)442 call b.setRadiusprocedureName?(b.radius + 0.2arguments?)443 end if
+if random() < 0.05condition?: 444 # 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) # procedure call445 b.setCentreYprocedureName?(75arguments?) # procedure call446 else:447 # bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) # procedure call448 b.setRadiusprocedureName?(b.radius + 0.2arguments?) # procedure call449 # end if
+if (random() < 0.05condition?) { 450 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // procedure call451 b.setCentreYprocedureName?(75arguments?); // procedure call452 } else {453 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // procedure call454 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // procedure call455 } // end if
+If random() < 0.05condition? Then 456 ' 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?) ' procedure call457 b.setCentreYprocedureName?(75arguments?) ' procedure call458 Else459 ' bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?) ' procedure call460 b.setRadiusprocedureName?(b.radius + 0.2arguments?) ' procedure call461 End If
+if (random() < 0.05condition?) { 462 // 5% chance bubble 'bursts' and starts again tiny at bottomcomment? b.setRadiusprocedureName?(0arguments?); // procedure call463 b.setCentreYprocedureName?(75arguments?); // procedure call464 } else {465 // bubble rises and grows slightlycomment? b.setCentreYprocedureName?(b.centreY - 1arguments?); // procedure call466 b.setRadiusprocedureName?(b.radius + 0.2arguments?); // procedure call467 } // 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 468 assign xvariableName? to min([x + 1, 39])value or expression?469 elif direction is 1condition? then470 assign xvariableName? to max([x - 1, 0])value or expression?471 elif direction is 2condition? then472 assign yvariableName? to min([y + 1, 29])value or expression?473 elif direction is 3condition? then474 assign yvariableName? to max([y - 1, 0])value or expression?475 end if
+if direction == 0condition?: 476 xvariableName? = min([x + 1, 39])value or expression? # assignment477 elif direction == 1condition?: # else if478 xvariableName? = max([x - 1, 0])value or expression? # assignment479 elif direction == 2condition?: # else if480 yvariableName? = min([y + 1, 29])value or expression? # assignment481 elif direction == 3condition?: # else if482 yvariableName? = max([y - 1, 0])value or expression? # assignment483 # end if
+if (direction == 0condition?) { 484 xvariableName? = min(new [] {x + 1, 39})value or expression?; // assignment485 } else if (direction == 1condition?) {486 xvariableName? = max(new [] {x - 1, 0})value or expression?; // assignment487 } else if (direction == 2condition?) {488 yvariableName? = min(new [] {y + 1, 29})value or expression?; // assignment489 } else if (direction == 3condition?) {490 yvariableName? = max(new [] {y - 1, 0})value or expression?; // assignment491 } // end if
+If direction = 0condition? Then 492 xvariableName? = min({x + 1, 39})value or expression? ' assignment493 ElseIf direction = 1condition? Then494 xvariableName? = max({x - 1, 0})value or expression? ' assignment495 ElseIf direction = 2condition? Then496 yvariableName? = min({y + 1, 29})value or expression? ' assignment497 ElseIf direction = 3condition? Then498 yvariableName? = max({y - 1, 0})value or expression? ' assignment499 End If
+if (direction == 0condition?) { 500 xvariableName? = min(list(x + 1, 39))value or expression?; // assignment501 } else if (direction == 1condition?) {502 xvariableName? = max(list(x - 1, 0))value or expression?; // assignment503 } else if (direction == 2condition?) {504 yvariableName? = min(list(y + 1, 29))value or expression?; // assignment505 } else if (direction == 3condition?) {506 yvariableName? = max(list(y - 1, 0))value or expression?; // assignment507 } // 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 falseFalsefalseFalsefalse (meaning that no more swaps have occurred):

+while hasChanged is truecondition? 508 assign hasChangedvariableName? to falsevalue or expression?509 +for iitem? in range(0, lastComp + 1)source? 510 +if li[i] > li[i + 1]condition? then 511 variable tempname? set to li[i]value or expression?512 assign li[i]variableName? to li[i + 1]value or expression?513 assign li[i + 1]variableName? to tempvalue or expression?514 assign hasChangedvariableName? to truevalue or expression?515 end if end for assign lastCompvariableName? to lastComp - 1value or expression?516 end while
+while hasChanged == Truecondition?: 517 hasChangedvariableName? = Falsevalue or expression? # assignment518 +for iitem? in range(0, lastComp + 1)source?: 519 +if li[i] > li[i + 1]condition?: 520 tempname? = li[i]value or expression? # variable definition521 li[i]variableName? = li[i + 1]value or expression? # assignment522 li[i + 1]variableName? = tempvalue or expression? # assignment523 hasChangedvariableName? = Truevalue or expression? # assignment524 # end if # end for lastCompvariableName? = lastComp - 1value or expression? # assignment525 # end while
+while (hasChanged == truecondition?) { 526 hasChangedvariableName? = falsevalue or expression?; // assignment527 +foreach (var iitem? in range(0, lastComp + 1)source?) { 528 +if (li[i] > li[i + 1]condition?) { 529 var tempname? = li[i]value or expression?;530 li[i]variableName? = li[i + 1]value or expression?; // assignment531 li[i + 1]variableName? = tempvalue or expression?; // assignment532 hasChangedvariableName? = truevalue or expression?; // assignment533 } // end if } // end foreach lastCompvariableName? = lastComp - 1value or expression?; // assignment534 } // end while
+While hasChanged = Truecondition? 535 hasChangedvariableName? = Falsevalue or expression? ' assignment536 +For Each iitem? In range(0, lastComp + 1)source? 537 +If li(i) > li(i + 1)condition? Then 538 Dim tempname? = li(i)value or expression? ' variable definition539 li(i)variableName? = li(i + 1)value or expression? ' assignment540 li(i + 1)variableName? = tempvalue or expression? ' assignment541 hasChangedvariableName? = Truevalue or expression? ' assignment542 End If Next i lastCompvariableName? = lastComp - 1value or expression? ' assignment543 End While
+while (hasChanged == truecondition?) { 544 hasChangedvariableName? = falsevalue or expression?; // assignment545 +foreach (var iitem? in range(0, lastComp + 1)source?) { 546 +if (li[i] > li[i + 1]condition?) { 547 var tempname? = li[i]value or expression?;548 li[i]variableName? = li[i + 1]value or expression?; // assignment549 li[i + 1]variableName? = tempvalue or expression?; // assignment550 hasChangedvariableName? = truevalue or expression?; // assignment551 } // end if } // end foreach lastCompvariableName? = lastComp - 1value or expression?; // assignment552 } // 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? 553 call moveGrowBurstprocedureName?(bubblesarguments?)554 end while
+while Truecondition?: 555 moveGrowBurstprocedureName?(bubblesarguments?) # procedure call556 # end while
+while (truecondition?) { 557 moveGrowBurstprocedureName?(bubblesarguments?); // procedure call558 } // end while
+While Truecondition? 559 moveGrowBurstprocedureName?(bubblesarguments?) ' procedure call560 End While
+while (truecondition?) { 561 moveGrowBurstprocedureName?(bubblesarguments?); // procedure call562 } // 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? 563 +if (stacks[0].length() mod 2) is 0condition? then 564 call moveBetweenprocedureName?(stacks, 0, 1arguments?)565 call moveBetweenprocedureName?(stacks, 0, 2arguments?)566 call moveBetweenprocedureName?(stacks, 1, 2arguments?)567 else568 call moveBetweenprocedureName?(stacks, 0, 2arguments?)569 call moveBetweenprocedureName?(stacks, 0, 1arguments?)570 call moveBetweenprocedureName?(stacks, 1, 2arguments?)571 end if end while
+while stacks[2].length() != nDiscscondition?: 572 +if (stacks[0].length() % 2) == 0condition?: 573 moveBetweenprocedureName?(stacks, 0, 1arguments?) # procedure call574 moveBetweenprocedureName?(stacks, 0, 2arguments?) # procedure call575 moveBetweenprocedureName?(stacks, 1, 2arguments?) # procedure call576 else:577 moveBetweenprocedureName?(stacks, 0, 2arguments?) # procedure call578 moveBetweenprocedureName?(stacks, 0, 1arguments?) # procedure call579 moveBetweenprocedureName?(stacks, 1, 2arguments?) # procedure call580 # end if # end while
+while (stacks[2].length() != nDiscscondition?) { 581 +if ((stacks[0].length() % 2) == 0condition?) { 582 moveBetweenprocedureName?(stacks, 0, 1arguments?); // procedure call583 moveBetweenprocedureName?(stacks, 0, 2arguments?); // procedure call584 moveBetweenprocedureName?(stacks, 1, 2arguments?); // procedure call585 } else {586 moveBetweenprocedureName?(stacks, 0, 2arguments?); // procedure call587 moveBetweenprocedureName?(stacks, 0, 1arguments?); // procedure call588 moveBetweenprocedureName?(stacks, 1, 2arguments?); // procedure call589 } // end if } // end while
+While stacks(2).length() <> nDiscscondition? 590 +If (stacks(0).length() Mod 2) = 0condition? Then 591 moveBetweenprocedureName?(stacks, 0, 1arguments?) ' procedure call592 moveBetweenprocedureName?(stacks, 0, 2arguments?) ' procedure call593 moveBetweenprocedureName?(stacks, 1, 2arguments?) ' procedure call594 Else595 moveBetweenprocedureName?(stacks, 0, 2arguments?) ' procedure call596 moveBetweenprocedureName?(stacks, 0, 1arguments?) ' procedure call597 moveBetweenprocedureName?(stacks, 1, 2arguments?) ' procedure call598 End If End While
+while (stacks[2].length() != nDiscscondition?) { 599 +if ((stacks[0].length() % 2) == 0condition?) { 600 moveBetweenprocedureName?(stacks, 0, 1arguments?); // procedure call601 moveBetweenprocedureName?(stacks, 0, 2arguments?); // procedure call602 moveBetweenprocedureName?(stacks, 1, 2arguments?); // procedure call603 } else {604 moveBetweenprocedureName?(stacks, 0, 2arguments?); // procedure call605 moveBetweenprocedureName?(stacks, 0, 1arguments?); // procedure call606 moveBetweenprocedureName?(stacks, 1, 2arguments?); // procedure call607 } // 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? 608 variable bname? set to (new CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression?609 call bubbles.appendprocedureName?(barguments?)610 end for
+for iitem? in range(1, 21)source?: 611 bname? = (CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression? # variable definition612 bubbles.appendprocedureName?(barguments?) # procedure call613 # end for
+foreach (var iitem? in range(1, 21)source?) { 614 var bname? = (new CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression?;615 bubbles.appendprocedureName?(barguments?); // procedure call616 } // end foreach
+For Each iitem? In range(1, 21)source? 617 Dim bname? = (New CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression? ' variable definition618 bubbles.appendprocedureName?(barguments?) ' procedure call619 Next i
+foreach (var iitem? in range(1, 21)source?) { 620 var bname? = (new CircleVG()).withCentreX(i*5 + 2).withCentreY(75).withRadius(0).withFillColour(transparent).withStrokeColour(randint(0, white))value or expression?;621 bubbles.appendprocedureName?(barguments?); // procedure call622 } // 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?) 623 variable pname? set to (200.0/order).floor()value or expression?624 variable turnIname? set to 0value or expression?625 +for turnitem? in turnssource? 626 assign turnIvariableName? to (if_(turn.equals(left), 1, -1))value or expression?627 call t.turnprocedureName?(-45*turnIarguments?)628 call t.moveprocedureName?(cornerarguments?)629 call t.turnprocedureName?(-45*turnIarguments?)630 call t.moveprocedureName?(sidearguments?)631 call sleep_msprocedureName?(parguments?)632 end for call t.penUpprocedureName?(arguments?)633 call t.hideprocedureName?(arguments?)634 end procedure
+def drawDragonname?(t: Turtle, order: int, turns: str, side: float, corner: floatparameter definitions?) -> None: # procedure635 pname? = (200.0/order).floor()value or expression? # variable definition636 turnIname? = 0value or expression? # variable definition637 +for turnitem? in turnssource?: 638 turnIvariableName? = (if_(turn.equals(left), 1, -1))value or expression? # assignment639 t.turnprocedureName?(-45*turnIarguments?) # procedure call640 t.moveprocedureName?(cornerarguments?) # procedure call641 t.turnprocedureName?(-45*turnIarguments?) # procedure call642 t.moveprocedureName?(sidearguments?) # procedure call643 sleep_msprocedureName?(parguments?) # procedure call644 # end for t.penUpprocedureName?(arguments?) # procedure call645 t.hideprocedureName?(arguments?) # procedure call646 # end procedure
+static void drawDragonname?(Turtle t, int order, string turns, double side, double cornerparameter definitions?) { // procedure647 var pname? = (200.0/order).floor()value or expression?;648 var turnIname? = 0value or expression?;649 +foreach (var turnitem? in turnssource?) { 650 turnIvariableName? = (if_(turn.equals(left), 1, -1))value or expression?; // assignment651 t.turnprocedureName?(-45*turnIarguments?); // procedure call652 t.moveprocedureName?(cornerarguments?); // procedure call653 t.turnprocedureName?(-45*turnIarguments?); // procedure call654 t.moveprocedureName?(sidearguments?); // procedure call655 sleep_msprocedureName?(parguments?); // procedure call656 } // end foreach t.penUpprocedureName?(arguments?); // procedure call657 t.hideprocedureName?(arguments?); // procedure call658 } // end procedure
+Sub drawDragonname?(t As Turtle, order As Integer, turns As String, side As Double, corner As Doubleparameter definitions?) ' procedure659 Dim pname? = (200.0/order).floor()value or expression? ' variable definition660 Dim turnIname? = 0value or expression? ' variable definition661 +For Each turnitem? In turnssource? 662 turnIvariableName? = (if_(turn.equals(left), 1, -1))value or expression? ' assignment663 t.turnprocedureName?(-45*turnIarguments?) ' procedure call664 t.moveprocedureName?(cornerarguments?) ' procedure call665 t.turnprocedureName?(-45*turnIarguments?) ' procedure call666 t.moveprocedureName?(sidearguments?) ' procedure call667 sleep_msprocedureName?(parguments?) ' procedure call668 Next turn t.penUpprocedureName?(arguments?) ' procedure call669 t.hideprocedureName?(arguments?) ' procedure call670 End Sub
+static void drawDragonname?(Turtle t, int order, String turns, double side, double cornerparameter definitions?) { // procedure671 var pname? = (200.0/order).floor()value or expression?;672 var turnIname? = 0value or expression?;673 +foreach (var turnitem? in turnssource?) { 674 turnIvariableName? = (if_(turn.equals(left), 1, -1))value or expression?; // assignment675 t.turnprocedureName?(-45*turnIarguments?); // procedure call676 t.moveprocedureName?(cornerarguments?); // procedure call677 t.turnprocedureName?(-45*turnIarguments?); // procedure call678 t.moveprocedureName?(sidearguments?); // procedure call679 sleep_msprocedureName?(parguments?); // procedure call680 } // end foreach t.penUpprocedureName?(arguments?); // procedure call681 t.hideprocedureName?(arguments?); // procedure call682 } // 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? 683 +for yitem? in range(0, 30)source? 684 variable colourname? set to nextCellValue(grid, x, y)value or expression?685 assign nextGen[x][y]variableName? to colourvalue or expression?686 end for end for
+for xitem? in range(0, 40)source?: 687 +for yitem? in range(0, 30)source?: 688 colourname? = nextCellValue(grid, x, y)value or expression? # variable definition689 nextGen[x][y]variableName? = colourvalue or expression? # assignment690 # end for # end for
+foreach (var xitem? in range(0, 40)source?) { 691 +foreach (var yitem? in range(0, 30)source?) { 692 var colourname? = nextCellValue(grid, x, y)value or expression?;693 nextGen[x][y]variableName? = colourvalue or expression?; // assignment694 } // end foreach } // end foreach
+For Each xitem? In range(0, 40)source? 695 +For Each yitem? In range(0, 30)source? 696 Dim colourname? = nextCellValue(grid, x, y)value or expression? ' variable definition697 nextGen(x)(y)variableName? = colourvalue or expression? ' assignment698 Next y Next x
+foreach (var xitem? in range(0, 40)source?) { 699 +foreach (var yitem? in range(0, 30)source?) { 700 var colourname? = nextCellValue(grid, x, y)value or expression?;701 nextGen[x][y]variableName? = colourvalue or expression?; // assignment702 } // 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 ListlistListListList (of any kind), or a variable of type ListlistListListList
    • 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 blocksblocksblocksblocksblocks:

call displayBlocksprocedureName?(blocksarguments?)703
displayBlocksprocedureName?(blocksarguments?) # procedure call704
displayBlocksprocedureName?(blocksarguments?); // procedure call705
displayBlocksprocedureName?(blocksarguments?) ' procedure call706
displayBlocksprocedureName?(blocksarguments?); // procedure call707

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

call moveGrowBurstprocedureName?(bubblesarguments?)708
moveGrowBurstprocedureName?(bubblesarguments?) # procedure call709
moveGrowBurstprocedureName?(bubblesarguments?); // procedure call710
moveGrowBurstprocedureName?(bubblesarguments?) ' procedure call711
moveGrowBurstprocedureName?(bubblesarguments?); // procedure call712
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?) 713 variable headname? set to headRef.value()value or expression?714 variable tailname? set to tailRef.value()value or expression?715 variable currentDirname? set to currentDirRef.value()value or expression?716 assign currentDirvariableName? to directionByKey(currentDir, getKey())value or expression?717 call tailRef.setprocedureName?(body[0]arguments?)718 call body.appendprocedureName?(headarguments?)719 call headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?)720 call currentDirRef.setprocedureName?(currentDirarguments?)721 end procedure
+def updateSnakename?(currentDirRef: AsRef[Direction], tailRef: AsRef[list[int]], headRef: AsRef[list[int]], body: list[list[int]]parameter definitions?) -> None: # procedure722 headname? = headRef.value()value or expression? # variable definition723 tailname? = tailRef.value()value or expression? # variable definition724 currentDirname? = currentDirRef.value()value or expression? # variable definition725 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression? # assignment726 tailRef.setprocedureName?(body[0]arguments?) # procedure call727 body.appendprocedureName?(headarguments?) # procedure call728 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?) # procedure call729 currentDirRef.setprocedureName?(currentDirarguments?) # procedure call730 # end procedure
+static void updateSnakename?(AsRef<Direction> currentDirRef, AsRef<List<int>> tailRef, AsRef<List<int>> headRef, List<List<int>> bodyparameter definitions?) { // procedure731 var headname? = headRef.value()value or expression?;732 var tailname? = tailRef.value()value or expression?;733 var currentDirname? = currentDirRef.value()value or expression?;734 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression?; // assignment735 tailRef.setprocedureName?(body[0]arguments?); // procedure call736 body.appendprocedureName?(headarguments?); // procedure call737 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?); // procedure call738 currentDirRef.setprocedureName?(currentDirarguments?); // procedure call739 } // 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?) ' procedure740 Dim headname? = headRef.value()value or expression? ' variable definition741 Dim tailname? = tailRef.value()value or expression? ' variable definition742 Dim currentDirname? = currentDirRef.value()value or expression? ' variable definition743 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression? ' assignment744 tailRef.setprocedureName?(body(0)arguments?) ' procedure call745 body.appendprocedureName?(headarguments?) ' procedure call746 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?) ' procedure call747 currentDirRef.setprocedureName?(currentDirarguments?) ' procedure call748 End Sub
+static void updateSnakename?(AsRef<Direction> currentDirRef, AsRef<List<int>> tailRef, AsRef<List<int>> headRef, List<List<int>> bodyparameter definitions?) { // procedure749 var headname? = headRef.value()value or expression?;750 var tailname? = tailRef.value()value or expression?;751 var currentDirname? = currentDirRef.value()value or expression?;752 currentDirvariableName? = directionByKey(currentDir, getKey())value or expression?; // assignment753 tailRef.setprocedureName?(body[0]arguments?); // procedure call754 body.appendprocedureName?(headarguments?); // procedure call755 headRef.setprocedureName?(getAdjacentSquare(head, currentDir)arguments?); // procedure call756 currentDirRef.setprocedureName?(currentDirarguments?); // procedure call757 } // 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? 828 variable resultname? set to 1value or expression?829 +if n > 1condition? then 830 assign resultvariableName? to n*factorial(n - 1)value or expression?831 elif n < 0condition? then832 throw CustomErrorexception type? "Factorial cannot be called with a negative argument"message?833 end if return resultvalue or expression?834 end function
+def factorialname?(n: intparameter definitions?) -> intType?: # function835 resultname? = 1value or expression? # variable definition836 +if n > 1condition?: 837 resultvariableName? = n*factorial(n - 1)value or expression? # assignment838 elif n < 0condition?: # else if839 raise CustomErrorexception type?("Factorial cannot be called with a negative argument"message?)840 # end if return resultvalue or expression?841 # end function
+static intType? factorialname?(int nparameter definitions?) { // function842 var resultname? = 1value or expression?;843 +if (n > 1condition?) { 844 resultvariableName? = n*factorial(n - 1)value or expression?; // assignment845 } else if (n < 0condition?) {846 throw new CustomErrorexception type?("Factorial cannot be called with a negative argument"message?);847 } // end if return resultvalue or expression?;848 } // end function
+Function factorialname?(n As Integerparameter definitions?) As IntegerType? 849 Dim resultname? = 1value or expression? ' variable definition850 +If n > 1condition? Then 851 resultvariableName? = n*factorial(n - 1)value or expression? ' assignment852 ElseIf n < 0condition? Then853 Throw New CustomErrorexception type?("Factorial cannot be called with a negative argument"message?)854 End If Return resultvalue or expression?855 End Function
+static intType? factorialname?(int nparameter definitions?) { // function856 var resultname? = 1value or expression?;857 +if (n > 1condition?) { 858 resultvariableName? = n*factorial(n - 1)value or expression?; // assignment859 } else if (n < 0condition?) {860 throw new CustomErrorexception type?("Factorial cannot be called with a negative argument"message?);861 } // end if return resultvalue or expression?;862 } // 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? 888 # Normal casescomment? assert colour(5)actual (computed) value? evaluates to greenexpected value? not run889 # Edge casescomment? assert colour(1)actual (computed) value? evaluates to redexpected value? not run890 assert colour(10)actual (computed) value? evaluates to 0xFF99CCexpected value? not run891 # Error casescomment? assert colour(11)actual (computed) value? evaluates to "Out of range index: 10 size: 10"expected value? not run892 end test
+class Test_colour(unittest.TestCase):
 def test_colourtest_name?(self) -> None: 893
# Normal casescomment? self.assertEqual(colour(5)actual (computed) value?, greenexpected value?) not run894 # Edge casescomment? self.assertEqual(colour(1)actual (computed) value?, redexpected value?) not run895 self.assertEqual(colour(10)actual (computed) value?, 0xFF99CCexpected value?) not run896 # Error casescomment? self.assertEqual(colour(11)actual (computed) value?, "Out of range index: 10 size: 10"expected value?) not run897 # end test
+[TestClass] class Test_colour
[TestMethod] static void test_colourtest_name?() { 898
// Normal casescomment? Assert.AreEqual(greenexpected value?, colour(5)actual (computed) value?); not run899 // Edge casescomment? Assert.AreEqual(redexpected value?, colour(1)actual (computed) value?); not run900 Assert.AreEqual(0xFF99CCexpected value?, colour(10)actual (computed) value?); not run901 // Error casescomment? Assert.AreEqual("Out of range index: 10 size: 10"expected value?, colour(11)actual (computed) value?); not run902 }} // end test
+<TestClass Class Test_colour
 <TestMethod> Sub test_colourtest_name?() 903
' Normal casescomment? Assert.AreEqual(greenexpected value?, colour(5)actual (computed) value?) not run904 ' Edge casescomment? Assert.AreEqual(redexpected value?, colour(1)actual (computed) value?) not run905 Assert.AreEqual(&HFF99CCexpected value?, colour(10)actual (computed) value?) not run906 ' Error casescomment? Assert.AreEqual("Out of range index: 10 size: 10"expected value?, colour(11)actual (computed) value?) not run907  End Sub
End Class
+class Test_colour {
@Test static void test_colourtest_name?() { 908
// Normal casescomment? assertEquals(greenexpected value?, colour(5)actual (computed) value?); not run909 // Edge casescomment? assertEquals(redexpected value?, colour(1)actual (computed) value?); not run910 assertEquals(0xFF99CCexpected value?, colour(10)actual (computed) value?); not run911 // Error casescomment? assertEquals("Out of range index: 10 size: 10"expected value?, colour(11)actual (computed) value?); not run912 }} // end test

What you need to know about an assert statement

  • An assert instruction evaluates to 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 StringstrstringStringString or ListlistListListList is returned.

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

Example using indexing on a string:

+main 913 variable aname? set to "Hello world!"value or expression?914 print(a[4]value or expression?)915 print(a.subString(4, a.length())value or expression?)916 print(a.subString(0, 7)value or expression?)917 end main
+def main() -> None: 918 aname? = "Hello world!"value or expression? # variable definition919 print(a[4]value or expression?)920 print(a.subString(4, a.length())value or expression?)921 print(a.subString(0, 7)value or expression?)922 # end main
+static void main() { 923 var aname? = "Hello world!"value or expression?;924 Console.WriteLine(a[4]value or expression?); // print statement925 Console.WriteLine(a.subString(4, a.length())value or expression?); // print statement926 Console.WriteLine(a.subString(0, 7)value or expression?); // print statement927 } // end main
+Sub main() 928 Dim aname? = "Hello world!"value or expression? ' variable definition929 Console.WriteLine(a(4)value or expression?) ' print statement930 Console.WriteLine(a.subString(4, a.length())value or expression?) ' print statement931 Console.WriteLine(a.subString(0, 7)value or expression?) ' print statement932 End Sub
+static void main() { 933 var aname? = "Hello world!"value or expression?;934 System.out.println(a[4]value or expression?); // print statement935 System.out.println(a.subString(4, a.length())value or expression?); // print statement936 System.out.println(a.subString(0, 7)value or expression?); // print statement937 } // end main






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

And on a ListlistListListList:

+main 938 variable liname? set to new List<of Int>()value or expression?939 assign livariableName? to [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]value or expression?940 print(li[4]value or expression?)941 print(li.subList(4, li.length())value or expression?)942 print(li.subList(0, 7)value or expression?)943 end main
+def main() -> None: 944 liname? = list[int]()value or expression? # variable definition945 livariableName? = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]value or expression? # assignment946 print(li[4]value or expression?)947 print(li.subList(4, li.length())value or expression?)948 print(li.subList(0, 7)value or expression?)949 # end main
+static void main() { 950 var liname? = new List<int>()value or expression?;951 livariableName? = new [] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}value or expression?; // assignment952 Console.WriteLine(li[4]value or expression?); // print statement953 Console.WriteLine(li.subList(4, li.length())value or expression?); // print statement954 Console.WriteLine(li.subList(0, 7)value or expression?); // print statement955 } // end main
+Sub main() 956 Dim liname? = New List(Of Integer)()value or expression? ' variable definition957 livariableName? = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}value or expression? ' assignment958 Console.WriteLine(li(4)value or expression?) ' print statement959 Console.WriteLine(li.subList(4, li.length())value or expression?) ' print statement960 Console.WriteLine(li.subList(0, 7)value or expression?) ' print statement961 End Sub
+static void main() { 962 var liname? = new List<int>()value or expression?;963 livariableName? = list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)value or expression?; // assignment964 System.out.println(li[4]value or expression?); // print statement965 System.out.println(li.subList(4, li.length())value or expression?); // print statement966 System.out.println(li.subList(0, 7)value or expression?); // print statement967 } // end main








4
    (the value of the ListlistListListList 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))print(pow(4, 4))print(pow(4, 4))print(pow(4, 4))print(pow(4, 4))
variable cname? set to sqrt(pow(a, 2) + pow(b, 2))value or expression?5cname? = sqrt(pow(a, 2) + pow(b, 2))value or expression? # variable definition6var cname? = sqrt(pow(a, 2) + pow(b, 2))value or expression?;7Dim cname? = sqrt(pow(a, 2) + pow(b, 2))value or expression? ' variable definition8var cname? = sqrt(pow(a, 2) + pow(b, 2))value or expression?;9

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? 968 return a and not b or b and not avalue or expression?969 end function
+def exOrname?(a: bool, b: boolparameter definitions?) -> boolType?: # function970 return a and not b or b and not avalue or expression?971 # end function
+static boolType? exOrname?(bool a, bool bparameter definitions?) { // function972 return a && !b || b && !avalue or expression?;973 } // end function
+Function exOrname?(a As Boolean, b As Booleanparameter definitions?) As BooleanType? 974 Return a And Not b Or b And Not avalue or expression?975 End Function
+static booleanType? exOrname?(boolean a, boolean bparameter definitions?) { // function976 return a && !b || b && !avalue or expression?;977 } // end function

String operator

The + operator is also used for concatenating StringstrstringStringString values.

Equality testing

Testing equality of Value types: IntintintIntegerint, FloatfloatdoubleDoubledouble and BooleanboolboolBooleanboolean

Equality testing of these Value types uses theisisisisis and isntisntisntisntisnt operators between two values and returns a BooleanboolboolBooleanboolean result:

  • isntisntisntisntisnt returns the opposite of isisisisis.
  • (a is b)(a == b)(a == b)(a = b)(a == b) returns trueTruetrueTruetrue, if aaaaa and bbbbb 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 isisisisis will return trueTruetrueTruetrue (and isntisntisntisntisnt 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 Reference types: including StringstrstringStringString

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

  • +if listA.equals(listB)condition? then 978 new code end if
    +if listA.equals(listB)condition?: 979 new code # end if
    +if (listA.equals(listB)condition?) { 980 new code } // end if
    +If listA.equals(listB)condition? Then 981 new code End If
    +if (listA.equals(listB)condition?) { 982 new code } // end if
  • assign differsvariableName? to set1.notEqualTo(set2)value or expression?10differsvariableName? = set1.notEqualTo(set2)value or expression? # assignment11differsvariableName? = set1.notEqualTo(set2)value or expression?; // assignment12differsvariableName? = set1.notEqualTo(set2)value or expression? ' assignment13differsvariableName? = set1.notEqualTo(set2)value or expression?; // assignment14

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?15xvariableName? = (a > b) and (b < c)value or expression? # assignment16xvariableName? = (a > b) && (b < c)value or expression?; // assignment17xvariableName? = (a > b) And (b < c)value or expression? ' assignment18xvariableName? = (a > b) && (b < c)value or expression?; // assignment19
assign xvariableName? to (a + b) > (c - d)value or expression?20xvariableName? = (a + b) > (c - d)value or expression? # assignment21xvariableName? = (a + b) > (c - d)value or expression?; // assignment22xvariableName? = (a + b) > (c - d)value or expression? ' assignment23xvariableName? = (a + b) > (c - d)value or expression?; // assignment24

Function reference

An expression may simply be a reference to a function, or it may include several function references within it. Examples:
+main 983 print(sin(radians(30))value or expression?)984 variable xname? set to pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression?985 variable namename? set to input("Your name")value or expression?986 print(name.upperCase()value or expression?)987 end main
+def main() -> None: 988 print(sin(radians(30))value or expression?)989 xname? = pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression? # variable definition990 namename? = input("Your name")value or expression? # variable definition991 print(name.upperCase()value or expression?)992 # end main
+static void main() { 993 Console.WriteLine(sin(radians(30))value or expression?); // print statement994 var xname? = pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression?;995 var namename? = input("Your name")value or expression?;996 Console.WriteLine(name.upperCase()value or expression?); // print statement997 } // end main
+Sub main() 998 Console.WriteLine(sin(radians(30))value or expression?) ' print statement999 Dim xname? = pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression? ' variable definition1000 Dim namename? = input("Your name")value or expression? ' variable definition1001 Console.WriteLine(name.upperCase()value or expression?) ' print statement1002 End Sub
+static void main() { 1003 System.out.println(sin(radians(30))value or expression?); // print statement1004 var xname? = pow(sin(radians(30)), 2) + pow(cos(radians(30)), 2)value or expression?;1005 var namename? = input("Your name")value or expression?;1006 System.out.println(name.upperCase()value or expression?); // print statement1007 } // 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:

lambda x as Float => x.round(8)
lambda x: float: x.round(8)
double x => x.round(8)
Function (x As Double) x.round(8)
(double x) -> x.round(8)

is equivalent to defining a function:

+function someNamename?(x as Floatparameter definitions?) returns FloatType? 1008 return x.round(8)value or expression?1009 end function
+def someNamename?(x: floatparameter definitions?) -> floatType?: # function1010 return x.round(8)value or expression?1011 # end function
+static doubleType? someNamename?(double xparameter definitions?) { // function1012 return x.round(8)value or expression?;1013 } // end function
+Function someNamename?(x As Doubleparameter definitions?) As DoubleType? 1014 Return x.round(8)value or expression?1015 End Function
+static doubleType? someNamename?(double xparameter definitions?) { // function1016 return x.round(8)value or expression?;1017 } // 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:

lambda n as Node => n.point.equals(p)
lambda n: Node: n.point.equals(p)
Node n => n.point.equals(p)
Function (n As Node) n.point.equals(p)
(Node n) -> n.point.equals(p)
lambda dd as Dictionary<of String, Int>, answer as String => incrementCount(dd, answer, attempt)
lambda dd: Dictionary[str, int], answer: str: incrementCount(dd, answer, attempt)
Dictionary<string, int> dd, string answer => incrementCount(dd, answer, attempt)
Function (dd As Dictionary(Of String, Integer), answer As String) incrementCount(dd, answer, attempt)
(Dictionary<String, int> dd, String answer) -> incrementCount(dd, answer, attempt)

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 evaluation of a Boolean value:

Its syntax is shown here:

variable isBiggername? set to if_(a >= b, true, false)value or expression?25isBiggername? = if_(a >= b, True, False)value or expression? # variable definition26var isBiggername? = if_(a >= b, true, false)value or expression?;27Dim isBiggername? = if_(a >= b, True, False)value or expression? ' variable definition28var isBiggername? = if_(a >= b, true, false)value or expression?;29

Similarly, but returning an evaluated expression:

variable dname? set to if_(c < 580, c - 40, c + 60)value or expression?30dname? = if_(c < 580, c - 40, c + 60)value or expression? # variable definition31var dname? = if_(c < 580, c - 40, c + 60)value or expression?;32Dim dname? = if_(c < 580, c - 40, c + 60)value or expression? ' variable definition33var dname? = if_(c < 580, c - 40, c + 60)value or expression?;34

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? 1018 return if_(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?1019 end function
+def fooname?(parameter definitionsparameter definitions?) -> intType?: # function1020 return if_(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?1021 # end function
+static intType? fooname?(parameter definitionsparameter definitions?) { // function1022 return if_(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?;1023 } // end function
+Function fooname?(parameter definitionsparameter definitions?) As IntegerType? 1024 Return if_(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?1025 End Function
+static intType? fooname?(parameter definitionsparameter definitions?) { // function1026 return if_(isGreen(attempt, target, n), setChar(attempt, n, "*"), attempt)value or expression?;1027 } // end function

   ● Using an if_ function in a procedure call print:

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

+static void main() { 1 var pname? = unicode(0x03c0)value or expression?;2 testPiprocedureName?(p, 3arguments?); // procedure call3 testPiprocedureName?(p, 4arguments?); // procedure call4 } // end main +static void testPiname?(String p, double vparameter definitions?) { // procedure5 System.out.println(value or expression?); // print statement6 } // end procedure
} // end Global





π > 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?35headRefname? = AsRef[list[int]](head)value or expression? # variable definition36var headRefname? = new AsRef<List<int>>(head)value or expression?;37Dim headRefname? = New AsRef(Of List(Of Integer))(head)value or expression? ' variable definition38var headRefname? = new AsRef<List<int>>(head)value or expression?;39

Miscellaneous

Data types

There are two fundamentally different kinds of data type, differing by how their values are passed around in a running program:

  • Value types that contain a single value, and are passed around directly:
    IntintintIntegerint, FloatfloatdoubleDoubledouble, BooleanboolboolBooleanboolean
  • Reference types that contain a more complex structure of values, and are passed around by references to them:
    StringstrstringStringString, ListlistListListList, etc. as well as any concrete class pr abstract class.

This table lists the Value types and the standard data structure (Reference) types, each linking to its definition and further details:

Value types
Value data
Integer
IntintintIntegerint
Floating point
(Real) number

FloatfloatdoubleDoubledouble

Truth value
BooleanboolboolBooleanboolean
Reference types
Standard
data
Character string
StringstrstringStringString
Regular expression
RegExpRegExpRegExpRegExpRegExp
List
ListlistListListList
Look-up dictionary
DictionaryDictionaryDictionaryDictionaryDictionary
structures Set
HashSetHashSetHashSetHashSetHashSet
LIFO stack
StackStackStackStackStack
FIFO queue
QueueQueueQueueQueueQueue
Graphics Turtle graphics
TurtleTurtleTurtleTurtleTurtle
SVG vector graphics
CircleVGCircleVGCircleVGCircleVGCircleVG
ImageVGImageVGImageVGImageVGImageVG
LineVGLineVGLineVGLineVGLineVG
RectangleVGRectangleVGRectangleVGRectangleVGRectangleVG
RawVGRawVGRawVGRawVGRawVG
Block graphics
(2D array)

List<of List<of Int>>list[list[int]]List<List<int>>List(Of List(Of Integer))List<List<int>>
Data I/O Text file output
TextFileWriterTextFileWriterTextFileWriterTextFileWriterTextFileWriter
Text file input
TextFileReaderTextFileReaderTextFileReaderTextFileReaderTextFileReader
User-defined Enumeration
enumenumenumenumenum
Concrete class
classclassclassclassclass
Abstract class
abstract classclassclassclassclass
Other Random number
RandomRandomRandomRandomRandom
Argument reference
AsRefAsRefAsRefAsRefAsRef
Optional property
MaybeMaybeMaybeMaybeMaybe

Named values are statically typed: their type, once defined, cannot be changed. A named value's type is:

  • either automatically inferred from the value of the literal or expression defining a value type,
  • or specified in the definition of:
    • data structures whose items have a particular type, e.g. List<of Int>list[int]List<int>List(Of Integer)List<int>
    • parameters of functions and procedures
    • return value of a function
    • property of an abstract class or concrete class

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?40nname? = s.length()value or expression? # variable definition41var nname? = s.length()value or expression?;42Dim nname? = s.length()value or expression? ' variable definition43var nname? = s.length()value or expression?;44
  • To apply a procedure to a named value, e.g. to initialise ListlistListListList lilililili using a library procedure:
    call li.initialiseprocedureName?(10, 0arguments?)45li.initialiseprocedureName?(10, 0arguments?) # procedure call46li.initialiseprocedureName?(10, 0arguments?); // procedure call47li.initialiseprocedureName?(10, 0arguments?) ' procedure call48li.initialiseprocedureName?(10, 0arguments?); // procedure call49
  • 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:
    new codenew codenew codenew code
    public class Global {

    new code
    } // end Global
  • 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?7
    pointname? = (3, 7)value or expression? # variable definition8
    var pointname? = (3, 7)value or expression?;9
    Dim pointname? = (3, 7)value or expression? ' variable definition10
    var pointname? = (3, 7)value or expression?;11
    variable xname? set to point.item_0value or expression?12
    xname? = point.item_0value or expression? # variable definition13
    var xname? = point.item_0value or expression?;14
    Dim xname? = point.item_0value or expression? ' variable definition15
    var xname? = point.item_0value or expression?;16
  • 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?17
    class SuitName?(Enum):
    spades = 1
    hearts = 2
    diamonds = 3
    clubs = 4
    values?
    18
    enum SuitName? {spades, hearts, diamonds, clubsvalues?values?}19
    Enum SuitName?
    spades = 0
    hearts = 1
    diamonds = 2
    clubs = 3
    End Enum
    values?
    20
    enum SuitName? {spades, hearts, diamonds, clubsvalues?values?}21
    variable suitname? set to Suit.spadesvalue or expression?22
    suitname? = Suit.spadesvalue or expression? # variable definition23
    var suitname? = Suit.spadesvalue or expression?;24
    Dim suitname? = Suit.spadesvalue or expression? ' variable definition25
    var suitname? = Suit.spadesvalue or expression?;26

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 +, isntisntisntisntisnt, 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?)1procedureNameprocedureName?(arguments?) # procedure call2procedureNameprocedureName?(arguments?); // procedure call3procedureNameprocedureName?(arguments?) ' procedure call4procedureNameprocedureName?(arguments?); // procedure call5 to call a library procedure, or user-defined procedure.
  • call instanceName.procedureMethodNameprocedureName?(arguments?)6instanceName.procedureMethodNameprocedureName?(arguments?) # procedure call7instanceName.procedureMethodNameprocedureName?(arguments?); // procedure call8instanceName.procedureMethodNameprocedureName?(arguments?) ' procedure call9instanceName.procedureMethodNameprocedureName?(arguments?); // procedure call10 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 .


Procedural programming Reference go to the top