Functional Programming Patterns

Learn Haskell in 10 minutes

Lists

"Hello" ++ ", World!" -- "Hello world!"
[1..5] == [1, 2, 3, 4, 5]
['A'..'F'] == "ABCDEF"
[1..]
[1..3] ++ [4..6] -- [1, 2, 3, 4, 5, 6]
[x*2 | x <- [1..5]] -- [2, 4, 6, 8, 10]
[(x, y) | x <- [1..3], y <- [1..3], x + y > 4] -- [(2,3), (3,2), (3,3)]

Functions

add :: Integer -> Integer -> Integer
add a b = a + b
add 1 3 -- 4
addOne = add 1 -- :: Integer -> Integer

addOne 4 -- 5
main :: IO ()
main = putStrLn "Hello, World!"

Controls

factorial :: Integer -> Integer
factorial n = if n < 2
              then 1
              else n * factorial (n - 1)
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)

Data

Products

data Person = Person Name String Int
data Person = Person {
  name :: Name,
  address :: String,
  age :: Int
  }

Sum

data Bool = False | True
data Maybe a = Nothing | Just a
data Error err a = Error err | Result a

Algebraic Data Types

data DeliveryStatus = Packing | Shipping Int | Delivered

Patterns

  • Semigroup
  • Monoid
  • Functor
  • Monad
  • Catamorphism
  • Appendable
  • Aggregatable
  • Mappable
  • Chainable
  • Collapsable

Category theory

Category

category.png

Examples of categories

name objects morphism
Set sets functions
Grp groups group homomorfisms
VectK vector spaces over field K linear transformations

Hask category

hask-category.png

Monoid

One configuration source

Config configFromArgs = fromArgs(args);
startApplication(configFromArgs);

Two configuration sources

Config configFromArgs = fromArgs(args);
Config configFromFile = fromFile("config.yml");
Config config = combine(configFromFile, configFromArgs);
startApplication(config);

Multiple configuration sources

Config config = combineAll(List.of(configFromServer,
                                   configFromFile,
                                   configFromSystemEnv,
                                   configFromArgs));
startApplication(config);
public <T> T apply(T a, T b);

Overview

\begin{multline} \shoveleft (G, \cdot : G \times G \rightarrow G) \\ \shoveleft (x \cdot y) \cdot z = x \cdot (y \cdot z) \\ \shoveleft e \cdot x = x \cdot e = x \\ \end{multline}

Java

public interface Monoid<A> {
    A apply(A a, A b);
    A empty();
}

Haskell

class Monoid a where
  (<>)    :: a -> a -> a
  mempty  :: a

Examples

String monoid

Java

class StringMonoid implements Monoid<String> {
    public String empty() { return ""; }
    public String apply(String a, String b) {
        return a + b;
    }
}
StringMonoid m = new StringMonoid();
m.apply("Hello", m.apply(" ", "World"));

Haskell

instance Monoid String where
  (<>)   = (++)
  mempty = ""
"Hello" <> " " <> "World"

Numeric monoids

Java

class IntSumMonoid implements Monoid<Integer> {
    public Integer empty() { return 0; }
    public Integer apply(Integer a, Integer b) {
        return a + b;
    }
}
IntSumMonoid m = new IntSumMonoid();
m.apply(10, m.apply(2, 3));

Haskell

instance Num a => Monoid (Sum a) where
  Sum a <> Sum b = Sum (a + b)
  mempty = Sum 0
Sum 10 <> Sum 2 <> Sum 3

Java

class IntProdMonoid implements Monoid<Integer> {
    public Integer empty() { return 1; }
    public Integer apply(Integer a, Integer b) {
        return a * b;
    }
}
IntProdMonoid m = new IntProdMonoid();
m.apply(10, m.apply(2, 3));

Haskell

instance Num a => Monoid (Product a) where
  Product a <> Product b = Product (a * b)
  mempty = Product 1
Product 10 <> Product 2 <> Product 3

Reduce

Java

public static <T> T mconcat(Monoid<T> monoid, List<T> list)

