Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
39 views

02 Functional Programming

Uploaded by

Pavel Zinevka
Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
39 views

02 Functional Programming

Uploaded by

Pavel Zinevka
Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 21

Basics of functional

programming in Java 8

A pragmatic overview

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
The gist of functional programming
No states of processing – solely functions which return results based on provided arguments – a
very important feature esp. in distributed processing
Functional programming paradigm – unlike imperative programming paradigm – focuses more on the
result (or processing single item) than specifying steps of algorithm which should be performed

Functional programming simplifies problem decomposition

Sample: create a list of square values of particular elements of integer number array
Imperative code:
List<Integer> src = Arrays.asList(5, 72, 10, 11, 9);
We focus on algorithm: List<Integer> target = new ArrayList<>();
(1) iterate over the source for (Integer n : src) {
collection; if (n < 10) { // filtering
(2) square element and add target.add(n * n); // processing and merging
result to the target. } // result
}

We focus on goals:
(1) specify predicate to filter the elements we want to
process;
(2) specify processing of an element.
Functional programming scheme:
List<Type> target = create( src,
                            a condition for filtering elements from src list,
                            specify the operation to perform on the selected elements );

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Specifying goals with interfaces

Interface Filter declares a predicate – a method test() responsible for selecting elements – returns
true if an element meets the criteria, false – otherwise.
public interface Filter<V> {
boolean test(V v);
}

Interface Transformer declares a method responsible for processing an element.


public interface Transformer<T,S> {
T transform(S v);
}

