Functional programming Reference

Getting started

Select functional programming paradigm

To undertake functional programming (FP) in Elan, you should first be reasonably familiar with procedural programming (PP) with Elan, and ideally, with object-oriented programming (OOP) with Elan. Then, switch to the functional paradigm from the menu at the top of the IDE. This changes two things:

  1. The List of demo programs will now show only programs that use FP. A very good place to start is to look at the Snake - functional demo, which is functionally identical to the (procedural) original, but it written using FP design and coding patterns.
  2. It will change some of the options available on the new code menus in the editor:
    • Within a function, the only type of instruction that you may add (above the fixed returnreturnreturnreturnreturn instruction) is a let statement (plus, of course, comments).
    • Within a concrete or abstract class, you may add function methods, but not procedure methods.
    • Within a concrete or abstract class, there is a new kind of method named a copy with method. Actually, this is just a template for a specific form of function method which is particularly useful in FP.
    The rationale for disallowing some of the instructions you are used to in PP or OOP, and how to use the new instructions, will become clear as you read through this document.

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

Principles of functional programming

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

Functions must be pure

A pure function observes the following rules:

Dedicated functional programming languages – such as Haskell – enforce these rules automatically. Common 'multi-paradigm' languages – including Python, VB, C#, and Java – do not enforce them. However, if any of those languages are used within Elan, then the Elan editor enforces the rules automatically.

