oop and fp: become a better programmer - simone bordet, mario fusco - codemotion rome 2015

Post on 15-Jul-2015

219 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Simone Bordet

Mario Fusco

OOP and FP

Become aBetter Programmer

Our definitionOf OOP and FP

Simone Bordet

Mario Fusco

Our Definition of OOP

In this presentation “OOP” will mean:

Idiomatic Java 7 programming style: Use of mutable variables and state Use of classes and void methods Use of external iteration (for loops) Use of threads

Simone Bordet

Mario Fusco

Our definition of FP

In this presentation “FP” will mean:

Java 8 programming style, with: Immutable variables and state Use classes, but avoid void methods Internal iteration via Stream CompletableFuture, not Thread

Goals

Simone Bordet

Mario Fusco

Goals

This session is about OOP and FP NOT about OOP versus FP

We want to show that in order to be a better programmer, you have to know both

In some cases it's better to apply one paradigm

In other cases it's better to apply the other We will hint at some guideline that helps deciding

Example #1“accumulator”

Simone Bordet

Mario Fusco

Example #1, v1

public String sum(List<Student> students) {

StringBuilder sb = new StringBuilder();

for (Student s : students)

sb.append(s.getName()).append(“, “);

return sb.toString();

}

OOP style External iteration Mutable variables

Simone Bordet

Mario Fusco

Example #1, v2

public String sum(List<Student> students) {

StringBuilder sb = new StringBuilder();

students.stream()

.forEach(s -> sb.append(s.getName()).append(“, “));

return sb.toString();

}

BAD style Use of mutable accumulator

Simone Bordet

Mario Fusco

Example #1, v3

public String sum(List<Student> students) {

String names = students.stream()

.map(s -> s.getName() + “, “)

.reduce(“”, (a, b) -> a + b);

return names;

}

FP style Internal iteration Mapping input to output No mutable variables

Example #2“what do you do ?”

Simone Bordet

Mario Fusco

Example #2, v1

What does this code do ?

List<Student> students = ...;

int min = 0;

for (Student s : students) {

if (s.getGradYear() != 2014)

continue;

int score = s.getGradScore();

if (score > min)

min = score;

}

Simone Bordet

Mario Fusco

Example #2, v2

Calculates the max, not the min ! And only for 2014 students !

List<Student> students = ...;

students.stream()

.filter(s -> s.getGradYear() == 2014)

.mapToInt(Student::getScore)

.max();

Somehow clearer to read

Less possibility of mistakes max() is a method, not a variable name

Simone Bordet

Mario Fusco

Example #2, v3

But how do you get 2 results iterating once ?

List<Student> students = ...;

int min = Integer.MAX_VALUE, max = 0;

for (Student s : students) {

int score = s.getGradScore();

min = Math.min(min, score);

max = Math.max(max, score);

}

Easy and readable

Simone Bordet

Mario Fusco

Example #2, v4

FP version:

Pair<Integer, Integer> result = students.stream()

.map(s -> new Pair<>(s.getScore(), s.getScore()))

.reduce(new Pair<>(Integer.MAX_VALUE, 0), (acc,elem)-> {

new Pair<>(Math.min(acc._1, elem._1),

Math.max(acc._2, elem._2))

});

What !?!

Simone Bordet

Mario Fusco

Example #2, v5

How about parallelizing this ?

Pair<Integer, Integer> result = students.stream().parallel()

.map(s -> new Pair<>(s.getScore(), s.getScore()))

.reduce(new Pair<>(Integer.MAX_VALUE, 0), (acc,elem)-> {

new Pair<>(Math.min(acc._1, elem._1),

Math.max(acc._2, elem._2))

});

Neat, but .parallel() can only be used under very strict conditions.

Example #3“let's group them”

Simone Bordet

Mario Fusco

Example #3, v1

Group students by their graduation year

Map<Integer, List<Student>> studentByGradYear = new HashMap<>();

for (Student student : students) {

int year = student.getGradYear();

List<Student> list = studentByGradYear.get(year);

if (list == null) {

list = new ArrayList<>();

studentByGradYear.put(year, list);

}

list.add(student);

}

Simone Bordet

Mario Fusco

Example #3, v2

Map<Integer, List<Student>> studentByGradYear =

students.stream()

.collect(groupingBy(student::getGradYear));

Example #4“separation of concerns”

Simone Bordet

Mario Fusco

Example #4, v1

Read first 40 error lines from a log file

List<String> errorLines = new ArrayList<>();

int errorCount = 0;

BufferedReader file = new BufferedReader(...);

String line = file.readLine();

while (errorCount < 40 && line != null) {

if (line.startsWith("ERROR")) {

errorLines.add(line);

errorCount++;

}

line = file.readLine();

}

Simone Bordet

Mario Fusco

Example #4, v2

List<String> errors = Files.lines(Paths.get(fileName))

.filter(l -> l.startsWith("ERROR"))

.limit(40)

.collect(toList());

Simone Bordet

Mario Fusco

Example #4

List<String> errorLines = new ArrayList<>();

int errorCount = 0;

BufferedReader file = new BufferedReader(new FileReader(filename));

String line = file.readLine();

while (errorCount < 40 && line != null) {

if (line.startsWith("ERROR")) {

errorLines.add(line);

errorCount++;

}

line = file.readLine();

}

return errorLines;

return Files.lines(Paths.get(fileName))

