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
forEach
method 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
filter
and how it can be used to filter by any arbitrary rule. - Show
genericFilter
and how we now need only write the code once: AnyArrayList
can use the code. - If this method was an
ArrayList
instance 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
map
andfilter
tend 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.)
map
andfilter
can 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
map
implementation.
Inject / Reduce
- JavaScript’s
reduce
function (calledinject
in 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
, andreduce
into 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
Random
class) - A sequence of values generated by some algorithm (See
generate
method)
- Users can then apply
filter
,map
,reduce
etc. 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
map
operations can run in parallel. - You create a parallel stream either
- directly from a Collection, or
- By calling
parallel
on aStream
- When possible, operations on parallel streams will run in parallel.