Haskell

mconcat :: Monoid a => [a] -> a

Reduce

monoid-1.png

Functor

Customer customer = findCustomerByName(name);
String city = null;
if (customer != null) {
    city = customer.getAddress().getCity();
}
List<Customer> customers = findAllCustomers();
List<String> cities = new ArrayList<String>();
for (Customer customer : customers) {
    String city = customer.getAddress().getCity();
    cities.add(city);
}
Future<Customer> customer = findCustomerByName(name);
String city = customer.get().getAddress().getCity();

Overview

Java

interface Functor<A> {
    <B> Functor<B> map(Function<A, B> fn);
}

Haskell

class Functor f where
  fmap :: (a -> b) -> f a -> f b

Laws

\begin{multline} \shoveleft f : X \rightarrow Y \in C, g : Y \rightarrow Z \in C \\ \shoveleft F(\text{id}_x)=\text{id}_{F(x)} \\ \shoveleft F(g \circ f) = F(g) \circ F(f) \\ \end{multline}

Identity Law

functor.map(x -> x) == functor

Composition Law

functor.map(x -> f(g(x))) == functor.map(g).map(f)

Examles

Optional

class Optional<T> implements Functor<T> {
    private final T value;

    private Optional(T value) {
        this.value = value;
    }

    @Override
    public <R> Optional<R> map(Function<T, R> f) {
        if (value == null)
            return empty();
        else
            return of(f.apply(value));
    }

    public static <T> Optional<T> of(T a) {
        return new Optional<T>(a);
    }

    public static <T> Optional<T> empty() {
        return new Optional<T>(null);
    }
}
Optional<Customer> customer = findCustomerByName(name);
Optional<String> city = customer
    .map(Customer::getAddress)
    .map(Address::getCity);

Haskell

Maybe a = Nothing | Just a
instance Functor Maybe where
    fmap _ Nothing   = Nothing
    fmap f (Just a)  = Just (f a)
fmap length (Just "Hello!")
length <$> Just "Hello!"

List

class FList<T> extends ArrayList<T> implements Functor<T> {

    @Override
    public <R> FList<R> map(Function<T, R> f) {
        FList<R> result = new FList<>();
        for (int i = 0; i < size(); i++) {
            R newElement = f.apply(get(i));
            result.add(newElement);
        }
        return result;
    }
}
FList<Customer> customers = getAllCustomers();
FList<String> cities = customers
    .map(Customer::getAddress)
    .map(Address::getCity);

Haskell

instance Functor [] where
  fmap = map
fmap (* 2) [1, 2, 3, 4]
(* 2) <$> [1, 2, 3, 4]

Promise

class Promise<T> implements Functor<T> {
    public <R> Promise<R> map(Function<T, R> f) { ... }
}
Promise<Customer> customer = customerServiceApi.getCustomerById(id);
Promise<String> city = customer
    .map(Customer::getAddress)
    .map(Address::getCity);

Monad

public Optional<Manager> findLocalManager(String city) { ... }
public Optional<Customer> findCustomerByName(String name) { ... }
public Optional<Manager> findLocalManager(String city) { ... }

//...
Optional<Customer> customer = findCustomerByName(name);
Optional<Optional<Manager>> manager = customer
    .map(Customer::getAddress)
    .map(Address::getCity)
    .map(city -> findLocalManager(city));

map

public <B> Functor<B> map(Function<A, B> fn);
fmap :: (a -> b) -> f a -> f b

flatMap

public <B> Functor<B> flatMap(Function<A, Functor<B>> fn);
flatMap :: (a -> f b) -> f a -> f b

Overview

Java

public interface Monad<T, M extends Monad<?, ?>> extends Functor<T> {
    M flatMap(Function<T, M> f);
}

Haskell

class Functor m => Monad m where
    (>>=)   :: m a -> (a -> m b) -> m b
    return  :: a -> m a

Examples

Optional

class Optional<T> implements Monad<T, Optional<T>> {
    private final T value;

