Navigate back to the homepage

Functional design patterns using rust

Bipin Paul Bedi
November 7th, 2019 · 3 min read

Wikipedia states - In software engineering, a software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. It is a description or template for how to solve a problem that can be used in many different situations. Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system.

Design patterns may be viewed as a structured approach to computer programming intermediate between the levels of a programming paradigm and a concrete algorithm.

If you are keen on functional programming, I would recommend reading Category Theory for Programmers, and in this post, we will be exploring some common functional programming design patterns

Functor Pattern

A functor is approximately the inverse of a function. A function defines a transformation, accepts data, and returns the result of the transformation. A functor defines data, accepts a function, and returns the result of the transformation.

1fn main() {
2 let m: Vec<u64> = vec![1, 2, 3];
3 let n: Vec<u64> = m.iter().map(|x| { x*x }).collect();
4 println!("{:?}", m);
5 println!("{:?}", n);

here ‘map’ is a functor. Thus a functor lifts or upgrades a function from one that cannot operate on an effect1 to one that can work on a single effect1, leaving the effect1 intact after the function is done.

effect1: ability to bring about a result i.e. list, array, tuple, struct, record, object, future, promise, function, tree, hashmap, dictionary etc.

I think of a functor as being a component of the command pattern, which also involves other infrastructure such as the invoker and command recipients.


1pub trait Functor: Generic1 {
2 fn fmap<Y, F: Fn(Self::Type) -> Y>(self, f: F) -> Self::Type
3 where Self: Rebind1<Y>;

Applicatives Pattern

Applicative functors allow us to deal with function of two arguments and two functors, by mapping curried function onto functor and then applying it to second functor

1let v1 = vec![1,2,3];
2let v2 = vec![4,5];
3let result: Vec = v1.iter().map(|x||y| x*y)).apply(v2.iter()).collect();
4let expect = vec![4, 5, 8, 10, 12, 15];
5assert_eq!(expect, result);

Based on above example a product of each element of V1 with V2 is applied, creating six elements in result vector. Then the following trait can form the foundation of applicative functors.

1pub trait Applicative: Functor {
2 fn apply<X, U>(self, x: X) -> Applied<Self, X>
3 where X: Applicative, Self: Sized { ... }

This pattern isn’t practically used in rust very often because this pattern is not considered idiomatic rust.

Monad Pattern

There is a natural hierarchy of types: any Monad is an Applicative, and any Applicative is a Functor.

This allows structuring programs generically while automating away the boilerplate code needed by the program logic. Monads achieve this by providing their own data type, which represents a specific form of computation, along with one procedure to wrap values of any basic type within the monad (yielding a monadic value) and another to compose functions that output monadic values (called monadic functions). Thus a monad defines return and bind operations for a type. The return operation is like a constructor to make the monad. The bind operation incorporates new information and returns a new monad.

1Monad::return(value) //We start with a new Monad<X>
2 .bind(|x| x+x) //We take a step into Monad<X+1>
3 .bind(|y| y*y); //Similarly we get to Monad<X+2>

Monadic bind operations are also polymorphic, meaning they should permit returning monads of different types from the current monad. A monad can hide state inside itself, which becomes essentially a larger, more complex function than what the programmer interacts with. One concrete example of side-effect hiding is the concept of a logger.

1use std::fmt::{Debug};
3struct UniversalLogger<T>(T);
5impl<T> UniversalLogger<T> {
6 fn return(t: T) -> UniversalLogger<T>
7 where T: Debug {
8 println!("{:?}", t);
9 UniversalLogger(t)
10 }
11 fn bind<R,F>(&self, f: F) -> UniversalLogger<R>
12 where F: FnOnce(&T) -> R,
13 R: Debug {
14 let r = f(&self.0);
15 println!("{:?}", r);
16 UniversalLogger(r)
17 }
20fn main() {
21 UniversalLogger::return(27111985)
22 .bind(|x| x+x)
23 .bind(|y| y*y)
24 .bind(|z| format!("{}{}{}", z, z, z));

The monad pattern is also very useful for chaining together code that can’t be written in a normal code block e.g. the lazy monad pattern such as an asynchronous web server.


1pub trait Monad: Functor {
2 fn unit(value: Self::Type) -> Self;
3 fn bind<U, F: Fn(Self::Type) -> Self::Type>(self, f: F)
4 -> Self::Type where Self: Rebind1<U>;
6 fn join<U>(self) -> Self::Type
7 where Self::Type: Equals<Self::Type>,
8 Self: Rebind1<U> + Sized { ... }

Combination Pattern

A combinator is a function that takes other functions as arguments and returns a new function. A simple example of a combinator would be the composition operator, which chains two functions together:

1fn compose<A,B,C,F,G>(f: F, g: G) -> impl Fn(A) -> C
2 where F: 'static + Fn(A) -> B,
3 G: 'static + Fn(B) -> C {
4 move |x| g(f(x))
7fn main() {
8 let fa = |x| x+1;
9 let fb = |y| y*2;
10 let fc = |z| z/3;
11 let g = compose(compose(fa,fb),fc);
12 println!("g(1) = {}", g(1));
13 println!("g(12) = {}", g(12));
14 println!("g(123) = {}", g(123));

Lazy Eval Pattern

A code blocks are always evaluated eagerly. If you want to define code that will be evaluated later or in pieces, the lazy eval pattern is very convenient. Lazy evaluation is a term used to describe code or data that is not evaluated until it is referenced. This is contrary to the typical eager evaluation of Rust code that will execute immediately regardless of context.

Iterators are lazy. They don’t do anything until you collect or otherwise iterate over them

Side Note
The last lazy pattern that we will introduce is functional reactive programming, FRP for short. There are entire programming languages, such as Elm, based on this concept. Popular web UI frameworks, such as React or Angular, are also influenced by FRP concepts. The FRP concept is an extension of the side-effect/state monad example. Event handling, state transitions, and side-effects can be turned into units of reactive programming. This is an advance concept and shall be covered in a separate post later.

#functional-programming #design-patterns #technology

More articles from Bipin

Clean architecture in functional programming

a clean approach to modularising software projects

November 5th, 2019 · 1 min read

57 counterproductive software design practices - anti patterns

deciding what not to do is as important as deciding what to do

March 27th, 2019 · 3 min read
© 2018–2050 Bipin
Link to $ to $ to $ to $