Functional Programming techniques
- A recent trend in programming has been to incorporate aspects of functional programming into OO and procedural programming languages.
- Specifically, we are going to look at how we can benefit from allowing programmers to pass code as if it were a parameter (i.e., pass a function as a parameter)
-
Imagine you have a list of Student objects, and you want to give each student a $100 credit. The traditional way of writing this code would look something like this:
for (Student s: studentList) { s.applyCredit(100) }
- The functional-style approach would be to make a
forEachmethod that takes the code to run as a parameter:studentList.forEach( (s) -> s.addCredit(100.0)); - This by itself is not a big deal. But, consider applying this approach to “filtering” an array:
- Show
filterByCredits - Show
filterand how it can be used to filter by any arbitrary rule. - Show
genericFilterand how we now need only write the code once: AnyArrayListcan use the code. - If this method was an
ArrayListinstance method, we could chain them together. (This is how many other programming languages work.)someList.filter( (i) -> i.condition1()).filter((i) -> i.condition2()).filter((i) -> i.condition3())- Although Java doesn’t, JavaScript, Ruby, and many other languages place some sort of “filter” method directly into the array class.
- Java uses a related concept called “Streams”. (More on that later.)
Map
- Another common “functional” method is
map. map“converts” the objects in an collection from one type to another.- Suppose we have collection of students and we want a collection of their names only.
- In most languages, this would looks something like
myList.map((i) -> i.getName()); - Let’s look at some JavaScript examples:
[1, 2, 3, 4, 5, 6, 7].map((i) => i*i)[{name: "George", age: 15}, {name: "Fred", age: 21}, {name: "Dave", age: 19}].map((i) => i.name);- (I’m using JavaScript, because
mapandfiltertend to be very helpful when doing web programming. You are more likely to use these methods in JavaScript that in Java. Also, Java does something a bit more complicated. I wanted to start with simpler examples.)
mapandfiltercan be chained:[1, 2, 3, 4, 5, 6, 7].map((i) => i*i).filter((i) => i % 2 == 0)[{name: "George", age: 15}, {name: "Fred", age: 21}, {name: "Dave", age: 19}].filter((i) => i.age >= 18).map((i) => i.name);
- Show
mapimplementation.
Inject / Reduce
- JavaScript’s
reducefunction (calledinjectin Ruby) can be very powerful. - However, I find it a bit confusing.
- I recommend taking note of it, but only using it when “traditional” techniques don’t work.
- https://thecodebarbarian.com/javascript-reduce-in-5-examples.html
Streams
- Unlike most programming languages, Java doesn’t put
filter,map, andreduceinto its Lists/Collections. - Instead it uses a concept of a Stream.
- Think of a Stream as a sequence of values. (Picture a bunch of rubber ducks floating down a stream. Each duck has a value on it.)
- The methods above can apply to streams the same way they apply to arrays:
- removing ducks
- changing the data on each duck
- However, Streams don’t necessarily need to just be arrays. Other options include
- The same value repeated
- A range of values (e.g.,
[10, 20]) - A never-ending stream of random numbers. (These methods are in the
Randomclass) - A sequence of values generated by some algorithm (See
generatemethod)
- Users can then apply
filter,map,reduceetc. to the stream object. - There is extra work at the front and back end to turn arrays/ArrayLists into Streams, then the Stream back into array/array list; but, the chain of operations can be easier to follow.
- Even if you find this style cumbersome in Java, I find it very helpful /elegant in JavaScript and Ruby.
Parallelism
- Another benefit of Java’s Stream approach is to help automate parallelism.
- Think about
filter.- In the vast majority of cases, each filtering decision is completely independent of other decisions.
- For example, when filtering people by age, we could look at each person in parallel.
- If our machine has 8 processors, we may as well use all 8 at the same time.
- Similarly, most
mapoperations can run in parallel. - You create a parallel stream either
- directly from a Collection, or
- By calling
parallelon aStream
- When possible, operations on parallel streams will run in parallel.