    private Optional(T value) {
        this.value = value;
    }

    public static <T> Optional<T> of(T a) {
        return new Optional<>(a);
    }

    public static <T> Optional<T> empty() {
        return new Optional<>(null);
    }

    @Override
    public <B> Optional<B> map(Function<T, B> fn) {
        if (value == null)
            return empty();
        else
            return of(fn.apply(value));
    }

    @Override
    public Optional<T> flatMap(Function<T, Optional<T>> fn) {
        if (value == null)
            return empty();
        else
            return fn.apply(value);
    }
}
public Optional<Manager> findLocalManager(String city) { ... }

//...
Optional<Customer> customer = findCustomerByName(name);
Optional<Manager> manager = customer
    .map(Customer::getAddress)
    .map(Address::getCity)
    .flatMap(this::findLocalManager);
class Person {
    private PersonalData personalData;
}

class PersonalData {
    private Contact contact;
}

class Contact {
    private Address address;
}

class Address {
    private String city;
}
String city = null;
if (person.getPersonalData() != null
    && person.getPersonalData().getContact() != null
    && person.getPersonalData().getContact().getAddress() != null) {
    city = person.getPersonalData().getContact().getAddress().getCity();
}
class Person {
    private Optional<PersonalData> personalData;
}

class PersonalData {
    private Optional<Contact> contact;
}

class Contact {
    private Optional<Address> address;
}

class Address {
    private String city;
}
Optional<String> city = person.getPersonalData()
    .flatMap(PersonalData::getContact)
    .flatMap(Contact::getAddress)
    .map(Address::getCity);

Promise

public Promise<Customer> getCustomerByName(String name) { ... }
public Promise<Manager> getLocalManager(Address address) { ... }
public Promise<Meeting> scheduleMeeting(Manager m, Customer c) { ... }
Promise<Meeting> meeting = getCustomerByName(name)
    .flatMap(customer ->
             getLocalManager(customer.getAddress())
                 .flatMap(manager ->
                          scheduleMeeting(manager, customer)));

For comprehension

Scala:

val meeting = getCustomerByName(name)
  .flatMap(customer =>
      getLocalManager(customer.address)
        .flatMap(manager =>
          scheduleMeeting(manager, customer))
  )
val meeting = for {
  customer <- getCustomerByName(name)
  manager <- getLocalManager(customer.address)
  meeting <- scheduleMeeting(manager, customer)
} yield meeting

Either

public class Either<L, R> implements Monad<R, Either<L, R>> {
    private final L leftVal;
    private final R rightVal;

    private Either(L left, R right) {
        this.leftVal = left;
        this.rightVal = right;
    }

    public static <L, R> Either<L, R> left(L left) {
        return new Either<>(left, null);
    }

    public static <L, R> Either<L, R> right(R right) {
        return new Either<>(null, right);
    }

    @Override
    public Either<L, R> flatMap(Function<R, Either<L, R>> f) {
        if (leftVal != null)
            return left(leftVal);
        else
            return f.apply(rightVal);
    }

    @Override
    public <B> Either<L, B> map(Function<R, B> f) {
        if (leftVal != null)
            return left(leftVal);
        else
            return right(f.apply(rightVal));
    }
}

Java

public Either<Err, Customer> getCustomerByName(String name) { ... }
public Either<Err, Manager> getLocalManager(Address address) { ... }
public Either<Err, Meeting> scheduleMeeting(Manager m, Customer c) { ... }

Either<Err, Meeting> meeting = getCustomerByName(name)
    .flatMap(customer ->
             getLocalManager(customer.getAddress())
             .flatMap(manager ->
                      scheduleMeeting(manager, customer)));

Scala

val meeting = for {
  customer <- getCustomerByName(name)
  manager <- getLocalManager(customer.address)
  meeting <- scheduleMeeting(manager, customer)
} yield meeting

Thank you!

Simplicity and elegance are unpopular because they require hard work and discipline to achieve and education to be appreciated.

And to make matters worse: complexity sells better.

— Edsger Dijkstra