Introduction to Functional Programming

Victor J. Reventos

What is Functional Programming?

  • It’s a style of programming that treats programs as evaluation of mathematical functions and avoids mutable state and side effects

Functional Programming Benefits

  • Declarative Code (what vs how)
  • Explicitness
  • Concurrency
  • Composability & Modularity
  • Testability
  • Easy to understand code

FP Concepts

  • Declarative vs Imperative
  • Pure Functions
  • Higher Order Functions
  • Function Composition
  • Immutability
  • Map/Filter/Reduce

Declarative vs Imperative

  • Defining WHAT to do vs HOW to do it
  • Expressive
  • Eliminate side-effects as much as possible

Declarative vs Imperative

    // Imperative 
    public static List<Integer> evenNumbers(List<Integer> integers) {
        final List<Integer> result = new ArrayList<>();

        for (Integer i : integers) {
            if (i % 2 == 0) {
                result.add(i);
            }
        }

        return result;
    }

    // Declarative / Function
    public static List<Integer> evenNumbers(List<Integer> integers) {
        return integers.stream()
                .filter(i -> i % 2 == 0)
                .collect(ImmutableList.toImmutableList());
    }

Pure Functions

  • Pure functions act on their parameters
  • Always produce the same output for the given parameters
    • Calling the function a "thousand times" should return the same value a every time.
  • Have No side effects

Pure Functions


public int value() {
    return 10;
}

public int add(int x, int y) {
    return x + y
}

public long countCharacters(List<String> strings) {
    return strings.stream()
            .map(String::length)
            .count();
}

Impure Functions


public class Impure {

    private int counter = 1;

    public int value() {
        return counter++;
    }

    public int add(int x, int y) {
        return x + y + counter++;
    }
}

public String sayHello(Person person) {
     // Nasty side effect
     person.setGreeted(true);
     return "Hello " + person.getName(); 
}

Referential Transparency

  • An expression is said to be referentially transparent if it can be replaced with its corresponding value without changing the program's behavior
  • A function composed of pure functions is a referentially transparent function

 

In short, it does what the type signature says its going to do.

Referential Transparency

// referentially transparent
// no surprises
public int multiply(int x, int y) {
    return x * y;
}

// Not referentially transparent

// is not valid for y = 0 and it will throw an exception
public int divide(int x, int y) {
    return x / y;
}

// Where does it force me to handle an error?
public int doSomething() {
    // do work
    // sneaky who knew I would give you a nasty side effect
    // exceptions: the glorified GOTO statement.
    throw new RuntimeException();
}

Higher Order Functions

  • Functions are first class citizens
    • Functions can take functions as arguments
    • Functions can return functions
  • Enables function composition

Higher Order Functions - Locking

public class Locking {

    // properly handle locking and unlock even when exceptions happen.
    public <T> T withLock(Lock lock, Supplier<T> supplier) {
        lock.lock();
        try {
            return supplier.get();
        } finally {
            lock.unlock();
        }
    }
}

Higher Order Functions - Timing

@Log4j2
public class Timing {

    public static <T> T time(Supplier<T> supplier, Consumer<Long> timeConsumer) {
        val startTime = System.nanoTime();
        val value = supplier.get();
        timeConsumer.accept(System.nanoTime() - startTime);
        return value;
    }
    
    
    public int doRealWork() {
        return 20 * 20;
    }
    
    public int timeDoRealWork() {
        return time(this::doRealWork, log::info);
    } 
}

Function Composition

 Is applying one function to the results of another: The result of f() is sent through g() It is written: (g º f)(x)

 

newFunction(x) = g(f(x))

Function Composition

public class Composition {

    public Function<Integer, Integer> times2() {
        return x -> x * 2;
    }

    public Function<Integer, Integer> square() {
        return x -> x * x;
    }

    public Function<Integer, Integer> squareTimes2() {
        return times2().andThen(square());
    }
    
    public Function<Integer, Integer> squaredTimes4() {
        return times2().andThen(times2()).andThen(square());
    }
    
    // see... LEGOS
    public Function<Integer, String> squaredTimes2InHex() {
        return squareTimes2().andThen(Integer::toHexString);
    } 
}

Immutability

  • An immutable object is an object whose state cannot be modified after it is created
    • Are thread-safe
    • Easy to test
    • Easy to track (no need to keep track how/where your object changes)
    • Favors caching

Immutability

@Value
@Builder
public ImmutableDataClass {
   private final String field1;
   private final int field2;
   ...
}

Common Functional Functions

  • map - Transforms a value into another
  • filter - Returns the value if it matches the given predicate
  • reduce - Applies a given function against an accumulator to reduce it to a single value

Problem - Count the number of Album titles that start with "Pop"

public long countTheNumberOfTitlesThatStartWithPop(List<Album> albums) {
    return albums.stream()
            // transform Album -> String
            .map(Album::getTitle)
            // filter - keep titles that start with "Pop"
            .filter(title -> title.startsWith("Pop"))
            // reduce -> accumulate over a count
            // this could also be written using .count()
            .reduce(0, (accumulator, title) -> accumulator + 1);
}

Null

  • Null breaks composition
// this is not a referentially transparent function
// where in the type signature does it say that the User might be absent?
public Function<Integer, User> getUser() {
    return id -> null;
}

public Function<User, Comments> getUserComments() {
    return user -> {
        if (user != null) {
            // fetch user comments
        } else {
            return null;
        }
    };
}

public Function<Integer, Comments> getComments() {
    return getUser().andThen(getComments());
}

Optional to the Rescue

  • Generally known as encoding effects with types
  • Optional models the potential absence of a value


// The type signature tells you that user might be absent.
public Optional<User> getUser(int id) {
    return Optional.empty();
}

public Comments getUserComments(User user) {
    // fetch comments
}

public Result doSomethingWithComments() {
    val comments = getUser(1).map(this::getUserComments);
    // do something with it.
}

Advanced Topics

  • Monoids / Functors / Applicative
  • Functional Architecture
  • Functional Error Handling

Questions?

May the Function be with you.

Introduction to Functional Programming

By Victor J. Reventos

Introduction to Functional Programming

Introduction to functional programming with Java examples.

  • 121