What is the difference between a Functor and a Monad?

Swift Functor, Applicative, Monad

Functor, Applicative, Monad:

  • solve the same problem - working with a wrapped value into context(class)
  • using closure[About]
  • return a new instance of context(class)

The difference is in parameters of closure

Pseudocode:

class SomeClass<T> {
    var wrappedValue: T //wrappedValue: - wrapped value
    func foo<U>(function: ???) -> Functor<U> { //function: - function/closure
        //logic
    }
}

where ???

function: (T) -> U == Functor
function: SomeClass< (T) -> U > == Applicative
function: (T) -> SomeClass<U> == Monad

Functor

Functor applies a function to a wrapped value

Pseudocode:

class Functor<T> {
    var value: T
    func map<U>(function: (T) -> U) -> Functor<U> {
        return Functor(value: function(value)) //<- apply a function to value
    }
}

Applicative or applicative functor

Applicative applies wrapped function to a wrapped value.

The diff with Functor is wrapped function instead of function

Pseudocode:

class Applicative<T> {
    var value: T
    func apply<U>(function: Applicative< (T) -> U >) -> Applicative<U> {
        return Applicative(value: unwrappedFunction(value))
    }
}

Monad

Monad applies a function(which returns a wrapped value) to a wrapped value

Pseudocode:

class Monad<T> {
    var value: T
    func flatMap<U>(function: (T) -> Monad<U>) -> Monad<U> { //function which returns a wrapped value
        return function(value) //applies the function to a wrapped value
    }
}

Swift:

  • Optional, Collection, Result is Functor and Monad
  • String is Functor

Optional as an example

enum CustomOptional<T> {
    case none
    case some(T)
    
    public init(_ some: T) {
        self = .some(some)
    }
    
    //CustomOptional is Functor
    func map<U>(_ transform: (T) -> U) -> CustomOptional<U> {
        switch self {
        case .some(let value):
            let transformResult: U = transform(value)
            let result: CustomOptional<U> = CustomOptional<U>(transformResult)
            return result
        case .none:
            return .none
        }
    }
    
    //CustomOptional is Applicative
    func apply<U>(transformOptional: CustomOptional<(T) -> U>) -> CustomOptional<U> {
        switch transformOptional {
        case .some(let transform):
            return self.map(transform)
        case .none:
            return .none
        }
    }
    
    //CustomOptional is Monad
    func flatMap<U>(_ transform: (T) -> CustomOptional<U>) -> CustomOptional<U> {
        switch self {
        case .some(let value):
            let transformResult: CustomOptional<U> = transform(value)
            let result: CustomOptional<U> = transformResult
            return result
        case .none:
            return .none
        }
    }
}

[Swift Optional map vs flatMap]


Let me explain my understanding without going into category theory:

Functors and monads both provide some tool to wrapped input, returning a wrapped output.

Functor = unit + map (i.e. the tool)

where,

unit = something which takes raw input and wraps it inside a small context.

map = the tool which takes a function as input, applies it to raw value in wrapper, and returns wrapped result.

Example: Let us define a function which doubles an integer

// doubleMe :: Int a -> Int b
const doubleMe = a => 2 * a;
Maybe(2).map(doubleMe)  // Maybe(4)

Monad = unit + flatMap (or bind or chain)

flatMap = the tool which flattens the map, as its name implies. It will be clear soon with the example below.

Example: Let us say we have a curried function which appends two strings only if both are not blank.

Let me define one as below:

append :: (string a,string b) -> Maybe(string c)  

Let's now see the problem with map (the tool that comes with Functor),

Maybe("a").map(append("b")) // Maybe(Maybe("ab"))  

How come there are two Maybes here?

Well, that's what map does; it applies the provided function to the wrapped value and wraps the result.

Let's break this into steps,

  1. Apply the mapped function to the wrapped value ; here the mapped function is append("b") and the wrapped value is "a", which results in Maybe("ab").

  2. Wrap the result, which returns Maybe(Maybe("ab")).

Now the value we are interested in is wrapped twice. Here comes flatMap to the rescue.

Maybe("a").flatMap(append("b")) // Maybe("ab")

Of course, functors and monads have to follow some other laws too, but I believe this is not in the scope of what is asked.