Introduction to Functional Programming with Elan
Getting started
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 profile from the menu at the top of the IDE.
This changes two things:
- 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.
- 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
return
return
return
return
return instruction, is a new instruction named let (plus 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 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 be drawing out the principles, patterns, practices of FP with reference to the various FP demo programs
Principle 1: Functions must be pure
A 'pure' function observes the following rules:
- The value (which may be a data structure) returned by the function must derived solely, and deterministically, from the data provided as parameters.
This means it may not depend on, for example, include instructions that obtain input directly from the user, from a file, or over the network
- though such data may be obtained outside the function and passed in as parameters.
Similarly, within the function you cannot make direct use of the system clock or system-generated random numbers. (There is a technique
to generate random numbers within a function, but this is best left until you have a thorough understanding of the basics of FP.)
The function may not generate any 'side effects' - meaning changes to the state of the system that may be observed outside the function.
When a function has completed its execution the only legitimate observable effect is the returned value.
Dedicated functional programmming languages - such as Haskell - enforce these rules automatically. Common 'multi-paradigm' languages
- including Python, VB, C#, and Java - do not enforce this rule. However, if any of those languages are used within Elan, then the
Elan editor enforces those rules automatically. This means that every function you see within Elan, and every function you have ever written within Elan
(in Python, VB, C#, Java), is a pure function. That is the reason why, even when you are using the Procedural or Object Oriented profile
and you are within a function you cannot add a print or call procedure, nor make use of system methods such as input,
random, or clock. This experience gives you a huge advantage in transitioning to FP.
Principle 2: 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 necessarily has dependencies on the system, and generates side effects such as generating output to a display.
And 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 the second principle of FP is that the main, and any procedures, should
be kept as small as possible. Any code within the main routine or procedures that performs calculation, evaluates logic, or
transforms data in any way, should be delegated to functions. Most procedural programs contain some functions,
but a program written within the discipline of FP should consist almost entirely of functions.
This may seem logical when a program is obviously heavily dependent upon calculation, logic, or data transformation
- as, for example, the best-fit and Wordle solver demo programs. 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 quick 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% |
Principle 3: 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' -
designed to test functions. They are extremely easy to write, and run automatically when the program is loaded from a file, and 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 to your code - either as a result of de-bugging, or to implement new requirements - do not break existing
functionality, resulting in bugs that might not surface to the user for a long time.
On some other 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, even such changes as a routine upgrade to the browser or operating system.
Elan does currently support those kind of tests. (Note: outside Elan Python, VB, C#, and Java, unit testing frameworks
will, 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 only safe when applied to
safe (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 than 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% |
Notes:
- The Best Fit demo defines a function and a class for use in other programs - it has no main instruction,
so comparison with the other programs in the table above would be misleading. The function, and the operation
of the class are both 100% tested.
Principle 4: Internally, functions should return an expression, optionally preceded by 'let' instructions
In the FP demos you will see that a significant number of functions simply return
return
return
return
return an expression to be evaluated, for example:
+function gameOvername?(g as Gameparameter definitions?) returns BooleanType?
1
return g.body.contains(g.head) or hasHitEdge(g)value or expression?2
end function
+def gameOvername?(g: Gameparameter definitions?) -> boolType?:
# function1
return g.body.contains(g.head) or hasHitEdge(g)value or expression?2
main()
+static boolType? gameOvername?(Game gparameter definitions?) {
// function1
return g.body.contains(g.head) || hasHitEdge(g)value or expression?;2
}
+Function gameOvername?(g As Gameparameter definitions?) As BooleanType?
1
Return g.body.contains(g.head) Or hasHitEdge(g)value or expression?2
End Function
+static boolType? gameOvername?(Game gparameter definitions?) {
// function1
return g.body.contains(g.head) || hasHitEdge(g)value or expression?;2
}
Other functions precede the return
return
return
return
return by one or more let statement, for example
+function hasHitEdgename?(g as Gameparameter definitions?) returns BooleanType?
1
let xname? be g.head.xvalue or expression?2
let yname? be g.head.yvalue or expression?3
return (x is -1) or (y is -1) or (x is 40) or (y is 30)value or expression?4
end function
+def hasHitEdgename?(g: Gameparameter definitions?) -> boolType?:
# function1
xname? = g.head.xvalue or expression? # let2
yname? = g.head.yvalue or expression? # let3
return (x == -1) or (y == -1) or (x == 40) or (y == 30)value or expression?4
main()
+static boolType? hasHitEdgename?(Game gparameter definitions?) {
// function1
var xname? = g.head.xvalue or expression?; // let2
var yname? = g.head.yvalue or expression?; // let3
return (x == -1) || (y == -1) || (x == 40) || (y == 30)value or expression?;4
}
+Function hasHitEdgename?(g As Gameparameter definitions?) As BooleanType?
1
Dim xname? = g.head.xvalue or expression? ' let2
Dim yname? = g.head.yvalue or expression? ' let3
Return (x = -1) Or (y = -1) Or (x = 40) Or (y = 30)value or expression?4
End Function
+static boolType? hasHitEdgename?(Game gparameter definitions?) {
// function1
var xname? = g.head.xvalue or expression?; // let2
var yname? = g.head.yvalue or expression?; // let3
return (x == -1) || (y == -1) || (x == 40) || (y == 30)value or expression?;4
}
A let statement works like a variable definition except that the named value created may not be re-assigned. But it differs from
a constant in that:
- it is defined within a function (or a test), whereas a constant is always defined at global (file) level.
- it may be defined by an expression rather than just by a literal value
- it may refer to a list, or other data structure
A let statement defines a 'sub-expression' that will be used in another expression, for one (or more) of these reasons:
- To make a long expression easier to read
- To eliminate duplication - where the same sub-expression otherwise appears in full in more than one place.
- Within a test, because the 'actual' field does not support 'chained' expressions (expressions containing more than one 'dot')
When running in the Functional profile, let statement is the only type of instruction that you may add within a function,
and the only type in addition to an assert that may be added into a test. (You may still add comments.)
Principle 5: Selection is implemented using 'if expressions'
When running in the Functional profile, you cannot add an if instruction into a function.
So how do you implement 'selection' and 'iteration'?
Iteration will be addressed in the next heading, but 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 that it does in an Excel spreadsheets
(though 'IF', like all Excel functions, is capitalised, therein). 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?
1
let isBettername? be wc2.count < wc1.countvalue or expression?2
let isEqualAndPossAnswername? be (wc2.count is wc1.count) and possAnswers.contains(wc2.word)value or expression?3
return if(isBetter or isEqualAndPossAnswer, wc2, wc1)value or expression?4
end function
+def betterOfname?(wc1: WordCount, wc2: WordCount, possAnswers: list[str]parameter definitions?) -> WordCountType?:
# function1
isBettername? = wc2.count < wc1.countvalue or expression? # let2
isEqualAndPossAnswername? = (wc2.count == wc1.count) and possAnswers.contains(wc2.word)value or expression? # let3
return if(isBetter or isEqualAndPossAnswer, wc2, wc1)value or expression?4
main()
+static WordCountType? betterOfname?(WordCount wc1, WordCount wc2, List<string> possAnswersparameter definitions?) {
// function1
var isBettername? = wc2.count < wc1.countvalue or expression?; // let2
var isEqualAndPossAnswername? = (wc2.count == wc1.count) && possAnswers.contains(wc2.word)value or expression?; // let3
return if(isBetter || isEqualAndPossAnswer, wc2, wc1)value or expression?;4
}
+Function betterOfname?(wc1 As WordCount, wc2 As WordCount, possAnswers As List(Of String)parameter definitions?) As WordCountType?
1
Dim isBettername? = wc2.count < wc1.countvalue or expression? ' let2
Dim isEqualAndPossAnswername? = (wc2.count = wc1.count) And possAnswers.contains(wc2.word)value or expression? ' let3
Return if(isBetter Or isEqualAndPossAnswer, wc2, wc1)value or expression?4
End Function
+static WordCountType? betterOfname?(WordCount wc1, WordCount wc2, List<String> possAnswersparameter definitions?) {
// function1
var isBettername? = wc2.count < wc1.countvalue or expression?; // let2
var isEqualAndPossAnswername? = (wc2.count == wc1.count) && possAnswers.contains(wc2.word)value or expression?; // let3
return if(isBetter || isEqualAndPossAnswer, wc2, wc1)value or expression?;4
}
+function nextCellValuename?(grid as List<of List<of Int>>, x as Int, y as Intparameter definitions?) returns IntType?
1
let livename? be willLive(grid[x][y], liveNeighbours(grid, x, y))value or expression?2
return if(live, black, white)value or expression?3
end function
+def nextCellValuename?(grid: list[list[int]], x: int, y: intparameter definitions?) -> intType?:
# function1
livename? = willLive(grid[x][y], liveNeighbours(grid, x, y))value or expression? # let2
return if(live, black, white)value or expression?3
main()
+static intType? nextCellValuename?(List<List<int>> grid, int x, int yparameter definitions?) {
// function1
var livename? = willLive(grid[x][y], liveNeighbours(grid, x, y))value or expression?; // let2
return if(live, black, white)value or expression?;3
}
+Function nextCellValuename?(grid As List(Of List(Of Integer)), x As Integer, y As Integerparameter definitions?) As IntegerType?
1
Dim livename? = willLive(grid[x][y], liveNeighbours(grid, x, y))value or expression? ' let2
Return if(live, black, white)value or expression?3
End Function
+static intType? nextCellValuename?(List<List<int>> grid, int x, int yparameter definitions?) {
// function1
var livename? = willLive(grid[x][y], liveNeighbours(grid, x, y))value or expression?; // let2
return if(live, black, white)value or expression?;3
}
The if function requires three arguments to be provided, in the following order:
- The condition. This condition may compare two values, or may call another function that returns a Boolean value
- The value to be returned by the if function when the condition evaluates to true
- The value to be returned by the if function when the condition evaluates to false
The if function, then, performs a role somewhat similar to an if instruction,
but differs from it in these important respects:
- The if function is not an instruction - forms part, or all, of an expression and may be used
within any expression that forms part of an instruction.
- It returns a value, which must be used.
- It may make use of other expressions to define the arguments, but may not contain other instructions.
if functions can be nested. However, nesting more than one level can become very hard to read.
In this circumstance it is often better to delegate to new function that perform part of the logic.
Principle 6: Iteration may be achieved using recursion
There are two ways to achieve implement iteration within a function. The first is using 'recursion' - also known as 'recursive' functions).
(The second approach is addressed in the next heading.)
A recursive function is best understood by looking at a classic example
- the calculation of a factorial:
+function factorialname?(n as Intparameter definitions?) returns IntType?
1
return if(n < 2, 1, n*factorial(n - 1))value or expression?2
end function
+def factorialname?(n: intparameter definitions?) -> intType?:
# function1
return if(n < 2, 1, n*factorial(n - 1))value or expression?2
main()
+static intType? factorialname?(int nparameter definitions?) {
// function1
return if(n < 2, 1, n*factorial(n - 1))value or expression?;2
}
+Function factorialname?(n As Integerparameter definitions?) As IntegerType?
1
Return if(n < 2, 1, n*factorial(n - 1))value or expression?2
End Function
+static intType? factorialname?(int nparameter definitions?) {
// function1
return if(n < 2, 1, n*factorial(n - 1))value or expression?;2
}
This simple example illustrates the three key principles of recursive functions:
- A recursive function calls itself in at least one place within the function.
In this example, the factorial function is called directly
within from within the function.
A different example, however, might call another function that in turn calls the original one. Either way there is always an implicit 'looping'.
- Typically, the function calls itself to solve a smaller version of the same problem - in this example
factorial(n)
factorial(n)
factorial(n)
factorial(n)
factorial(n) calls factorial(n - 1)
factorial(n - 1)
factorial(n - 1)
factorial(n - 1)
factorial(n - 1).
If it called itself with the same argument(s), this would cause an infinite loop and would eventually result in a 'Stack overflow' - which crashes the program.
- This recursion must 'bottom out' and there must be specific code to detect this, and then not make another recursive call. In the above example
the execution bottoms out when
n < 2
n < 2
n < 2
n < 2
n < 2, return 1 at that point. The most common mistake when designing a recursive function is to forget
to implement a condition and write, say:
+function factorialname?(n as Intparameter definitions?) returns IntType?
1
return n*factorial(n - 1)value or expression?2
end function
+def factorialname?(n: intparameter definitions?) -> intType?:
# function1
return n*factorial(n - 1)value or expression?2
main()
+static intType? factorialname?(int nparameter definitions?) {
// function1
return n*factorial(n - 1)value or expression?;2
}
+Function factorialname?(n As Integerparameter definitions?) As IntegerType?
1
Return n*factorial(n - 1)value or expression?2
End Function
+static intType? factorialname?(int nparameter definitions?) {
// function1
return n*factorial(n - 1)value or expression?;2
}
whereby the function would never exit naturally, and would eventually fail as a stack overflow error.
Principle 7: Lists and other data structures are never mutated
In procedural programming a list may be mutated either by re-assigning an indexed value, for example:
set mySubjects[3]variableName? to "Geography"value or expression?0
mySubjects[3]variableName? = "Geography"value or expression? # re-assign variable0
mySubjects[3]variableName? = "Geography"value or expression?; // re-assign variable0
mySubjects[3]variableName? = "Geography"value or expression? ' re-assign variable0
mySubjects[3]variableName? = "Geography"value or expression?; // re-assign variable0
or by calling one of the available procedure methods on on it such as:
call mySubjects.appendprocedureName?("Music"arguments?)0
mySubjects.appendprocedureName?("Music"arguments?) # call procedure0
mySubjects.appendprocedureName?("Music"arguments?); // call procedure0
mySubjects.appendprocedureName?("Music"arguments?) ' call procedure0
mySubjects.appendprocedureName?("Music"arguments?); // call procedure0
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 the data structures, but with specified differences from the original. For example, you may write:
let myNewSubjects be mySubjects.withSet(3, "Geography")
Code does not parse as Elan.
let myNewSubjects be mySubjects.withSet(3, "Geography")
Code does not parse as Elan.
let myNewSubjects be mySubjects.withSet(3, "Geography")
Code does not parse as Elan.
let myNewSubjects be mySubjects.withSet(3, "Geography")
Code does not parse as Elan.
let myNewSubjects be mySubjects.withSet(3, "Geography")
Code does not parse as Elan.
or
let myNewSubjects be mySubjects.withAppend("Music")
Code does not parse as Elan.
let myNewSubjects be mySubjects.withAppend("Music")
Code does not parse as Elan.
let myNewSubjects be mySubjects.withAppend("Music")
Code does not parse as Elan.
let myNewSubjects be mySubjects.withAppend("Music")
Code does not parse as Elan.
let myNewSubjects be mySubjects.withAppend("Music")
Code does not parse as Elan.
At first sight this 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 String
str
string
String
String.
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 myNewString be myString. "Geography")
Code does not parse as Elan.
let myNewString be myString. "Geography")
Code does not parse as Elan.
let myNewString be myString. "Geography")
Code does not parse as Elan.
let myNewString be myString. "Geography")
Code does not parse as Elan.
let myNewString be myString. "Geography")
Code does not parse as Elan.
Merge Sort demo,
which, as well as demonstrating recursion, is a good example of functional decomposition: the principal function, sort
delegates work, directly or indirectly to four other functions -
sortedFrontHalf, sortedBackHalf, merge, and mergeNonEmpty
each of which has its own associated unit tests. This makes the code easier to read, and possible to test progressively.
sort and merge are both recursive. This might not be immediately apparent, because they don't
make use of themselves directly. But sort delegates work to sortedFrontHalf
and sortedBackHalf, both of which in turn make use of the sort:
+function sortname?(li as List<of String>parameter definitions?) returns List<of String>Type?
1
return if(li.length() < 2, li, merge(sortedFrontHalf(li), sortedBackHalf(li)))value or expression?2
end function
+function sortedFrontHalfname?(li as List<of String>parameter definitions?) returns List<of String>Type?
3
let midname? be divAsInt(li.length(), 2)value or expression?4
let frontHalfname? be li.subList(0, mid)value or expression?5
return sort(frontHalf)value or expression?6
end function
+function sortedBackHalfname?(li as List<of String>parameter definitions?) returns List<of String>Type?
7
let midname? be divAsInt(li.length(), 2)value or expression?8
let backHalfname? be li.subList(mid, li.length())value or expression?9
return sort(backHalf)value or expression?10
end function
+def sortname?(li: list[str]parameter definitions?) -> list[str]Type?:
# function1
return if(li.length() < 2, li, merge(sortedFrontHalf(li), sortedBackHalf(li)))value or expression?2
+def sortedFrontHalfname?(li: list[str]parameter definitions?) -> list[str]Type?:
# function3
midname? = divAsInt(li.length(), 2)value or expression? # let4
frontHalfname? = li.subList(0, mid)value or expression? # let5
return sort(frontHalf)value or expression?6
+def sortedBackHalfname?(li: list[str]parameter definitions?) -> list[str]Type?:
# function7
midname? = divAsInt(li.length(), 2)value or expression? # let8
backHalfname? = li.subList(mid, li.length())value or expression? # let9
return sort(backHalf)value or expression?10
main()
+static List<string>Type? sortname?(List<string> liparameter definitions?) {
// function1
return if(li.length() < 2, li, merge(sortedFrontHalf(li), sortedBackHalf(li)))value or expression?;2
}
+static List<string>Type? sortedFrontHalfname?(List<string> liparameter definitions?) {
// function3
var midname? = divAsInt(li.length(), 2)value or expression?; // let4
var frontHalfname? = li.subList(0, mid)value or expression?; // let5
return sort(frontHalf)value or expression?;6
}
+static List<string>Type? sortedBackHalfname?(List<string> liparameter definitions?) {
// function7
var midname? = divAsInt(li.length(), 2)value or expression?; // let8
var backHalfname? = li.subList(mid, li.length())value or expression?; // let9
return sort(backHalf)value or expression?;10
}
+Function sortname?(li As List(Of String)parameter definitions?) As List(Of String)Type?
1
Return if(li.length() < 2, li, merge(sortedFrontHalf(li), sortedBackHalf(li)))value or expression?2
End Function
+Function sortedFrontHalfname?(li As List(Of String)parameter definitions?) As List(Of String)Type?
3
Dim midname? = divAsInt(li.length(), 2)value or expression? ' let4
Dim frontHalfname? = li.subList(0, mid)value or expression? ' let5
Return sort(frontHalf)value or expression?6
End Function
+Function sortedBackHalfname?(li As List(Of String)parameter definitions?) As List(Of String)Type?
7
Dim midname? = divAsInt(li.length(), 2)value or expression? ' let8
Dim backHalfname? = li.subList(mid, li.length())value or expression? ' let9
Return sort(backHalf)value or expression?10
End Function
+static List<String>Type? sortname?(List<String> liparameter definitions?) {
// function1
return if(li.length() < 2, li, merge(sortedFrontHalf(li), sortedBackHalf(li)))value or expression?;2
}
+static List<String>Type? sortedFrontHalfname?(List<String> liparameter definitions?) {
// function3
var midname? = divAsInt(li.length(), 2)value or expression?; // let4
var frontHalfname? = li.subList(0, mid)value or expression?; // let5
return sort(frontHalf)value or expression?;6
}
+static List<String>Type? sortedBackHalfname?(List<String> liparameter definitions?) {
// function7
var midname? = divAsInt(li.length(), 2)value or expression?; // let8
var backHalfname? = li.subList(mid, li.length())value or expression?; // let9
return sort(backHalf)value or expression?;10
}
Similarly merge delegates to mergeNonEmpty, which in turn makes use of merge:
+function mergename?(a as List<of String>, b as List<of String>parameter definitions?) returns List<of String>Type?
1
let oneIsEmptyname? be (a.length() is 0) or (b.length() is 0)value or expression?2
return if(oneIsEmpty, a.withAppendList(b), mergeNonEmpty(a, b))value or expression?3
end function
+function mergeNonEmptyname?(a as List<of String>, b as List<of String>parameter definitions?) returns List<of String>Type?
4
let aHeadname? be a.head()value or expression?5
let bHeadname? be b.head()value or expression?6
let aTailname? be a.tail()value or expression?7
let bTailname? be b.tail()value or expression?8
return if(aHead.isBefore(bHead), [aHead].withAppendList(merge(aTail, b)), [bHead].withAppendList(merge(a, bTail)))value or expression?9
end function
+def mergename?(a: list[str], b: list[str]parameter definitions?) -> list[str]Type?:
# function1
oneIsEmptyname? = (a.length() == 0) or (b.length() == 0)value or expression? # let2
return if(oneIsEmpty, a.withAppendList(b), mergeNonEmpty(a, b))value or expression?3
+def mergeNonEmptyname?(a: list[str], b: list[str]parameter definitions?) -> list[str]Type?:
# function4
aHeadname? = a.head()value or expression? # let5
bHeadname? = b.head()value or expression? # let6
aTailname? = a.tail()value or expression? # let7
bTailname? = b.tail()value or expression? # let8
return if(aHead.isBefore(bHead), [aHead].withAppendList(merge(aTail, b)), [bHead].withAppendList(merge(a, bTail)))value or expression?9
main()
+static List<string>Type? mergename?(List<string> a, List<string> bparameter definitions?) {
// function1
var oneIsEmptyname? = (a.length() == 0) || (b.length() == 0)value or expression?; // let2
return if(oneIsEmpty, a.withAppendList(b), mergeNonEmpty(a, b))value or expression?;3
}
+static List<string>Type? mergeNonEmptyname?(List<string> a, List<string> bparameter definitions?) {
// function4
var aHeadname? = a.head()value or expression?; // let5
var bHeadname? = b.head()value or expression?; // let6
var aTailname? = a.tail()value or expression?; // let7
var bTailname? = b.tail()value or expression?; // let8
return if(aHead.isBefore(bHead), [aHead].withAppendList(merge(aTail, b)), [bHead].withAppendList(merge(a, bTail)))value or expression?;9
}
+Function mergename?(a As List(Of String), b As List(Of String)parameter definitions?) As List(Of String)Type?
1
Dim oneIsEmptyname? = (a.length() = 0) Or (b.length() = 0)value or expression? ' let2
Return if(oneIsEmpty, a.withAppendList(b), mergeNonEmpty(a, b))value or expression?3
End Function
+Function mergeNonEmptyname?(a As List(Of String), b As List(Of String)parameter definitions?) As List(Of String)Type?
4
Dim aHeadname? = a.head()value or expression? ' let5
Dim bHeadname? = b.head()value or expression? ' let6
Dim aTailname? = a.tail()value or expression? ' let7
Dim bTailname? = b.tail()value or expression? ' let8
Return if(aHead.isBefore(bHead), {aHead}.withAppendList(merge(aTail, b)), {bHead}.withAppendList(merge(a, bTail)))value or expression?9
End Function
+static List<String>Type? mergename?(List<String> a, List<String> bparameter definitions?) {
// function1
var oneIsEmptyname? = (a.length() == 0) || (b.length() == 0)value or expression?; // let2
return if(oneIsEmpty, a.withAppendList(b), mergeNonEmpty(a, b))value or expression?;3
}
+static List<String>Type? mergeNonEmptyname?(List<String> a, List<String> bparameter definitions?) {
// function4
var aHeadname? = a.head()value or expression?; // let5
var bHeadname? = b.head()value or expression?; // let6
var aTailname? = a.tail()value or expression?; // let7
var bTailname? = b.tail()value or expression?; // let8
return if(aHead.isBefore(bHead), [aHead].withAppendList(merge(aTail, b)), [bHead].withAppendList(merge(a, bTail)))value or expression?;9
}
These examples demonstrate two key principles of
Principle 7: A function may be passed into, or returned by, another function 3>
# Higher order functions
- Use of Hofs
- passing in a function name
- defining a lambda
Principle 8: Data structures should never be mutated
- standard data structures
- `with` methods return a copy with specified differences
- with methods may be chained. (Maybe add graphics into best-fit demo?)
- simple examples
- ser-defined types
- Classes can only have function methods, no procedures
- 'updates' (like strings, or lists) are done by returning a copy with the changes
- 'with' instructions are just a special case of a function method, with own template, and code and a 'set' which you should not delete.
Patterns and Practices
Generating Random numbers within functions
Manipulating 2D lists