This means that every function you see within Elan, and every function you have ever written within Elan (in Python, VB, C#, or Java), is a pure function. That is the reason why, even when you are using the procedural or object-oriented paradigms and you are within a function, you cannot add a print statement, input statement, or procedure call, nor can you make use of system methods such as input, random, or clock. This experience gives you a huge advantage in transitioning to FP.

As much of the code as possible should be in functions

When using FP in Python, VB, C#, or Java, every program still needs a main routine, which is necessarily procedural in nature, because it cannot work without dependencies on the system, and it generates side effects such as output to a display.

To avoid your main routine becoming difficult to read, and to avoid duplicated code, you may still define procedures (which also, inherently, use procedural programming to manage input/output and other system operations). But this second principle of FP implies that the main routine together with any necessary procedures should be kept as small as possible:

This may seem logical when a program is obviously heavily dependent upon calculation, logic, or data transformation – as, for example, the Best Fit demo program. But what about an interactive game, where most of the logic is concerned with managing the user input and the display? Consider the two demo programs: Snake – procedural and Snake - procedural. The two programs behave identically when run. We recommend that you take the time to explore the these two programs in detail,but here's a summary of the key differences:

Snake - procedural Snake - functional
Total instructions, excluding tests 72 115
Number of procedures, including main 4 1
Number of functions 4 21 (incl. function methods within classes)
Instructions within procedures, including main 47 = 65% 11 = 10%
Instructions within functions 25 = 35% 104 = 90%

Every function can and should be unit tested

The idea of writing automated tests isn't restricted to functional programming. The tests offered by Elan are known as 'unit tests', and are designed to test functions.

Such tests are extremely easy to write, and run automatically when the program is loaded from a file, and then run again after every edit that results in code that compiles.

Unit tests give you huge confidence that your code does what it is supposed to do, and that subsequent changes, whether as a result of debugging or of implementing new requirements, do not break existing functionality. Without unit tests, bugs might go unnoticed and surface only later and less obviously.

On some platforms it is possible to write automated tests that also cover procedures (including the main routine). These are sometimes referred to as 'end to end tests', or 'UI tests' because they can simulate a user interacting with the program. But such tests are much harder to write than unit tests, and notoriously 'brittle' – meaning that a tiny change to code can break many existing tests and take hours to fix. Changes external to your program, such as a routine upgrade to the browser or operating system, can easily render such tests invalid. Elan does not currently support those kind of tests.

Note, however, that outside Elan, Python, VB, C#, and Java, have unit testing frameworks that can theoretically allow unit tests to run any method. This can be dangerous because running the tests might, without your awareness, be updating a file or database, or sending Internet messages. Unit tests are safe only when applied to safe (i.e. pure) functions.

It follows that the higher the percentage of a program's total instructions that exist within functions, the higher the percentage of your code that can be safely unit tested. The table below shows the percentage of instructions covered by tests for two of the demo programs that offer both procedural and functional implementations. You can verify this information by examining the demo code.

Snake - procedural Snake - functional Life - procedural Life - functional
Total instructions, excluding tests 72 115 78 76
Instructions covered by tests 53% 90% 73% 90%

Note on demo program Best Fit

The function body should consists of a 'return', plus optional let instructions only

In the FP demos you will see that a significant number of functions that simply returnreturnreturnreturnreturn an expression to be evaluated, for example:

+function gameOvername?(g as Gameparameter definitions?) returns BooleanType? 0 return g.body.contains(g.head) or hasHitEdge(g)value or expression?1 end function
+def gameOvername?(g: Gameparameter definitions?) -> boolType?: # function2 return g.body.contains(g.head) or hasHitEdge(g)value or expression?3 # end function
+static boolType? gameOvername?(Game gparameter definitions?) { // function4 return g.body.contains(g.head) || hasHitEdge(g)value or expression?;5 } // end function
+Function gameOvername?(g As Gameparameter definitions?) As BooleanType? 6 Return g.body.contains(g.head) Or hasHitEdge(g)value or expression?7 End Function
+static booleanType? gameOvername?(Game gparameter definitions?) { // function8 return g.body.contains(g.head) || hasHitEdge(g)value or expression?;9 } // end function

Other functions precede the returnreturnreturnreturnreturn by one or more let statements, for example

+function hasHitEdgename?(g as Gameparameter definitions?) returns BooleanType? 10 let xname? be g.head.xvalue or expression?11 let yname? be g.head.yvalue or expression?12 return (x is -1) or (y is -1) or (x is 40) or (y is 30)value or expression?13 end function
+def hasHitEdgename?(g: Gameparameter definitions?) -> boolType?: # function14 xname? = g.head.xvalue or expression? # let15 yname? = g.head.yvalue or expression? # let16 return (x == -1) or (y == -1) or (x == 40) or (y == 30)value or expression?17 # end function
+static boolType? hasHitEdgename?(Game gparameter definitions?) { // function18 var xname? = g.head.xvalue or expression?; // let19 var yname? = g.head.yvalue or expression?; // let20 return (x == -1) || (y == -1) || (x == 40) || (y == 30)value or expression?;21 } // end function
+Function hasHitEdgename?(g As Gameparameter definitions?) As BooleanType? 22 Dim xname? = g.head.xvalue or expression? ' let23 Dim yname? = g.head.yvalue or expression? ' let24 Return (x = -1) Or (y = -1) Or (x = 40) Or (y = 30)value or expression?25 End Function
+static booleanType? hasHitEdgename?(Game gparameter definitions?) { // function26 var xname? = g.head.xvalue or expression?; // let27 var yname? = g.head.yvalue or expression?; // let28 return (x == -1) || (y == -1) || (x == 40) || (y == 30)value or expression?;29 } // end function

A let statement works like a variable definition except that the named value created may not be reassigned. This may sound like a local constant, but a let statement differs from a constant in that:

In the four languages supported by Elan, let statements are really just disguised variable definitions, executed sequentially. But in a pure FP language such as Haskell, OCAML, or F#, let statement (some languages permit 'let clauses' within an expression) are executed lazily, meaning that execution of a function starts by evaluating the returned expression, and evaluates the sub-expressions defined in let statements only when or if they are needed. So getting used to using let statements in Elan leaves you better prepared to move onto a pure FP language at a future point.

A let statement defines a 'sub-expression' that will be used in another expression, for one (or more) of these reasons:

When running in the functional paradigm, let statement is the only type of instruction that you may add within a function, and the only type in addition to an assert statement that may be added into a test. (You can still, of course, add comments.)

Selection is implemented using 'conditional expressions'

When running in the Functional paradigm, you cannot add an if statement into a function. Selection is handled by means of a 'conditional expression'.

All of the languages supported by Elan have their own custom syntax for conditional expressions. For simplicity, and understandability, Elan offers an if function that is used identically in all the languages. (Incidentally, the if function works the same way as it does in an Excel spreadsheet). Here are some examples of conditional expressions from the demo programs:

+function betterOfname?(wc1 as WordCount, wc2 as WordCount, possAnswers as List<of String>parameter definitions?) returns WordCountType? 30 let isBettername? be wc2.count < wc1.countvalue or expression?31 let isEqualAndPossAnswername? be (wc2.count is wc1.count) and possAnswers.contains(wc2.word)value or expression?32 return if_(isBetter or isEqualAndPossAnswer, wc2, wc1)value or expression?33 end function
+def betterOfname?(wc1: WordCount, wc2: WordCount, possAnswers: list[str]parameter definitions?) -> WordCountType?: # function34 isBettername? = wc2.count < wc1.countvalue or expression? # let35 isEqualAndPossAnswername? = (wc2.count == wc1.count) and possAnswers.contains(wc2.word)value or expression? # let36 return if_(isBetter or isEqualAndPossAnswer, wc2, wc1)value or expression?37 # end function
+static WordCountType? betterOfname?(WordCount wc1, WordCount wc2, List<string> possAnswersparameter definitions?) { // function38 var isBettername? = wc2.count < wc1.countvalue or expression?; // let39 var isEqualAndPossAnswername? = (wc2.count == wc1.count) && possAnswers.contains(wc2.word)value or expression?; // let40 return if_(isBetter || isEqualAndPossAnswer, wc2, wc1)value or expression?;41 } // end function
+Function betterOfname?(wc1 As WordCount, wc2 As WordCount, possAnswers As List(Of String)parameter definitions?) As WordCountType? 42 Dim isBettername? = wc2.count < wc1.countvalue or expression? ' let43 Dim isEqualAndPossAnswername? = (wc2.count = wc1.count) And possAnswers.contains(wc2.word)value or expression? ' let44 Return if_(isBetter Or isEqualAndPossAnswer, wc2, wc1)value or expression?45 End Function
+static WordCountType? betterOfname?(WordCount wc1, WordCount wc2, List<String> possAnswersparameter definitions?) { // function46 var isBettername? = wc2.count < wc1.countvalue or expression?; // let47 var isEqualAndPossAnswername? = (wc2.count == wc1.count) && possAnswers.contains(wc2.word)value or expression?; // let48 return if_(isBetter || isEqualAndPossAnswer, wc2, wc1)value or expression?;49 } // end function
+function nextCellValuename?(grid as List<of List<of Int>>, x as Int, y as Intparameter definitions?) returns IntType? 50 let livename? be willLive(grid[x][y], liveNeighbours(grid, x, y))value or expression?51 return if_(live, black, white)value or expression?52 end function
+def nextCellValuename?(grid: list[list[int]], x: int, y: intparameter definitions?) -> intType?: # function53 livename? = willLive(grid[x][y], liveNeighbours(grid, x, y))value or expression? # let54 return if_(live, black, white)value or expression?55 # end function
+static intType? nextCellValuename?(List<List<int>> grid, int x, int yparameter definitions?) { // function56 var livename? = willLive(grid[x][y], liveNeighbours(grid, x, y))value or expression?; // let57 return if_(live, black, white)value or expression?;58 } // end function
+Function nextCellValuename?(grid As List(Of List(Of Integer)), x As Integer, y As Integerparameter definitions?) As IntegerType? 59 Dim livename? = willLive(grid(x)(y), liveNeighbours(grid, x, y))value or expression? ' let60 Return if_(live, black, white)value or expression?61 End Function
+static intType? nextCellValuename?(List<List<int>> grid, int x, int yparameter definitions?) { // function62 var livename? = willLive(grid[x][y], liveNeighbours(grid, x, y))value or expression?; // let63 return if_(live, black, white)value or expression?;64 } // end function

The if function requires three arguments to be provided, in the following order:

  1. The condition, which may compare two values, or it may call another function that returns a Boolean value
  2. The value to be returned by the if function when the condition evaluates to trueTruetrueTruetrue
  3. The value to be returned by the if function when the condition evaluates to falseFalsefalseFalsefalse

Thus the if function performs a role somewhat similar to an if statement, but differs from it in these important respects:

Although if functions can be nested, nesting to more than one level can become very hard to read. When required, it is often better to delegate to a new function to perform part of the logic.

Iteration may be achieved using recursion

There are two ways to implement iteration within a function:

Either by using 'recursion', i.e. a recursive function (described here), or by the approach described in later.

A recursive function is one that at some point evaluates itself, and is best understood by example. In the Recursive functions demo, the factorial function is a good place to start:

+function factorialname?(n as Intparameter definitions?) returns IntType? 65 return if_(n < 2, 1, n*factorial(n - 1))value or expression?66 end function
+def factorialname?(n: intparameter definitions?) -> intType?: # function67 return if_(n < 2, 1, n*factorial(n - 1))value or expression?68 # end function
+static intType? factorialname?(int nparameter definitions?) { // function69 return if_(n < 2, 1, n*factorial(n - 1))value or expression?;70 } // end function
+Function factorialname?(n As Integerparameter definitions?) As IntegerType? 71 Return if_(n < 2, 1, n*factorial(n - 1))value or expression?72 End Function
+static intType? factorialname?(int nparameter definitions?) { // function73 return if_(n < 2, 1, n*factorial(n - 1))value or expression?;74 } // end function

It shows these important aspects of designing a recursive function:

Lists and other data structures are never mutated

In procedural programming a ListlistListListList may be mutated either by reassigning an indexed value, for example:

assign mySubjects[3]variableName? to "Geography"value or expression?85
mySubjects[3]variableName? = "Geography"value or expression? # assignment86
mySubjects[3]variableName? = "Geography"value or expression?; // assignment87
mySubjects(3)variableName? = "Geography"value or expression? ' assignment88
mySubjects[3]variableName? = "Geography"value or expression?; // assignment89
or by calling one of the available procedure methods on on it such as:
call mySubjects.appendprocedureName?("Music"arguments?)90
mySubjects.appendprocedureName?("Music"arguments?) # procedure call91
mySubjects.appendprocedureName?("Music"arguments?); // procedure call92
mySubjects.appendprocedureName?("Music"arguments?) ' procedure call93
mySubjects.appendprocedureName?("Music"arguments?); // procedure call94
but in FP this is not permitted. Data structures are never to be mutated. Instead, you may call a function that returns a copy of a data structure with specified differences from the original. For example, you may write:

let myNewSubjectsname? be mySubjects.withPut(3, "Geography")value or expression?95
myNewSubjectsname? = mySubjects.withPut(3, "Geography")value or expression? # let96
var myNewSubjectsname? = mySubjects.withPut(3, "Geography")value or expression?; // let97
Dim myNewSubjectsname? = mySubjects.withPut(3, "Geography")value or expression? ' let98
var myNewSubjectsname? = mySubjects.withPut(3, "Geography")value or expression?; // let99

or

let myNewSubjectsname? be mySubjects.withAppend("Music")value or expression?100
myNewSubjectsname? = mySubjects.withAppend("Music")value or expression? # let101
var myNewSubjectsname? = mySubjects.withAppend("Music")value or expression?; // let102
Dim myNewSubjectsname? = mySubjects.withAppend("Music")value or expression? ' let103
var myNewSubjectsname? = mySubjects.withAppend("Music")value or expression?; // let104

At first sight this may seem awkward, strange and certainly inefficient. But though you might never have thought about it in these terms, this is what you have always done when manipulating a StringstrstringStringString. You can never actually mutate a string, such as converting it to upper case or trimming off the leading spaces. You have to write, for example:

let myNewStringname? be myString.upperCase()value or expression?105
myNewStringname? = myString.upperCase()value or expression? # let106
var myNewStringname? = myString.upperCase()value or expression?; // let107
Dim myNewStringname? = myString.upperCase()value or expression? ' let108
var myNewStringname? = myString.upperCase()value or expression?; // let109

When you want to apply operations to all items in a ListlistListListList you need to use iteration, which means using either recursion or Higher-order Functions and within that technique make use of the various with.. methods. The sum function in the Recursive functions demo calculates the sum of all items in any ListlistListListList of floating point numbers (the tests included in the demo show example results):

+function sumname?(li as List<of Float>parameter definitions?) returns FloatType? 110 return if_(li.length() is 0, 0.0, li.head() + sum(li.tail()))value or expression?111 end function
+def sumname?(li: list[float]parameter definitions?) -> floatType?: # function112 return if_(li.length() == 0, 0.0, li.head() + sum(li.tail()))value or expression?113 # end function
+static doubleType? sumname?(List<double> liparameter definitions?) { // function114 return if_(li.length() == 0, 0.0, li.head() + sum(li.tail()))value or expression?;115 } // end function
+Function sumname?(li As List(Of Double)parameter definitions?) As DoubleType? 116 Return if_(li.length() = 0, 0.0, li.head() + sum(li.tail()))value or expression?117 End Function
+static doubleType? sumname?(List<double> liparameter definitions?) { // function118 return if_(li.length() == 0, 0.0, li.head() + sum(li.tail()))value or expression?;119 } // end function

Note the use of these two dot function calls on the Li
st lilililili(see Dot syntax in Language Reference):

In FP, algorithms to process lists are often written in terms of the 'head' and 'tail' of the List. You could just write e.g. li[0]li[0]li[0]li[0]li[0] and li.substring(1, li.length())li.substring(1, li.length())li.substring(1, li.length())li.substring(1, li.length())li.substring(1, li.length()) instead, with the same result. But using the head and tail methods makes your code easier to read and may make your intention clearer.

The next example combines the previous two ideas:

in a function to reverse the order or items in a ListlistListListList:
+function reversename?(li as List<of Float>parameter definitions?) returns List<of Float>Type? 120 return if_(li.length() < 2, li, reverse(li.tail()).withAppend(li.head()))value or expression?121 end function
+def reversename?(li: list[float]parameter definitions?) -> list[float]Type?: # function122 return if_(li.length() < 2, li, reverse(li.tail()).withAppend(li.head()))value or expression?123 # end function
+static List<double>Type? reversename?(List<double> liparameter definitions?) { // function124 return if_(li.length() < 2, li, reverse(li.tail()).withAppend(li.head()))value or expression?;125 } // end function
+Function reversename?(li As List(Of Double)parameter definitions?) As List(Of Double)Type? 126 Return if_(li.length() < 2, li, reverse(li.tail()).withAppend(li.head()))value or expression?127 End Function
+static List<double>Type? reversename?(List<double> liparameter definitions?) { // function128 return if_(li.length() < 2, li, reverse(li.tail()).withAppend(li.head()))value or expression?;129 } // end function

A more advanced example is the Merge Sort demo, where:

A function may be passed into another function

The alternative to writing your own recursive function is to use Higher-order Functions (see HoFs in Library Reference). A HoF is a function that takes in another function as a parameter. (Strictly speaking, a HoF may also be a function that returns another function, but Elan does not currently support that pattern.) Under the covers, HoFs are written using recursion, but you don't see this.

There are many possible HoFs, but three of them – filter, map and reduce – are so widely used to process lists in FP, and in many different ways, that they are sometimes referred to as the 'Holy Trinity of Functional Programming'. They can be used individually or in combination. The purpose and operation of each is explained below.

All of the languages supported by Elan have their own versions of filter, map and reduce that are similar in intent but have differing syntax, and sometimes different names (e.g. 'fold' in place of 'reduce').The Elan library has its own version of these functions which work the same way in each language.

Start by loading and running the demo program Map, Filter, Reduce, and look at the test:

+test test_Map_Filter_Reducetest_name? 130 let numsname? be [2.22, 5.37, 8.97, 7.53, 8.2, 9.43, 7.74, 7.03, 9.62, 2.5]value or expression?131 assert nums.filter(lessThan5)actual (computed) value? evaluates to [2.22, 2.5]expected value? not run132 assert nums.map(cube)actual (computed) value? evaluates to [10.94, 154.85, 721.73, 426.96, 551.37, 838.56, 463.68, 347.43, 890.28, 15.63]expected value? not run133 assert nums.reduce(1.0, product).floor()actual (computed) value? evaluates to 81480107expected value? not run134 assert nums.filter(lessThan5).map(inverse)actual (computed) value? evaluates to [0.45, 0.4]expected value? not run135 assert nums.filter(lessThan5).map(inverse).map(toString).reduce("results: ", concat)actual (computed) value? evaluates to "results: 0.45|0.4|"expected value? not run136 end test
+class Test_Map_Filter_Reduce(unittest.TestCase):
 def test_Map_Filter_Reducetest_name?(self) -> None: 137
numsname? = [2.22, 5.37, 8.97, 7.53, 8.2, 9.43, 7.74, 7.03, 9.62, 2.5]value or expression? # let138 self.assertEqual(nums.filter(lessThan5)actual (computed) value?, [2.22, 2.5]expected value?) not run139 self.assertEqual(nums.map(cube)actual (computed) value?, [10.94, 154.85, 721.73, 426.96, 551.37, 838.56, 463.68, 347.43, 890.28, 15.63]expected value?) not run140 self.assertEqual(nums.reduce(1.0, product).floor()actual (computed) value?, 81480107expected value?) not run141 self.assertEqual(nums.filter(lessThan5).map(inverse)actual (computed) value?, [0.45, 0.4]expected value?) not run142 self.assertEqual(nums.filter(lessThan5).map(inverse).map(toString).reduce("results: ", concat)actual (computed) value?, "results: 0.45|0.4|"expected value?) not run143 # end test
+[TestClass] class Test_Map_Filter_Reduce
[TestMethod] static void test_Map_Filter_Reducetest_name?() { 144
var numsname? = new [] {2.22, 5.37, 8.97, 7.53, 8.2, 9.43, 7.74, 7.03, 9.62, 2.5}value or expression?; // let145 Assert.AreEqual(new [] {2.22, 2.5}expected value?, nums.filter(lessThan5)actual (computed) value?); not run146 Assert.AreEqual(new [] {10.94, 154.85, 721.73, 426.96, 551.37, 838.56, 463.68, 347.43, 890.28, 15.63}expected value?, nums.map(cube)actual (computed) value?); not run147 Assert.AreEqual(81480107expected value?, nums.reduce(1.0, product).floor()actual (computed) value?); not run148 Assert.AreEqual(new [] {0.45, 0.4}expected value?, nums.filter(lessThan5).map(inverse)actual (computed) value?); not run149 Assert.AreEqual("results: 0.45|0.4|"expected value?, nums.filter(lessThan5).map(inverse).map(toString).reduce("results: ", concat)actual (computed) value?); not run150 }} // end test
+<TestClass Class Test_Map_Filter_Reduce
 <TestMethod> Sub test_Map_Filter_Reducetest_name?() 151
Dim numsname? = {2.22, 5.37, 8.97, 7.53, 8.2, 9.43, 7.74, 7.03, 9.62, 2.5}value or expression? ' let152 Assert.AreEqual({2.22, 2.5}expected value?, nums.filter(lessThan5)actual (computed) value?) not run153 Assert.AreEqual({10.94, 154.85, 721.73, 426.96, 551.37, 838.56, 463.68, 347.43, 890.28, 15.63}expected value?, nums.map(cube)actual (computed) value?) not run154 Assert.AreEqual(81480107expected value?, nums.reduce(1.0, product).floor()actual (computed) value?) not run155 Assert.AreEqual({0.45, 0.4}expected value?, nums.filter(lessThan5).map(inverse)actual (computed) value?) not run156 Assert.AreEqual("results: 0.45|0.4|"expected value?, nums.filter(lessThan5).map(inverse).map(toString).reduce("results: ", concat)actual (computed) value?) not run157  End Sub
End Class
+class Test_Map_Filter_Reduce {
@Test static void test_Map_Filter_Reducetest_name?() { 158
var numsname? = list(2.22, 5.37, 8.97, 7.53, 8.2, 9.43, 7.74, 7.03, 9.62, 2.5)value or expression?; // let159 assertEquals(list(2.22, 2.5)expected value?, nums.filter(lessThan5)actual (computed) value?); not run160 assertEquals(list(10.94, 154.85, 721.73, 426.96, 551.37, 838.56, 463.68, 347.43, 890.28, 15.63)expected value?, nums.map(cube)actual (computed) value?); not run161 assertEquals(81480107expected value?, nums.reduce(1.0, product).floor()actual (computed) value?); not run162 assertEquals(list(0.45, 0.4)expected value?, nums.filter(lessThan5).map(inverse)actual (computed) value?); not run163 assertEquals("results: 0.45|0.4|"expected value?, nums.filter(lessThan5).map(inverse).map(toString).reduce("results: ", concat)actual (computed) value?); not run164 }} // end test

Each of the three HoFs is applied to a ListlistListListList by a 'dot method call'. In this example each is applied to the same ListlistListListList (of floating point numbers), though the subject ListlistListListList may be of any type. Because they are all dot methods, they can easily be chained in dot sequences, as shown.

Each HoF is passed the name of a function that is defined elsewhere in the program. The function name is given as an argument, without brackets or arguments, e.g. lessThan5. For filter and map, this is the only argument; for reduce there is another argument. Taking each HoF in turn:

filter

map

reduce

filter and map methods may be chained, and in any order, provided that the item type of each resulting ListlistListListList is of the type expected by the next one.

Because reduce transforms a ListlistListListList into a single value, it is typically used in isolation or as the last of a chain. It can, of course, be followed in the chain by a simpler function (such as round).

Lambdas

In the examples above the function being passed into the HoF (using its name) is defined as a 'standalone' (global) function elsewhere in the program. This pattern has two advantages:>/p>

However, there are many circumstances where the function you need to pass into a HoF, is unlikely to be used in any different context, and where the function is so simple that it hardly warrants its own test – especially if the whole expression that uses the HoF is being tested.

A lambda is a function that is defined 'inline' as an argument in a function call. For example, the expression from the example above:

nums.filter(lessThan5)
nums.filter(lessThan5)
nums.filter(lessThan5)
nums.filter(lessThan5)
nums.filter(lessThan5)
could be written as:
nums.filter(lambda x as Int => x < 5)
nums.filter(lambda x: int: x < 5)
nums.filter(int x => x < 5)
nums.filter(Function (x As Integer) x < 5)
nums.filter((int x) -> x < 5)

thereby eliminating the need to define the lessThan5 function for one use only.

There is a whole branch of mathematics devoted to 'lambda calculus', which in turn provided the theoretical underpinning for the concept of function programming (FP). And lambdas have capabilities that are both more subtle and more sophisticated than can be described here, including the idea of 'closure' (which you can look up if you are ready to go much deeper – and which is supported in Elan). However, you should note that, because of the need for compatibility across all the supported languages, Elan has a restricted implementation of HoFs and of 'function passing' in general, but it is sufficient to grasp the key principles of functional programming. To go further you would need to switch to a pure FP language such as Haskell. Currently Elan does not permit (in any of the supported languages):

Using HoFs to iterate over a 2D List

If you are using Elan's Block Graphics, or have another need to process a 2D ListlistListListList (such as working with matrices or tabular data), and you want to do this in pure functions, you have the challenge of how to iterate over two dimensions – something that is straightforward in procedural programming by means of 'nested loops'. It is certainly possible to iterate over two dimensions of a ListlistListListList using HoFs within a single expression, but until you are fluent in such programming, the recommended approach is to break up the task into an 'inner' function that processes just a single column (or row, if preferred) of the 2D structure and an 'outer' function that processes each of the columns (or rows).

This is exemplified in the following functions, taken from the Life - functional demo, which together perform a role equivalent to the nextGeneration procedure in the Life - procedural demo:

+function nextGenerationname?(grid as List<of List<of Int>>parameter definitions?) returns List<of List<of Int>>Type? 165 let colsname? be range(0, 40)value or expression?166 return cols.map(lambda x as Int => nextCol(grid, x))value or expression?167 end function
+def nextGenerationname?(grid: list[list[int]]parameter definitions?) -> list[list[int]]Type?: # function168 colsname? = range(0, 40)value or expression? # let169 return cols.map(lambda x: int: nextCol(grid, x))value or expression?170 # end function
+static List<List<int>>Type? nextGenerationname?(List<List<int>> gridparameter definitions?) { // function171 var colsname? = range(0, 40)value or expression?; // let172 return cols.map(int x => nextCol(grid, x))value or expression?;173 } // end function
+Function nextGenerationname?(grid As List(Of List(Of Integer))parameter definitions?) As List(Of List(Of Integer))Type? 174 Dim colsname? = range(0, 40)value or expression? ' let175 Return cols.map(Function (x As Integer) nextCol(grid, x))value or expression?176 End Function
+static List<List<int>>Type? nextGenerationname?(List<List<int>> gridparameter definitions?) { // function177 var colsname? = range(0, 40)value or expression?; // let178 return cols.map((int x) -> nextCol(grid, x))value or expression?;179 } // end function
+function nextColname?(grid as List<of List<of Int>>, x as Intparameter definitions?) returns List<of Int>Type? 180 let colname? be grid[x]value or expression?181 let rowsname? be range(0, 30)value or expression?182 return rows.map(lambda y as Int => nextCellValue(grid, x, y))value or expression?183 end function
+def nextColname?(grid: list[list[int]], x: intparameter definitions?) -> list[int]Type?: # function184 colname? = grid[x]value or expression? # let185 rowsname? = range(0, 30)value or expression? # let186 return rows.map(lambda y: int: nextCellValue(grid, x, y))value or expression?187 # end function
+static List<int>Type? nextColname?(List<List<int>> grid, int xparameter definitions?) { // function188 var colname? = grid[x]value or expression?; // let189 var rowsname? = range(0, 30)value or expression?; // let190 return rows.map(int y => nextCellValue(grid, x, y))value or expression?;191 } // end function
+Function nextColname?(grid As List(Of List(Of Integer)), x As Integerparameter definitions?) As List(Of Integer)Type? 192 Dim colname? = grid(x)value or expression? ' let193 Dim rowsname? = range(0, 30)value or expression? ' let194 Return rows.map(Function (y As Integer) nextCellValue(grid, x, y))value or expression?195 End Function
+static List<int>Type? nextColname?(List<List<int>> grid, int xparameter definitions?) { // function196 var colname? = grid[x]value or expression?; // let197 var rowsname? = range(0, 30)value or expression?; // let198 return rows.map((int y) -> nextCellValue(grid, x, y))value or expression?;199 } // end function

Note the use of range in both functions to create colscolscolscolscols and rowsrowsrowsrowsrows respectively, which are just lists of numbers e.g. [0, 1, 2, .. , 39]. The map HoFs iterate over these column and row numbers using them to access the appropriate portions of the gridgridgridgridgrid, which is a 2D List<of List<of Int>>list[list[int]]List<List<int>>List(Of List(Of Integer))List<List<int>>.

User-defined classes should be immutable

It is possible, indeed highly desirable, to have user-defined types (i.e. classes) in FP. However, in true FP, an instance of a class would never be mutated. So, when running under the functional paradigm, you may define classes and add properties and function methods, but you may not define any procedure method. (In Object-oriented Programming you would use procedure methods to mutate an instance or do anything that cannot be done by a function.)

But this raises a question: how are we to make changes to an instance of a class?. The answer is similar to that for updating a ListlistListListList, String, or other standard data structure within a function (described in earlier): you need ,ake a copy of the instance with specified changes. This is best achieved with the aid of the library function: copyWith, which requires three arguments:

The copyWith may be used anywhere, but it is often convenient to use it within one or more 'with...' methods defined on the class. For example, in the Snake - functional demo, the class GameGameGameGameGame has several with.. methods, one of which is shown here:

+function with_headname?(head as Squareparameter definitions?) returns GameType? 200 return copyWith(this, "head", head)value or expression?201 end function
+def with_headname?(head: Squareparameter definitions?) -> GameType?: # function202 return copyWith(self, "head", head)value or expression?203 # end function
+static GameType? with_headname?(Square headparameter definitions?) { // function204 return copyWith(this, "head", head)value or expression?;205 } // end function
+Function with_headname?(head As Squareparameter definitions?) As GameType? 206 Return copyWith(Me, "head", head)value or expression?207 End Function
+static GameType? with_headname?(Square headparameter definitions?) { // function208 return copyWith(this, "head", head)value or expression?;209 } // end function

Note that:

Defining 'with...' methods on the class, rather than just using copyWith elsewhere to do the update, offers several advantages:

Note, in the same demo, that the class SquareSquareSquareSquareSquare does not define any with.. methods because there is no need within the program to update any instance of Square – instead, a new instance is created, specifying the xxxxx and yyyyy coordinates as needed.

Generating Random numbers within a function requires a special pattern

Warning: This is an advanced technique and we recommend not attempting to use it until you are fairly comfortable with all the previous Principles.

In procedural programming you use the 'system methods' random (for a floating point number in the range 0 to 1) or randInt for an integer in a specified range. You may use these system methods within the main routine or a procedure, and you may pass these random values into a function, but you may not generate one or more random values within a function. When writing games and simulations that rely heavily on generating random numbers this can make it very difficult to achieve the primary objective of doing all data transformations within functions. Fortunately, there is a mechanism to generate random numbers within functions. Before looking at how to use it, it is important to understand the following:

The 'functional Random' pattern, works within these constraints. If you want to generate one or more random numbers within a function, you must pass in the 'state' needed to derive that next random value, and you must pass the updated state to the next place where a new random is required, either inside or outside the function. This is done using an instance of the type RandomRandomRandomRandomRandom (note the capital R, which distinguishes this from the procedural methods) which may be thought of as an instance of a 'Random Number Generator'.

To see how RandomRandomRandomRandomRandom works, we will first explore it within a tiny program intended to show the result of rolling a dice 10 times:

+main 1 variable rngname? set to new Random()value or expression?2 +for iitem? in range(1, 10)source? 3 print(rollDice(rng)value or expression?)4 end for end main +function rollDicename?(rng as Randomparameter definitions?) returns IntType? 5 return rng.asInt(1, 6)value or expression?6 end function
+def main() -> None: 1 rngname? = Random()value or expression? # variable definition2 +for iitem? in range(1, 10)source?: 3 print(rollDice(rng)value or expression?)4 # end for # end main +def rollDicename?(rng: Randomparameter definitions?) -> intType?: # function5 return rng.asInt(1, 6)value or expression?6 # end function main()
+static void main() { 1 var rngname? = new Random()value or expression?;2 +foreach (var iitem? in range(1, 10)source?) { 3 Console.WriteLine(rollDice(rng)value or expression?); // print statement4 } // end foreach } // end main +static intType? rollDicename?(Random rngparameter definitions?) { // function5 return rng.asInt(1, 6)value or expression?;6 } // end function
+Sub main() 1 Dim rngname? = New Random()value or expression? ' variable definition2 +For Each iitem? In range(1, 10)source? 3 Console.WriteLine(rollDice(rng)value or expression?) ' print statement4 Next i End Sub +Function rollDicename?(rng As Randomparameter definitions?) As IntegerType? 5 Return rng.asInt(1, 6)value or expression?6 End Function
public class Global {

+static void main() { 1 var rngname? = new Random()value or expression?;2 +foreach (var iitem? in range(1, 10)source?) { 3 System.out.println(rollDice(rng)value or expression?); // print statement4 } // end foreach } // end main +static intType? rollDicename?(Random rngparameter definitions?) { // function5 return rng.asInt(1, 6)value or expression?;6 } // end function
} // end Global
If you run this program you will see that it generates the same value (namely 3) each time! We need to change it so that after each roll the variable rngrngrngrngrng is updated to the next value of the Random number generator. So changing just the main routine to the following will generate a more random looking sequence:
+main 7 variable rngname? set to new Random()value or expression?8 +for iitem? in range(1, 10)source? 9 print(rollDice(rng)value or expression?)10 assign rngvariableName? to rng.nextGen()value or expression?11 end for end main
+def main() -> None: 12 rngname? = Random()value or expression? # variable definition13 +for iitem? in range(1, 10)source?: 14 print(rollDice(rng)value or expression?)15 rngvariableName? = rng.nextGen()value or expression? # assignment16 # end for # end main
+static void main() { 17 var rngname? = new Random()value or expression?;18 +foreach (var iitem? in range(1, 10)source?) { 19 Console.WriteLine(rollDice(rng)value or expression?); // print statement20 rngvariableName? = rng.nextGen()value or expression?; // assignment21 } // end foreach } // end main
+Sub main() 22 Dim rngname? = New Random()value or expression? ' variable definition23 +For Each iitem? In range(1, 10)source? 24 Console.WriteLine(rollDice(rng)value or expression?) ' print statement25 rngvariableName? = rng.nextGen()value or expression? ' assignment26 Next i End Sub
+static void main() { 27 var rngname? = new Random()value or expression?;28 +foreach (var iitem? in range(1, 10)source?) { 29 System.out.println(rollDice(rng)value or expression?); // print statement30 rngvariableName? = rng.nextGen()value or expression?; // assignment31 } // end foreach } // end main

Now when you run the same program it produces random looking values, but always in the same sequence. This effect can be very useful for testing code, but it makes for very predictable games!

To overcome this predictability, initialise the random generator from the system clock (given that it changes 1,000 times per second) which will make it almost impossible to predict the sequence:

+main 32 variable rngname? set to new Random()value or expression?33 call rng.initialiseFromClockprocedureName?(arguments?)34 +for iitem? in range(1, 10)source? 35 print(rollDice(rng)value or expression?)36 assign rngvariableName? to rng.nextGen()value or expression?37 end for end main
+def main() -> None: 38 rngname? = Random()value or expression? # variable definition39 rng.initialiseFromClockprocedureName?(arguments?) # procedure call40 +for iitem? in range(1, 10)source?: 41 print(rollDice(rng)value or expression?)42 rngvariableName? = rng.nextGen()value or expression? # assignment43 # end for # end main
+static void main() { 44 var rngname? = new Random()value or expression?;45 rng.initialiseFromClockprocedureName?(arguments?); // procedure call46 +foreach (var iitem? in range(1, 10)source?) { 47 Console.WriteLine(rollDice(rng)value or expression?); // print statement48 rngvariableName? = rng.nextGen()value or expression?; // assignment49 } // end foreach } // end main
+Sub main() 50 Dim rngname? = New Random()value or expression? ' variable definition51 rng.initialiseFromClockprocedureName?(arguments?) ' procedure call52 +For Each iitem? In range(1, 10)source? 53 Console.WriteLine(rollDice(rng)value or expression?) ' print statement54 rngvariableName? = rng.nextGen()value or expression? ' assignment55 Next i End Sub
+static void main() { 56 var rngname? = new Random()value or expression?;57 rng.initialiseFromClockprocedureName?(arguments?); // procedure call58 +foreach (var iitem? in range(1, 10)source?) { 59 System.out.println(rollDice(rng)value or expression?); // print statement60 rngvariableName? = rng.nextGen()value or expression?; // assignment61 } // end foreach } // end main

This now works well. But if we are going to have to go back out to the main routine between each use of RandomRandomRandomRandomRandom, we might as well have used the procedural methods in the first place. The following example shows something that could not be done that way: creating a ListlistListListList of random dice-throws entirely within a function:

+main 1 variable rngname? set to new Random()value or expression?2 call rng.initialiseFromClockprocedureName?(arguments?)3 print(randomList(rng).item_0value or expression?)4 end main +function randomListname?(rng as Randomparameter definitions?) returns (List<of Int>, Random)Type? 5 let liname? be new List<of Int>()value or expression?6 let itemsname? be range(0, 10)value or expression?7 return items.reduce((li, rng), appendDiceThrow)value or expression?8 end function +function appendDiceThrowname?(tup as (List<of Int>, Random), row as Intparameter definitions?) returns (List<of Int>, Random)Type? 9 let colname? be tup.item_0value or expression?10 let rngname? be tup.item_1value or expression?11 return (col.withAppend(rng.asInt(1, 6)), rng.nextGen())value or expression?12 end function
+def main() -> None: 1 rngname? = Random()value or expression? # variable definition2 rng.initialiseFromClockprocedureName?(arguments?) # procedure call3 print(randomList(rng).item_0value or expression?)4 # end main +def randomListname?(rng: Randomparameter definitions?) -> tuple[list[int], Random]Type?: # function5 liname? = list[int]()value or expression? # let6 itemsname? = range(0, 10)value or expression? # let7 return items.reduce((li, rng), appendDiceThrow)value or expression?8 # end function +def appendDiceThrowname?(tup: tuple[list[int], Random], row: intparameter definitions?) -> tuple[list[int], Random]Type?: # function9 colname? = tup.item_0value or expression? # let10 rngname? = tup.item_1value or expression? # let11 return (col.withAppend(rng.asInt(1, 6)), rng.nextGen())value or expression?12 # end function main()
+static void main() { 1 var rngname? = new Random()value or expression?;2 rng.initialiseFromClockprocedureName?(arguments?); // procedure call3 Console.WriteLine(randomList(rng).item_0value or expression?); // print statement4 } // end main +static (List<int>, Random)Type? randomListname?(Random rngparameter definitions?) { // function5 var liname? = new List<int>()value or expression?; // let6 var itemsname? = range(0, 10)value or expression?; // let7 return items.reduce((li, rng), appendDiceThrow)value or expression?;8 } // end function +static (List<int>, Random)Type? appendDiceThrowname?((List<int>, Random) tup, int rowparameter definitions?) { // function9 var colname? = tup.item_0value or expression?; // let10 var rngname? = tup.item_1value or expression?; // let11 return (col.withAppend(rng.asInt(1, 6)), rng.nextGen())value or expression?;12 } // end function
+Sub main() 1 Dim rngname? = New Random()value or expression? ' variable definition2 rng.initialiseFromClockprocedureName?(arguments?) ' procedure call3 Console.WriteLine(randomList(rng).item_0value or expression?) ' print statement4 End Sub +Function randomListname?(rng As Randomparameter definitions?) As (List(Of Integer), Random)Type? 5 Dim liname? = New List(Of Integer)()value or expression? ' let6 Dim itemsname? = range(0, 10)value or expression? ' let7 Return items.reduce((li, rng), appendDiceThrow)value or expression?8 End Function +Function appendDiceThrowname?(tup As (List(Of Integer), Random), row As Integerparameter definitions?) As (List(Of Integer), Random)Type? 9 Dim colname? = tup.item_0value or expression? ' let10 Dim rngname? = tup.item_1value or expression? ' let11 Return (col.withAppend(rng.asInt(1, 6)), rng.nextGen())value or expression?12 End Function
public class Global {

+static void main() { 1 var rngname? = new Random()value or expression?;2 rng.initialiseFromClockprocedureName?(arguments?); // procedure call3 System.out.println(randomList(rng).item_0value or expression?); // print statement4 } // end main +static (List<int>, Random)Type? randomListname?(Random rngparameter definitions?) { // function5 var liname? = new List<int>()value or expression?; // let6 var itemsname? = range(0, 10)value or expression?; // let7 return items.reduce((li, rng), appendDiceThrow)value or expression?;8 } // end function +static (List<int>, Random)Type? appendDiceThrowname?((List<int>, Random) tup, int rowparameter definitions?) { // function9 var colname? = tup.item_0value or expression?; // let10 var rngname? = tup.item_1value or expression?; // let11 return (col.withAppend(rng.asInt(1, 6)), rng.nextGen())value or expression?;12 } // end function
} // end Global

In Life - functional you can find this pattern employed to create a randomised initial grid of random black or white blocks.

Global Instructions

Same as for object-oriented programming

Coding with Elan using the functional paradigm offers the same global instructions as offered in the object-oriented paradigm.

Statement instructions

Within the main routine or any procedure coding with Elan using the functional paradigm offers the same statement instructions as offered in the object-oriented paradigm.

However, within a function coding with Elan using the functional paradigm allows only the return statement - which forms part of the function template, plus the option to precede this with one or more ...

Let Statement

Examples of a let statement

From the Life - functional demo:

+function northname?(cell as (Int, Int)parameter definitions?) returns (Int, Int)Type? 13 let xname? be cell.item_0value or expression?14 let yname? be cell.item_1value or expression?15 let y2name? be if_(y is 0, 29, y - 1)value or expression?16 return (x, y2)value or expression?17 end function
+def northname?(cell: tuple[int, int]parameter definitions?) -> tuple[int, int]Type?: # function18 xname? = cell.item_0value or expression? # let19 yname? = cell.item_1value or expression? # let20 y2name? = if_(y == 0, 29, y - 1)value or expression? # let21 return (x, y2)value or expression?22 # end function
+static (int, int)Type? northname?((int, int) cellparameter definitions?) { // function23 var xname? = cell.item_0value or expression?; // let24 var yname? = cell.item_1value or expression?; // let25 var y2name? = if_(y == 0, 29, y - 1)value or expression?; // let26 return (x, y2)value or expression?;27 } // end function
+Function northname?(cell As (Integer, Integer)parameter definitions?) As (Integer, Integer)Type? 28 Dim xname? = cell.item_0value or expression? ' let29 Dim yname? = cell.item_1value or expression? ' let30 Dim y2name? = if_(y = 0, 29, y - 1)value or expression? ' let31 Return (x, y2)value or expression?32 End Function
+static (int, int)Type? northname?((int, int) cellparameter definitions?) { // function33 var xname? = cell.item_0value or expression?; // let34 var yname? = cell.item_1value or expression?; // let35 var y2name? = if_(y == 0, 29, y - 1)value or expression?; // let36 return (x, y2)value or expression?;37 } // end function

The first two let statements are used to extract the two values from the 2-tuple cellcellcellcellcell into named values xxxxx and yyyyy. The third let statement, calculates the y coordinate for the cell immediately to the North, using a conditional expression to 'wrap' around to the bottom of the 40x30 grid if necessary.

From the Merge Sort demo:

+function sortedFrontHalfname?(li as List<of String>parameter definitions?) returns List<of String>Type? 38 let midname? be divAsInt(li.length(), 2)value or expression?39 let frontHalfname? be li.subList(0, mid)value or expression?40 return sort(frontHalf)value or expression?41 end function
+def sortedFrontHalfname?(li: list[str]parameter definitions?) -> list[str]Type?: # function42 midname? = divAsInt(li.length(), 2)value or expression? # let43 frontHalfname? = li.subList(0, mid)value or expression? # let44 return sort(frontHalf)value or expression?45 # end function
+static List<string>Type? sortedFrontHalfname?(List<string> liparameter definitions?) { // function46 var midname? = divAsInt(li.length(), 2)value or expression?; // let47 var frontHalfname? = li.subList(0, mid)value or expression?; // let48 return sort(frontHalf)value or expression?;49 } // end function
+Function sortedFrontHalfname?(li As List(Of String)parameter definitions?) As List(Of String)Type? 50 Dim midname? = divAsInt(li.length(), 2)value or expression? ' let51 Dim frontHalfname? = li.subList(0, mid)value or expression? ' let52 Return sort(frontHalf)value or expression?53 End Function
+static List<String>Type? sortedFrontHalfname?(List<String> liparameter definitions?) { // function54 var midname? = divAsInt(li.length(), 2)value or expression?; // let55 var frontHalfname? = li.subList(0, mid)value or expression?; // let56 return sort(frontHalf)value or expression?;57 } // end function

This function could have been written without using any let statements, as:

+function sortedFrontHalfname?(li as List<of String>parameter definitions?) returns List<of String>Type? 58 return sort(li.subList(0, divAsInt(li.length(), 2)))value or expression?59 end function
+def sortedFrontHalfname?(li: list[str]parameter definitions?) -> list[str]Type?: # function60 return sort(li.subList(0, divAsInt(li.length(), 2)))value or expression?61 # end function
+static List<string>Type? sortedFrontHalfname?(List<string> liparameter definitions?) { // function62 return sort(li.subList(0, divAsInt(li.length(), 2)))value or expression?;63 } // end function
+Function sortedFrontHalfname?(li As List(Of String)parameter definitions?) As List(Of String)Type? 64 Return sort(li.subList(0, divAsInt(li.length(), 2)))value or expression?65 End Function
+static List<String>Type? sortedFrontHalfname?(List<String> liparameter definitions?) { // function66 return sort(li.subList(0, divAsInt(li.length(), 2)))value or expression?;67 } // end function
but the use of let statements makes the intention much easier to read.

What you need to know about a let statement

  • A let statement is like a variable definition, except that the named value created may not then be reassigned to another value.
  • A let statement is therefore somewhat like a constant, except that:
    • It is defined within a function, not globally
    • The value assigned to the name may be defined by an expression that incorporates function parameters and/or the named values from any (previous) let statements in the function.
    • The value may be a data structure such as a ListlistListListList
  • The motivations for using let statements is to:
    • To eliminate duplication, where the same sub-expression would appear more than once within the final expression to be evaluated and returned by the function.
    • To improve readabilty. In theory it is possible to implement any function using just a returnreturnreturnreturnreturn statement followed by a single expression, but the expressions can quickly become very hard to read, so it is better out sub-expressions into let statement - or, if the sub-expressions might be useful in in other contexts, as separate functions.
    • Elan does have some limits on the complexity of expressions, and breaking out sub-expressions into lets is a good way to avoid encountering those limits.

Member instructions

When using the functional paradigm, in the context of a class you will be offered the same member instructions as when using the object-oriented paradigm, except that the procedure method is not available for use - because procedure methods cause side effects. All input/output or other system interaction within a functional program must be handled directly within the main routine, or a global procedure.

Elan Language Reference go to the top