.filter(l -> l.startsWith("ERROR")

.limit(40)

.collect(toList());

Example #5“grep -B 1”

Simone Bordet

Mario Fusco

Example #5, v1

Find lines starting with “ERROR” and previous line

List<String> errorLines = new ArrayList<>();

String previous = null;

String current = reader.readLine();

while (current != null) {

if (current.startsWith("ERROR")) {

if (previous != null)

errorLines.add(previous);

errorLines.add(current);

}

previous = current;

current = reader.readLine();

}

Simone Bordet

Mario Fusco

Example #5, v2

Not easy – immutability is now an obstacle

Must read the whole file in memory

This does not work:

Stream.generate(() -> reader.readLine())

readLine() throws and can't be used in lambdas

Simone Bordet

Mario Fusco

Example #5, v2

Files.lines(Paths.get(filename))

.reduce(new LinkedList<String[]>(),

(list, line) -> {

if (!list.isEmpty())

list.getLast()[1] = line;

list.offer(new String[]{line, null});

return list;

},

(l1, l2) -> {

l1.getLast()[1] = l2.getFirst()[0];

l1.addAll(l2); return l1;

}).stream()

.filter(ss -> ss[1] != null && ss[1].startsWith("ERROR"))

.collect(Collectors.toList());

Example #6“callback hell”

Simone Bordet

Mario Fusco

Example #6, v1

Find a term, in parallel, on many search engines, then execute an action

final List<SearchEngineResult> result =

new CopyOnWriteArrayList<>();

final AtomicInteger count = new AtomicInteger(engines.size());

for (Engine e : engines) {

http.newRequest(e.url("codemotion")).send(r -> {

String c = r.getResponse().getContentAsString();

result.add(e.parse(c));

boolean finished = count.decrementAndGet() == 0;

if (finished)

lastAction.perform(result);

});

}

Simone Bordet

Mario Fusco

Example #6, v1

Code smells Mutable concurrent accumulators: result and count Running the last action within the response callback

What if http.newRequest() returns a CompletableFuture ?

Then I would be able to compose those futures !

Let's try to write it !

Simone Bordet

Mario Fusco

Example #6, v2

CompletableFuture<List<SearchEngineResult>> result =

CompletableFuture.completed(new CopyOnWriteArrayList<>());

for (Engine e : engines) {

CompletableFuture<Response> request =

http.sendRequest(e.url("codemotion"));

result = result.thenCombine(request, (list, response) -> {

String c = response.getContentAsString();

list.add(e.parse(c));

return list;

});

}

result.thenAccept(list -> lastAction.perform(list));

Simone Bordet

Mario Fusco

Example #6, v3

List<CompletableFuture<SearchEngineResult>> results =

engines.stream()

.map(e ->

new Pair<>(e, http.newRequest(e.url("codemotion"))))

.map(p ->

p._2.thenCombine(response -> p._1.parse(response.getContentAsString())))

.collect(toList());

CompletableFuture.supplyAsync(() -> results.stream()

.map(future -> future.join())

.collect(toList()))

.thenApply(list -> lastAction.perform(list));

Example #7“statefulness”

Simone Bordet

Mario Fusco

Example #7, v1

class Cat {

private Bird prey;

private boolean full;

void chase(Bird bird) { prey = bird; }

void eat() { prey = null; full = true; }

boolean isFull() { return full; }

}

class Bird {

}

Simone Bordet

Mario Fusco

Example #7, v1

It is not evident how to use it:

new Cat().eat() ???

The use case is instead:

Cat useCase(Cat cat, Bird bird) {

cat.chase(bird);

cat.eat();

assert cat.isFull();

return cat;

}

Simone Bordet

Mario Fusco

Example #7, v2

How about we use types to indicate state ?

class Cat {

CatWithPrey chase(Bird bird) {

return new CatWithPrey(bird);

}

}

class CatWithPrey {

private final Bird prey;

public CatWithPrey(Bird bird) { prey = bird; }

FullCat eat() { return new FullCat(); }

}

class FullCat { }

Simone Bordet

Mario Fusco

Example #7, v2

Now it is evident how to use it:

FullCat useCase(Cat cat, Bird bird) {

return cat.chase(bird).eat();

}

BiFunction<Cat, Bird, CatWithPrey> chase = Cat::chase;

BiFunction<Cat, Bird, FullCat> useCase =

chase.andThen(CatWithPrey::eat);

More classes, but clearer semantic

Example #8“encapsulation”

Simone Bordet

Mario Fusco

Example #8, v1

interface Shape2D {

Shape2D move(int deltax, int deltay)

}

class Circle implements Shape {

private final Point center;

private final int radius;

Circle move(int deltax, int deltay) {

// translate the center

}

}

class Polygon implements Shape {

private final Point[] points;

Polygon move(int deltax, int deltay) {

// Translate each point.

}

}

Simone Bordet

Mario Fusco

Example #8, v1

for (Shape shape : shapes)

shape.move(1, 2);

How do you do this using an FP language ?

What is needed is dynamic polymorphism Some FP language does not have it Other FP languages mix-in OOP features

Simone Bordet

Mario Fusco

Example #8, v2

defn move [shape, deltax, deltay] (

// Must crack open shape, then

// figure out what kind of shape is

// and then translate only the points

)

OOP used correctly provides encapsulation

FP must rely on OOP features to provide the same Data types are not enough Pattern matching is not enough Really need dynamic polimorphism

Conclusions

Simone Bordet

Mario Fusco

Conclusions

If you come from an OOP background Study FP

If you come from an FP background Study OOP

Poly-paradigm programming is more generic, powerful and effective than polyglot programming.

Simone Bordet

Mario Fusco

Questions&

Answers

top related