# Cats

Cats is a simple and concise functional programming library for Scala. It appeals to me for its compactness and simpler class hierarchy.

I have no formal education in category theory. But I am a student of theoretical Mathematics and have studied Abstract Algebra. Though I am not aware of formal category theory terms, I will try converting them to simple set theory.

Category theory is concerned with study of algebraic structures. Algebraic system can be described as a set of objects together with some operations for combining them. Example’s can be vectors, groups, fields etc. Or even Sets.

The aim of this blog post is to explain `cats` first in a very simple lucid way. And they try putting it in code. I will be using some terms:

1. Universe: For example: Integer-Universe would imply a universe containing integers or set of integers. Think of it as our infinite terrestrial universe which contains milky way which contains solar system and ultimately planets.
2. Space/Type: A set of elements corresponding to a property. Space exists in universe. You can think Space as a type in scala.

## Functor

Let `A` and `B` be space in some universe. And there exists a function `f` from A to B.  Then Functor for `X` universe is a mapping from (mapping of space A in X universe) `X(A)` to (mapping of space `B` in universe `X`) `X(B)` Now what does this mean in scala world? Say we have a function `f` that tells us how to convert from type A to type B. So `f` does `Int => String`.

Now we want a mechanism to convert elements of another universe, say `Option` universe. First we need a mechanism to convert from space `A` in our universe to `Option(A)` (Space A in Option universe). We will see how it happens later below (Applicative).

So now some how we have mapped A to Option universe. Lets call it `Option(A)` space. We wish to convert from space `Option(A)` to space `Option(B)` (i.e. we need to `Option[Int] => Option[String]`) (Or F[Option] in above image)

Functors are exactly for this purpose with `map` being its flagship method:

```trait Functor[F[_]]  { self =>
def map[A, B](f: F[A])(f: A => B): F[B]
}
```
```implicit val OptionFunctor = new Functor[Option] {
override def map[A, B](fa: Option[A])(f: (A) => B): Option[B] = fa map f
}

Functor[Option].map(Some(1))(x => x*13)
Functor[Option].map(Some(1))(x => x.toString)
```

Functor[Option] is a mapping mechanism from space `Option(A)` to set `Option(B)` in Option universe. Think of it as:

We know how to convert from a space A to another space B in some universe. In another universe X, Functor of X lets us convert from X(A) to X(B)

So it lets you convert from `Option[Int] => Option[String]` or `Option[String] => Option[List[Int]]` etc. In short any space to any space in `Option` universe

Lift:

Lift is my favorite method in Functor. Notice with map, provided you have `Option[A]`, map converts to `Option[B]`. i.e. it consumes `Option[A]` object. But the conversion from `Option[A] => Option[B]` is just another function can be useful if it can be pass around as function. i.e. `F[X]` in above image.

```def toStri(n:Int) = n.toString
val lift: (Option[Int]) => Option[String] = Functor[Option].lift(toStri)
lift(Some(1))
```

If you think, lift seems something like this:

```val lift = Functor[Option].map _ //ignore map types
```

`lift` seems everything of a `map` but as a pure function. And thats how lift is implemented

```def lift[A, B](f: A => B): F[A] => F[B] =
x => map(x)(f)
```

For a function from Space A to Space B, lift provides a function in Universe X to convert from X(A) to X(B)

## Applicative

Now notice, using a Functor we can convert from X(A) to X(B), given a function A to B. But how do we convert an element x belonging to A, to an element in X(A)? Applicative lets us do that:

```implicit val OptionApplicative = new Applicative[Option] {
override def pure[A](x: A): Option[A] = Option(x)

override def apply[A, B](fa: Option[A])(f: Option[(A) => B]): Option[B] = ...
}
val op: Option[Int] = Applicative[Option].pure(1)
```

`pure` lets us convert an element from a universe to another. In above case, it lets us convert from element of type `Int` to `Option[Int]`. Applicative is an `Apply`

## Apply

Apply is also a Functor.

```def apply[A, B](fa: F[A])(f: F[A => B]): F[B]
```

Just instead of the function `f` which converts from `A => B`, you have `X(A => B)`. i.e. a function in X universe. Notice it is not `X[A] => X[B]`.

Lets have some more comprehensive example:

```implicit val ListApplicative = new Applicative[List] {
override def pure[A](x: A): List[A] = List(x)

override def apply[A, B](fa: List[A])(f: List[(A) => B]): List[B] = fa.flatMap(x => f.map(y => y(x)))
}

val lsOp = Applicative[List].pure(2)
```

You ask the Applicative for `List` to convert an element 2 to something in `List` universe. i.e. `List(2)`

```
scala> val ls = Applicative[List].apply(List(1,2,3))(List(x => "a=>"+x, y => "b ==>"+y))
ls: List[String] = List(a=>1, b ==>1, a=>2, b ==>2, a=>3, b ==>3)

scala> val ls2 = Applicative[List].apply2(List(1,2,3), List(4,5,6))(List((x:Int,y:Int) => x+" --a--> "+y, (x:Int,y:Int) => x+" --b--> "+y))
ls2: List[String] = List(1 --a--> 4, 1 --b--> 4, 2 --a--> 4, 2 --b--> 4, 3 --a--> 4, 3 --b--> 4, 1 --a--> 5, 1 --b--> 5, 2 --a--> 5, 2 --b--> 5, 3 --a--> 5, 3 --b--> 5, 1 --a--> 6, 1 --b--> 6, 2 --a--> 6, 2 --b--> 6, 3 --a--> 6, 3 --b--> 6)

```

### Compose:

```implicit val OptionApplicative = new Applicative[Option] {
override def pure[A](x: A): Option[A] = Option(x)

override def apply[A, B](fa: Option[A])(f: Option[(A) => B]): Option[B] = fa.flatMap(x => f.map(y => y(x)))
}
scala> val comp= (Applicative[List] compose Applicative[Option])
comp: cats.Applicative[[α]List[Option[α]]] = cats.Applicative\$\$anon\$1@168d1858

scala> comp.pure(12)
res5: List[Option[Int]] = List(Some(12))

```

Compose is quite interesting. So say you have an Applicative for List universe and another Applicative for Option universe. By composing them, you get an Applicative for `List[Option]`. Calling pure convert’s 12 from Int to a List(Option(1)).

PS: Will keep updating with time