Functional Programming - HolmesJJ/OOP-FP GitHub Wiki

Meaning

  1. Simplify the codes and improve coding efficiency
  2. Reduce performance wastes

Pure Function

The same input will always get the same output without any significant side effects
The interactions with the external environment of the function are all side effects

  • Cannot perform I/O (System.out.println();)
  • Cannot throw exceptions
  • Cannot use Random() (same input will get different output)
  • Cannot send http request
  • Change the variables outside the function cannot affect the function

Functor

A functor is a Generic Type that contains a thing (also known as payload), and provides a Constructor (make(), also known as of()), and a map() method that transforms the payload.

Functor Laws

Law Description Explanation
Identity The value of make(t).map(x -> x) equals to the value of make(t),
which is make(t).map(x -> x).get() equals to make(t).get()
make(t): t
make(t).map(x -> x): t
Associativity The value of make(t).map(f).map(g) equals to the value of make(t).map(f.andThen.g),
which is make(t).map(f).map(g).get() equals to make(t).map(f.andThen.g).get()
make(t): t
make(t).map(f): f(t)
make(t).map(f).map(g): g(f(t))

make(t): t
f.andThen.g: g(f(x))
make(t).map(f.andThen.g): g(f(t))

Functor in Java

Class Constructor Method
Optional<T> of() map()
Stream<T> of() map()
ArrayList<T> Arrays.asList() None (Can be provided as following example below)
Function<T, U> Java’s assignment of Lambda expression andThen()
CompletableFuture<T> runAsync(), supplyAsync(), failedFuture(), completedFuture(), etc. thenApply()

map() of ArrayList<T>

<T,U> ArrayList<U> map(Function<T,U> f, ArrayList<T> list) {
    ArrayList<U> result = new ArrayList<>();
    for (T item: list) {
        result.add(f.apply(item));
    }
    return result;
}

filter() of ArrayList<T>

<T> ArrayList<T> filter(Predicate<T> p, List<T> list) {
    ArrayList<T> result = new ArrayList<>();
    for (T item : list) {
        if (p.test(item)) {
            result.add(item);
        }
    }
    return result;
}

Constructor and map() of Function<T, U>

// The Constructor is Java’s assignment of Lambda expression
Function<Boolean, String> boolStringFunc = v -> v ? "hello" : "goodbye";

// the map() method is andThen()
boolStringFunc.andThen(String::length);

Features of Functor

  • Functor is a container.
  • The general convention of Functional Programming is that the functor has a Constructor of() to generate a new container.
  • Functor contains the value, which is passed in by the Constructor of().
  • Functor has a map() method, which maps each value in the container to another container.
  • The calculations in Functional Programming are all done by Functor and do not directly operate the value in the Functor.
  • The functor itself has an external interface (map() method). The function passed in via the interface (map() method) is the operator to change the value in the Functor.

Monad

A Monad is a Parametrized Type that contains a thing, along with a Constructor (of(), also known as unit()), and a method flatmap() (also known as bind()).

Monad Laws

Law Description Explanation
Left Identity of(t).flatMap(f) == f(t),
which is of(t).flatMap(f).get() equals to f.apply(of(t).get()).get()
of(t): t
of(t).flatMap(f): f(t)
Right Identity The value of of(t).flatMap(t -> of(t)) equals to the value of of(t),
which is of(t).flatMap(t -> of(t)).get() equals to of(t).get()
of(t): t
of(t).flatMap(x -> of(x)): t (f(t) = t)
Associativity The value of of(t).flatMap(f).flatMap(g) equals to the value of of(t).flatMap(t -> f(t).flatMap(g)),
which is of(t).flatMap(f).flatMap(g).get() equals to of(t).flatMap(t -> f(t).flatMap(g)).get()
of(t): t
of(t).flatMap(f): f(t)
of(t).flatMap(f).flatMap(g): g(f(t))

of(t): t
t -> f(t).flatMap(g)): g(f(t))
of(t).flatMap(t -> f(t).flatMap(g)): g(f(t))

Right Identity Example

/**
 * t: [Arrays.asList("A", "B"), Arrays.asList("C", "D"), Arrays.asList("E", "F")]
 * f: x -> of(x) ===> List<String> -> Stream<List<String>>
 * of(t).flatMap(x -> of(x)) == of(t)
 */
public static void rightIdentityExample() {
    Stream<List<String>> stream = Stream.of(
            Arrays.asList("A", "B"),
            Arrays.asList("C", "D"),
            Arrays.asList("E", "F")
    );
    System.out.println(stream.collect(Collectors.toList()));

    Stream<List<String>> stream2 = Stream.of(
            Arrays.asList("A", "B"),
            Arrays.asList("C", "D"),
            Arrays.asList("E", "F")
    );
    Stream<List<String>> stream3 = stream2
            .flatMap(new Function<List<String>, Stream<List<String>>>() {
                @Override
                public Stream<List<String>> apply(List<String> strings) {
                    return Stream.of(strings);
                }
            });
    System.out.println(stream3.collect(Collectors.toList()));
}

