Workshop java8 - "From Imperative to Declarative" - practical example

Plan

  • We will start with something familiar which is an old style imperative loop which reads csv file.
  • Then we will learn how to make computation configurable to reduce repetition.
  • We will also see how FP simplifies testing
  • Meanwhile we will start thinking in higher levels of abstraction
  • At the end we will create reusable library which can be easily used to solve different problems.

Dependency Injection in FP

Before Java 8 Dependency Injection was mainly about composing objects with different objects and inside different object.

So in standard web app yellow circle could be a controller and blue could be DAO or something like this.

In FP we have a new very handy mechanism which can be used to implement DI - currying

Function<BLUE,Function<GREEN,Function<RED,YELLOW>>> injectDependency =
  blue -> 
    green ->
      red -> yellow(blue,green,red)

Exercise : Functional DI for logger

Exercise file : https://github.com/PawelWlodarski/workshops/blob/master/src/main/java/workshops/functional/fp2/exercises/LoggerModule.java

Test file : https://github.com/PawelWlodarski/workshops/blob/master/src/test/java/workshops/functional/fp2/exercises/LoggerModuleTest.java

As a warm up let's define a curried logging function. It has signature

public static Function<Logging,Consumer<String>> createLogger

Where consumer is a Function String -> () which is the simplest definition of a logger.

Logging is an interface deined in the same class/module

 public interface Logging{
        default void log(String message){
            System.out.println("FP2 : "+message);
        }
    }

    private static class DefaultLogger implements Logging{};

Also try to implement unit test to check how easy will it be to inject testable version of logger

Consumer<String> testableLogger= null;

testableLogger.accept("message1");
testableLogger.accept("message2");
testableLogger.accept("message3");

/*
import static org.assertj.core.api.Assertions.*;
assertThat(loggerWithMemory.getLogs()).contains("message1","message2","message3");
*/

ADDITIONAL EXERCISE

Try to define second logger which also logs date when log was created. Try to use import java.time.Clock; which can be mocked in tests.

Reading CSV File

During this workshops we are going to parse following CSV file :

Login,Product,Price,Date
user1,tv,3000,01-02-2016
user2,tv,3000,02-02-2016
user1,console,1500,02-02-2016
user3,book,30,03-02-2016
user3,poster,15,03-02-2016
user1,stereo,1000,03-02-2016
user3,tv,3000,04-02-2016
user1,mouse,25,10-02-2016

In the first example we are working with monolithic imperative loop.

The plan is to start with a classical solution which we saw many times in Java7 or earlier and gradually move to functional composition.

Exercise : count users

Exercise code : https://github.com/PawelWlodarski/workshops/blob/master/src/main/java/workshops/functional/fp2/exercises/ImperativeLoop1.java

In the first exercise you have to count how many times given user has purchased a product. Save result in Map. Check the new Map functionality which was added in Java8 : Map.compute.

Map<String,Integer> counts=new HashMap<>();

            //LAB
            for (String line : lines) {
                // split line
                // take user
                // fill counts map with [user -> number of occurences]
            }

At the end run main and check the result.

Repetition - part 2

Our first task was to count users. The second one is to count how many given product was purchased . Sounds similar? Because it is VERY similar to the first example.

Exercise : count products

Exercise code : https://github.com/PawelWlodarski/workshops/blob/master/src/main/java/workshops/functional/fp2/exercises/ImperativeLoop2.java

Implement whichProductPurchasedTheMost() functionality.

ADDITIONAL

Implement dateWhenMostProductsWasPurchased()

DRY & passing an algorithm - part 3

Here we are going to solve problem with repetition introduced in the previous example by passing a recipe to our algorithm on which field use in logic.

Also this time we will be able to finally write a unit test for part of our funcitonality.

Exercise : Function to extract user

Exercise file : https://github.com/PawelWlodarski/workshops/blob/master/src/main/java/workshops/functional/fp2/exercises/ConfigurableLoop3.java

Test File: https://github.com/PawelWlodarski/workshops/blob/master/src/test/java/workshops/functional/fp2/exercises/ConfigurableLoop3Test.java

Write a function to extract user field from a csv line.

 static Function<String,String> extractUser = ...

ADDITIONAL 1

Crate curried function which extracts any field.

static Function<Integer,Function<String,String>> extractField

ADDITIONAL 2

Write generic extract function

static <A> Function<String,A> extractToObject(Integer index,Function<String,A> fieldToObject){
        throw new UnsupportedOperationException("lab not finished");
    }

Make testing easy again - part 4

Now we are going to extract another part of the functionality to make testing easy.

By moving count functionality to external function we can test this part of code independently.

 @Test
    public void testFieldSummaryFunction() throws Exception {
        List<String> input = Arrays.asList("user1", "user2", "user1", "user2", "user1");

        Map<String, Integer> output = ConfigurableLoop4Answer.fieldsSummary.apply(input);

        assertThat(output).containsEntry("user1",3).containsEntry("user2",2);
    }

Exercise : Function to count occurences

Exercise File : https://github.com/PawelWlodarski/workshops/blob/master/src/main/java/workshops/functional/fp2/exercises/ConfigurableLoop4.java

Test File: https://github.com/PawelWlodarski/workshops/blob/master/src/test/java/workshops/functional/fp2/exercises/ConfigurableLoop4Test.java

static Function<List<String>,Map<String,Integer>> fieldsSummary

ADDITIONAL