We have separated particular stages and realized intermediate phases of processing we can achieve
the ultimate goal by combining them.
static <T, S> List<T> create(List<S> src, Filter<S> f, Transformer<T, S> t) {
List<T> target = new ArrayList<>();
for (S e : src) {
if (f.test(e)) { // invoke filtering to be specified in usage
target.add(t.transform(e)); // invoke processing to be specified in usage }
} // and then merge the result
return target;
}

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Before Java 8
We could use anonymous classes and implement interfaces at site of use
List<Integer> src = Arrays.asList(5, 72, 10, 11, 9);
List<Integer> target = create(
src,

/** boilerplate code is greyed out */


new Filter<Integer>() {
public boolean test(Integer n) {
return n < 10;
}
},

new Transformer<Integer, Integer>() {


public Integer transform(Integer n) {
return n * n;
}
});

We had to provide some so called boilerplate code – i.e. pieces of code which do not introduce
much added value and solely result from the syntax of the programming language.

In the above sample the boilerplate parts are highlighted in blue, the parts which introduce
significant value are highlighted in green.

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Lambda expression
Lambda expression – an anonymous method considered as an object
List<Integer> Lambda expression with parameter n returns
target = create(src,
n -> n < 10, result of comparison n < 10
n -> n * n The compiler fits lambda-expression n -> n < 10
); into the second parameter (Filter<S>) of create()
method.
As a result method Filter<S>.test() is
Lambda expression with parameter n returns
implemented by the provided lambda expression.
squared n (i.e. n * n).
The required boilerplace code is generated by
The compiler fits lambda expression
the compiler
n -> n * n into the third parameter
(Transformer<T, S>) of create() method.
Method Transformer<T, S>.transform() is
implemented by the provided lambda
lambda expression in Java
expression.
The required boilerplace code is generated parameter list -> code
by the compiler.
parameter list – list of parameters enclosed
in brackets (if there are more than one)
code – a (1) single expression or a (2) set of
instructions enclosed in curly brackets
© Krzysztof Barteczko 2014 (translated
from Polish by Edgar Głowacki)
public class Employee {

private String lname;


Let us look at the simple private String fname;
Employee private Integer age;
private Double salary;
class.
public Employee(String lname, String fname, Integer age, Double salary) {
this.lname = lname;
this.fname = fname;
this.age = age;
this.salary = salary;
}

public String getLname() {


return lname;
}

public String getFname() {


return fname;
}

public Integer getAge() {


return age;
}

public Double getSalary() {


return salary;
}

public void setSalary(Double salary) {


this.salary = salary;
}

@Override
public String toString() {
return lname + " " + fname;
}
}

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Flexibility
List<Integer> num = Arrays.asList( 1, 3, 5, 10, 9, 12, 7);

List<String> txt = Arrays.asList(


“one", “two", “three", “four", “five", “six" );
• various data types
List<Employee> emp = Arrays.asList( • various predicates
new Employee(“Smith", “John", 34, 3400.0),
new Employee(“Brown", "Alexander", 27, 4100.0), • various transforms
new Employee(“Allen", “Sofia", 33, 3700.0),
new Employee(“Owen", “Michael", 41 , 3600.0)
);

System.out.println(
create(num, n -> n % 2 != 0, n -> n * 100)
);

System.out.println(
create(txt,
s -> s.startsWith(“t"),
s -> s.toUpperCase() + " " + s.length())
);

List<Employee> givePayRise =
create(emp,
e -> e.getAge() > 30 && e.getSalary() < 4000, [100, 300, 500, 900, 700]
e -> e [TWO 3, THREE 5]
); Pay rise should be given to:
System.out.println(“Pay rise should be given to:"); [Smith John, Allen Sofia, Owen
System.out.println(givePayRise); Michael]

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Mapping to other result types and method references

The result of method create() does not need to be list of the same type as the source list.
For instance we can easily get a list of employee salaries.
List<Double> sal =
create(emp,
e -> true,
e -> e.getSalary() [3400.0, 4100.0, 3700.0, 3600.0]
);
System.out.println(sal);

Instead of defining lamba expression we can also refer methods (method reference) defined in a
class.
e -> e.getSalary() is equivalent to Employee::getSalary

create(emp,
e -> true,
Employee::getSalary
);

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Modifying list elements with lambda expression

Let us create interface Modifier specifying a single method modify()


public interface Modifier<S> {
void modify(S v);
}

Let us define method change(), which modifies elements of the source list if they meet some given
predicate.
static <S> void change(List<S> list, Filter<S> f, Modifier<S> mod) {
for (S e : list) {
if (f.test(e)) {
mod.modify(e);
}
}
}

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Modifying list elements with lambda expression
List<Employee> emp =
Arrays.asList(
new Employee("Kowal", "Jan", 34, 3400.0),
new Employee("As", "Ala", 27, 4100.0),
new Employee("Kot", "Zofia", 33, 3700.0),
new Employee("Puchacz", "Jan", 41, 3600.0)
);

change(
emp,
e -> e.getAge() > 30 && e.getSalary() < 4000,
e -> {
double oldSalary = e.getSalary();
double newSalary = oldSalary + 200;
e.setSalary(newSalary);
});

for (Employee e : emp) {


System.out.println(e + " " + e.getSalary());
}

Kowal Jan 3600.0


As Ala 4100.0
Kot Zofia 3900.0
Puchacz Jan 3800.0

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Do we need to specify our custom interfaces?

No, we do not.
There are interfaces for frequent lambda expression usage schemes available out of the box in
package java.util.function.

Interfaces:
(1) java.util.function.Predicate and
(2) java.util.function.Function

static <T, S> List<T> create(List<S> src, Predicate<S> filter, Function<S, T> func) {
List<T> target = new ArrayList<>();
for (S e : src) { Method Predicate.test() evaluates boolean
if (filter.test(e)) {
target.add(func.apply(e)); result based on value of type S (generic
} parameter)
}
return target;
}
Method Function.apply() evaluates result of
type T based on value of type S

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Function<S, T> vs. Consumer<S>

Apart from Function<S, T> which returns a value java.util.function package provides interface
Consumer<S> whose single method accept() simply processes the input of type S.

NOTE: Collection processing scheme based on lambda expression resembles the one described in
Visitor design pattern which also defines method accept().

static <S> void change(List<S> list, Filter<S> f, Consumer<S> mod) {


for (S e : list) {
if (f.test(e)) {
mod.accept(e);
}
}
}
Method Consumer<S>.accept() does not return
any result.

The counterparts of java.util.function.Function<S, T> and java.util.function.Consumer<S> in


Microsoft .NET platform are delegates (types for method references) System.Function<S, T> and
System.Action<S> respectively.

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Streams (stream processing) – introduction
We do not need to define our custom interfaces whose methods are implemented by lambda
expressions.
Do we need to define methods which operate on lists – i.e. create() and change()?
No, we do not need either
create( List<Employee> givePayRise =
emp, create(
e -> true, // select all elements emp,
e -> e.getSalary() e -> e.getAge() > 30 && e.getSalary() < 4000,
); e -> e // return the processed element
);

Our custom method are not very flexible – we always assume the same processing scheme:
first (1) selecting the elements and then (2) processing one by one.
Inversing the above order would require providing another implementation
We may also want to further process the output – unfortunately our simple methods create() and
change().

We can use much more generic paradigm – streams (do not confuse with I/O streams).
In general a programming language construct when multiple methods invoked one after another as a
single statement and the consecutive methods consume the output of the preceding ones is called
method chaining.

Method chaining in Microsoft .NET world is also called fluent interface.


© Krzysztof Barteczko 2014 (translated
from Polish by Edgar Głowacki)
Streams (stream processing) – overview
Overview of stream processing concept in Java.
The consecutive method in the call chain uses output of the preceding one as its input.

.map
List Stream Stream
.filter

.filter
Stream ... Stream
.map

.collect scalar value,


.reduce container,
.forEach modification

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Streams (stream processing) – overview
Call of method stream() returns a stream or a sequence.
A stream allows us to further process the input with methods:
(1) map – processing;
(2) filter – filtering/selecting the elements to be subjected to further operations;
(3) collect – creating collections for the result of the preceding operations;
(4) reduce – aggregate the result of the preceding operations – i.e. create single element result;
(5) forEach – modify (or in general iterate over all) elements of the stream.

Method map() accepts a lambda expression returning a value of specified type based on input
parameter – exactly in the same way as Function<S, T>.apply() does.

Method filter() accepts a predicate as a lambda expression – exactly in the same way as
Predicate<S>.test() does.
Method filter() filters out the elements of the stream which do not satisfy the predicate.

Method collect() is the ultimate step of stream processing responsible for creating a collection (e.g.
list or array) based on object of Collector class instance (e.g. Collectors.toList()).

Method reduce() aggregates the values returned by stream processing.

Method forEach() enables processing values one by one (e.g. printing them out to the console).

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Streams (stream processing) – samples

Getting a list of squared elements of src which are less than 10.
import static java.util.stream.Collectors.toList;
// ...
List<Integer> src = Arrays.asList(5, 72, 10, 11, 9);
List<Integer> target = src
.stream()
.filter(n -> n < 10)
.map(n -> n * n)
.collect(toList()); [25, 81]

Inverting the order of operations – first compute a square of the element and then filter values
greater than 80.
List<Integer> num = Arrays.asList(1, 3, 5, 10, 9, 12, 7);
List<Integer> out = num
.stream()
.map(n -> n * n)
.filter(n -> n > 80)
.collect(Collectors.toList()); [100, 81, 144]

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Streams (stream processing) – map() and filter()
We can invoke map() and filter() multiple times and in an arbitrary order.

Sample:
(1) filter 3-digit strings representing numbers in decimal notation;
(2) convert filtered elements into numbers;
(3) filter even numbers;
(4) create a new list of strings in decimal notation.

List<String> snum = Arrays.asList("7", "20", "160", "777", "822");


snum = snum
.stream()

.filter(s -> s.length() == 3)


.map(s -> Integer.parseInt(s))

.filter(n -> n % 2 == 0) ["160“, "822“]


.map(n -> String.valueOf(n))

.collect(toList());

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Streams (stream processing) – forEach()

We can easily implement introducing modifications to salaries of employees with forEach().

List<Employee> emp = ...


Emp
.stream()
.filter(e -> e.getAge() > 30 && e.getSalary() < 4000)
.forEach(e -> e.setSalary(e.getSalary() * 1.1));
Kowal Jan 3740
As Ala 4100
Kot Zofia 4070
Puchacz Jan 3960

In the first example we use forEach() in order to process each element returned by filter().

List<Employee> emp = ...

emp.forEach( Kowal Jan 3740


e -> System.out.printf("%s %.0f\n", e, e.getSalary()) As Ala 4100
); Kot Zofia 4070
Puchacz Jan 3960

In the second example we illustrate how to use forEach() with Iterable interface implementations
(i.e. not a stream) – a sample of one of new default methods introduced in Iterable since Java 8

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Streams (stream processing) – reduce()

We can aggregate the result of stream operations performed on collections with reduce() method.

reduce(initial, function)
initial – initial value;
function – function which accepts two arguments:
(1) part the intermediate result and
(2) next consecutive stream element

part = initial
for each stream element {
reduce() processing scheme part = func.apply(part, next)
}
return part

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Streams (stream processing) – reduce()

Sample – calculate total for all employees’ salaries

List<Employee> emp = Arrays.asList(


new Employee("Kowal", "Jan", 34, 3400.0),
new Employee("As", "Ala", 27, 4100.0),
new Employee("Kot", "Zofia", 33, 3700.0),
new Employee("Puchacz", "Jan", 41, 3600.0)
);

Double sum = emp


.stream()
.map(Employee::getSalary)
.reduce(0.0, (part, next) -> part + next);

System.out.println(sum);

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)
Advantages lambda expressions in Java 8

1. a significant reduction of boilerplate code related with implementation of interfaces in


anonymous classes;

2. we do not need to create custom interfaces for operations on collections – most common
usages are supported by interfaces delivered out of the box in java.util.function package;

3. streams in Java 8 is a very flexible implementation which covers most of scenarios for
collection processing.

Streams introduced in Java 8 is a new approach for collection processing resembling


IEnumerable extension methods available in Microsoft .NET since version 3.0.

Streams enable “lazy evaluation” – i.e. evaluation of the given stream method in a
pipeline when the result for all the pipeline is requested – e.g. during iteration or
when we want to materialize the result in a list of elements.

© Krzysztof Barteczko 2014 (translated


from Polish by Edgar Głowacki)

You might also like