/* Output */
[[A, B], [C, D], [E, F]]
[[A, B], [C, D], [E, F]]

Associativity Example

public static void associativityExample() {
    /**
     * of(t).flatMap(f).flatMap(g)
     */
    Stream<List<String>> stream = Stream.of(
            Arrays.asList("A", "B"),
            Arrays.asList("C", "D"),
            Arrays.asList("E", "F")
    );
    Stream<String> stream2 = stream
            .flatMap(new Function<List<String>, Stream<String>>() {
                @Override
                public Stream<String> apply(List<String> strings) {
                    return strings.stream();
                }
            }).flatMap(new Function<String, Stream<String>>() {
                @Override
                public Stream<String> apply(String strings) {
                    return Stream.of(strings);
                }
            });
    System.out.println(stream2.collect(Collectors.toList()));

    /**
     * of(t).flatMap(x -> f(x).flatMap(g))
     */
    Stream<List<String>> stream3 = Stream.of(
            Arrays.asList("A", "B"),
            Arrays.asList("C", "D"),
            Arrays.asList("E", "F")
    );
    Stream<String> stream4 = stream3
            .flatMap(new Function<List<String>, Stream<String>>() {
                @Override
                public Stream<String> apply(List<String> strings) {
                    Stream<String> s = strings.stream();
                    return s.flatMap(new Function<String, Stream<String>>() {
                        @Override
                        public Stream<String> apply(String strings) {
                            return Stream.of(strings);
                        }
                    });
                }
            });
    System.out.println(stream4.collect(Collectors.toList()));
}

/* Output */
[A, B, C, D, E, F]
[A, B, C, D, E, F]

Monad in Java

Class Constructor Method
Optional<T> of() flatMap()
Stream<T> of() flatMap()
ArrayList<T> Arrays.asList() None (Can be provided as following example below)
CompletableFuture<T> runAsync(), supplyAsync(), failedFuture(), completedFuture(), etc. thenCompose()

flatMap() of ArrayList<T>

<T,U> ArrayList<U> flatmap(Function<T, List<U>> f, List<T> list) {
    ArrayList<U> result = new ArrayList<>();
    for (T items : list) {
        result.addAll(f.apply(items));
    }
    return result;
}

map(), filter() and flatMap() of ArrayList<T> Example

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.function.Function;
import java.util.function.Predicate;

public class Main {

    public static void main(String[] args) {

        System.out.println(
                map(String::toUpperCase,
                        intoWords("The rain in Spain falls mainly in the plain"))
        );

        System.out.println(
                map(String::toUpperCase,
                        filter(s -> isEven(s.length()),
                                intoWords("The rain in Spain falls mainly in the plain")))
        );

        System.out.println(
                map(Main::stringReverse,
                        filter(word -> word.contains("i"),
                                intoWords("The rain in Spain falls mainly in the plain")))
        );

        System.out.println(
                map(Main::stringToList,
                        intoWords("CS2030 is great fun"))
        );

        System.out.println(
                flatmap(Main::stringToList,
                        intoWords("CS2030 is great fun"))
        );
    }

    public static <T,U> ArrayList<U> map(Function<T,U> f, ArrayList<T> list) {
        ArrayList<U> result = new ArrayList<>();
        for (T item: list) {
            result.add(f.apply(item));
        }
        return result;
    }

    public static <T,U> ArrayList<U> flatmap(Function<T, List<U>> f, List<T> list) {
        ArrayList<U> result = new ArrayList<>();
        for (T items : list) {
            result.addAll(f.apply(items));
        }
        return result;
    }

    public static <T> ArrayList<T> filter(Predicate<T> p, List<T> list) {
        ArrayList<T> result = new ArrayList<>();
        for (T item : list) {
            if (p.test(item)) {
                result.add(item);
            }
        }
        return result;
    }

    public static ArrayList<String> intoWords(String sentence) {
        ArrayList<String> result = new ArrayList<>();
        //Add each word into the result list
        new Scanner(sentence).forEachRemaining(result::add);
        return result;
    }

    public static boolean isEven(int n) {
        return n % 2 == 0;
    }

    public static String stringReverse(String s) {
        return new String(new StringBuffer(s).reverse());
    }

    public static ArrayList<String> stringToList(String s) {
        ArrayList<String> result = new ArrayList<>();
        for (char c : s.toCharArray()) {
            result.add(new String(new char[]{c}));
        }
        return result;
    }
}

/* Output */
[THE, RAIN, IN, SPAIN, FALLS, MAINLY, IN, THE, PLAIN]
[RAIN, IN, MAINLY, IN]
[niar, ni, niapS, ylniam, ni, nialp]
[[C, S, 2, 0, 3, 0], [i, s], [g, r, e, a, t], [f, u, n]]
[C, S, 2, 0, 3, 0, i, s, g, r, e, a, t, f, u, n]
⚠️ **GitHub.com Fallback** ⚠️