Implement generic variant :

 static <A> Function<Collection<A>,Map<A,Integer>> createGenericFieldsSummary(){
        throw new UnsupportedOperationException("lab not implemented");
    }

Configurable loop - part 5

It's time to move most of fixed functionality outside the loop. This time we are going to split our logic into extracting field and further processing. Where further processing may be anything built from simple functions.

We will also generalize result type.

So our loop function will look like this :

static <B> List<B> queryForMostUsages(Function<String,String> extractField, Function<List<String>,List<B>> createSummary){

Exercise : Function to sort counts and join them into the final report

Exercise File : https://github.com/PawelWlodarski/workshops/blob/master/src/main/java/workshops/functional/fp2/exercises/ConfigurableLoop5.java

Test File: https://github.com/PawelWlodarski/workshops/blob/master/src/test/java/workshops/functional/fp2/exercises/ConfigurableLoop5Test.java

static Function<Map<String,Integer>, List<String>> joinSorted

ADDITIONAL :

As a preparation for next phases implement lift high order function which transforms a simple function into a function which operates on a container level

 static <A,B> Function<AbstractContainer<A>,AbstractContainer<B>> lift(Function<A,B> pure){
            throw new UnsupportedOperationException("lab not completed");
 }

Functional abstractions - part 6

Time to create general functional library which will provide all necessary abstraction to build composable logic.

We are going to create special function which is able to take pure function which operates on simple values and lift it into level of collections

Exercise : Implement lift to collection method

Exercise file: https://github.com/PawelWlodarski/workshops/blob/master/src/main/java/workshops/functional/fp2/exercises/ProcessCollection6.java

Test file: https://github.com/PawelWlodarski/workshops/blob/master/src/test/java/workshops/functional/fp2/exercises/ProcessCollection6Test.java

public static <A,B> Function<Collection<A>,Collection<B>> liftToCollection(Function<A,B> f)

ADDITIONAL

Implement lift to optional

public static <A,B> Function<Optional<A>,Optional<B>> liftToOptional(Function<A,B> f)

Processing engine - part 7

Here we are going to limit our main function into a method which reads a file and then process it's content according to provided algorithm.

Exercise File : https://github.com/PawelWlodarski/workshops/blob/master/src/main/java/workshops/functional/fp2/exercises/ProcessCollection7.java

Exercise : computation as external functions

Implement remove header and complete logic invocation in the main function.

static Function<Collection<String>,Collection<String>> removeHeader

More abstractions - part 8

Lets build another abstractions which could be moved to our FP library.

Exercise : more FP asbtarctions in library

Exercise File : https://github.com/PawelWlodarski/workshops/blob/master/src/main/java/workshops/functional/fp2/exercises/ProcessCollection8.java

Test File: https://github.com/PawelWlodarski/workshops/blob/master/src/test/java/workshops/functional/fp2/exercises/ProcessCollection8Test.java

Implement general method which produces count function

public static <A> Function<Collection<A>,Map<A,Integer>> countFunction()

ADDITIONAL

Implement sort function :

 public static <A> Function<Collection<A>,Collection<A>> sortFunction(Comparator<A> comparator)

Computation pipeline - part 9

As the final step we will add reading file to ours computation pipeline. Because reading file has significant side effect then this facts needs to expressed properly in return type.

Exercise : implement read lines function

static Function<String,List<String>> readLines

ADDITIONAL

Implement safer variant of read line with optionality effect :

static Function<String,Optional<List<String>>> safeReadLines

ADDITIONAL

Use Try mechanism from JavaSlang library to perform calculation in Possible Error Effect instead of Optionality effect

static Function<String,Try<List<String>>> exceptionallySafeReadLines

Final Observations

Now take a look at the main method and observe how the whole computation can be executed and interpreted according to the effects which was used in types.

theProgram.apply("fpjava/purchases.csv").forEach(logger);

safeProgram.apply("fpjava/purchases.csv").orElse(Arrays.asList("THERE WAS UNKNOWN ERROR")).forEach(logger);

exceptionallySafeProgram.apply("fpjava/purchases.csv")
                .getOrElseThrow(e->new RuntimeException("ERROR DURING COMPUTATION",e))
                .forEach(logger);

Practice

And try to find a solution to the problem : "calculate final amount of money on the account 001". In Functional library you will also find a new function

static <A> Function<Collection<A>,Collection<A>> filterFunction(Predicate<A> p)

You may use following skeleton but it is not necessary.

https://github.com/PawelWlodarski/workshops/blob/master/src/main/java/workshops/functional/fp2/practice/TransactionsPractice.java

Function<Collection<String>, Collection<Tuple3<String, Integer, String>>> toTuples = null;

Function<Collection<Tuple3<String, Integer, String>>, Collection<Tuple3<String, Integer, String>>> filter001 = null

Function<Collection<Tuple3<String, Integer, String>>, Collection<Integer>> mapToTransfers = null;

Function<Collection<String>, Collection<Integer>> transformation =
                removeHeader.andThen(toTuples).andThen(filter001).andThen(mapToTransfers);


readLines.apply("fpjava/transactions.csv")
                .map(transformation)
                .map((Collection<Integer> sums)->sums.stream().reduce((i1,i2)->i1+i2))
                .getOrElseThrow(e->new RuntimeException("ERROR DURING COMPUTATION",e))
                .ifPresent(System.out::println);

results matching ""

    No results matching ""