Liking cljdoc? Tell your friends :D

Cats Documentation

cats logo

1. Introduction

Category Theory and algebraic abstractions for Clojure.

2. Rationale

The main motivations for writing this library are:

  • The existing libraries do not have support for ClojureScript.

  • We do not intend to write a little Haskell inside Clojure. We have adopted a practical and Clojure like approach, always with correctness in mind.

  • We do not like viral/copyleft like licenses and in contrast to other libraries cats is licensed under the BSD (2 clauses) license.

  • We do not intend to only implement monads. Other category theory and algebraic abstractions are also first class in cats.

Alternatives:

  • algo.monads: This is the official Clojure library for monads. Its approach for modeling monads is slightly limited, only supports the monad abstraction and does not have support for ClojureScript.

  • fluokitten: Slightly unmaintaned by the original author. It is focused on being very practical without taking care of correctness (for example, it extends Clojure types with monadic abstractions that do not make sense). It has no support for ClojureScript either.

  • monads: Is the most advanced monads library, supports also functors, applicatives and other related abstractions. It lacks a good and readable documentation, focus on correctness, has Haskell like sugar syntax (instead of Clojure like syntax) and does not have support for ClojureScript.

All listed alternatives are licensed with EPL or similar licenses.

3. Project Maturity

Since cats is a young project, there can be some API breakage.

4. Install

This section covers installing cats.

4.1. Leiningen

The simplest way to use cats in a Clojure project is by including it as a dependency in your project.clj:

[cats "0.4.0"]

4.2. Get the Code

cats is open source and can be found on github.

You can clone the public repository with this command:

git clone https://github.com/funcool/cats

5. User Guide

This section introduces almost all the category theory and algebraic abstractions that the cats library supports.

We will use Maybe for the example snippets, because it has support for all the abstractions and is very easy to understand. You can read more about it in the next section of this documentation.

5.1. Semigroup

A semigroup is an algebraic structure with an associative binary operation (mappend). Most of the builtin collections form a semigroup because their associative binary operation is analogous to Clojure’s into.

(require '[cats.core :as m])

(m/mappend [1 2 3] [4 5 6])
;; => [1 2 3 4 5 6]

Given that the values it contains form a Semigroup, we can mappend multiple Maybe values.

(require '[cats.core :as m])
(require '[cats.monad.maybe :as maybe])

(m/mappend (maybe/just [1 2 3])
           (maybe/just [4 5 6]))
;; => #<Just [1 2 3 4 5 6]>

5.2. Monoid

A Monoid is a Semigroup with an identity element (mempty). For the collection types the mempty function is analogous to Clojure’s empty.

Given that the values it contains form a Semigroup, we can mappend multiple Maybe, with Nothing being the identity element.

(require '[cats.core :as m])
(require '[cats.monad.maybe :as maybe])

(m/mappend (maybe/just [1 2 3])
           (maybe/nothing)
           (maybe/just [4 5 6])
           (maybe/nothing))
;; => #<Just [1 2 3 4 5 6]>

5.3. Functor

Let’s dive into the functor. The Functor represents some sort of "computational context", and the abstraction consists of one unique function: fmap.

Signature of fmap function
(fmap [f fv])

The higher-order function fmap takes a plain function as the first parameter and a value wrapped in a functor context as the second parameter. It extracts the inner value, applies the function to it and returns the result wrapped in same type as the second parameter.

But what is the functor context? It sounds more complex than it is. A Functor wrapper is any type that acts as "Box" and implements the Context and Functor protocols.

One good example of a functor is the Maybe type:
(require '[cats.monad.maybe :as maybe])

(maybe/just 2)
;; => #<Just 2>

The just function is a constructor of the Just type that is part of the Maybe monad.

Let’s see one example of using fmap over a just instance:

Example using fmap over just instance.
(require '[cats.core :as m])

(m/fmap inc (maybe/just 1))
;; => #<Just 2>

The Maybe type also has another constructor: nothing. It represents the absence of a value. It is a safe substitute for nil and may represent failure.

Let’s see what happens if we perform the same operation as the previous example over a nothing instance:

Example using fmap over nothing.
(m/fmap inc (nothing))
;; => #<Nothing >

Oh, awesome, instead of raising a NullPointerException, it just returns nothing. Another advantage of using the functor abstraction, is that it always returns a result of the same type as its second argument.

Let’s see an example of applying fmap over a Clojure vector:

Example using fmav over vector.
(require '[cats.builtin])

(m/fmap inc [1 2 3])
;; => [2 3 4]

The main difference compared to the previous example with Clojure’s map function, is that map returns lazy seqs no matter what collection we pass to it:

(type (map inc [1 2 3]))
;; => clojure.lang.LazySeq (cljs.core/LazySeq in ClojureScript)

But why can we pass vectors to the fmap function? Because some Clojure container types like vectors, lists and sets, also implement the functor abstraction.

5.4. Applicative

Let’s continue with applicative functors. The Applicative Functor represents some sort of "computational context" like a plain Functor, but with the ability to execute a function wrapped in the same context.

The Applicative Functor abstraction consists of two functions: fapply and pure.

Signature of fapply function
(fapply [af av])
the pure function will be explained later.

The use case for Applicative Functors is roughly the same as for plain Functors: safe evaluation of some computation in a context.

Let’s see an example to better understand the differences between functor and applicative functor:

Imagine you have some factory function that, depending on the language, returns a greeter function, and you only support a few languages.

(defn make-greeter
  [^String lang]
  (condp = lang
    "es" (fn [name] (str "Hola " name))
    "en" (fn [name] (str "Hello " name))
    nil))

Now, before using the resulting greeter you should always defensively check if the returned greeter is a valid function or a nil value.

Let’s convert this factory to use the Maybe type:

(defn make-greeter
  [^String lang]
  (condp = lang
    "es" (just (fn [name] (str "Hola " name)))
    "en" (just (fn [name] (str "Hello " name)))
    (nothing)))

As you can see, this version of the factory differs only slightly from the original implementation. And this tiny change gives you a new superpower: you can apply the returned greeter to any value without a defensive nil check:

(fapply (make-greeter "es") (just "Alex"))
;; => #<Just "Hola Alex">

(fapply (make-greeter "en") (just "Alex"))
;; => #<Just "Hello Alex">

(fapply (make-greeter "it") (just "Alex"))
;; => #<Nothing >

Moreover, the applicative functor comes with the pure function, which allows you to put some value in side-effect-free context of the current type.

Examples:

(require '[cats.monad.maybe :as maybe])

(pure maybe/maybe-monad 5)
;; => #<Just 5>

If you do not understand the purpose of the pure function, the next section should clarify its purpose.

5.5. Monad

Monads are the most discussed programming concept to come from category theory. Like functors and applicatives, monads deal with data in contexts.

Additionally, monads can also transform contexts by unwrapping data, applying functions to it and putting new values in a completely different context.

The monad abstraction consists of two functions: bind and return

Bind function signature.
(bind [mv f])

As you can see, bind works much like a Functor but with inverted arguments. The main difference is that in a monad, the function is responsible for wrapping a returned value in a context.

Example usage of the bind higher-order function.
(m/bind (maybe/just 1)
        (fn [v] (maybe/just (inc v))))
;; => #<Just 2>

One of the key features of the bind function is that any computation executed within the context of bind (monad) knows the context type implicitly. With this, if you apply some computation over some monadic value and you want to return the result in the same container context but don’t know what that container is, you can use return or pure functions:

Usage of return function in bind context.
(m/bind (maybe/just 1)
        (fn [v]
          (m/return (inc v))))
;; => #<Just 2>

The return or pure functions, when called with one argument, try to use the dynamic scope context value that’s set internally by the bind function. Therefore, you can’t use them with one argument outside of a bind context.

We now can compose any number of computations using monad bind functions. But observe what happens when the number of computations increases:

Composability example of bind function.
(m/bind (maybe/just 1)
        (fn [a]
          (m/bind (maybe/just (inc a))
                  (fn [b]
                    (m/return (* b 2))))))

This can quickly lead to callback hell. To solve this, cats comes with a powerful macro: mlet

Previous example but using mlet macro.
(m/mlet [a (maybe/just 1)
         b (maybe/just (inc a))]
  (m/return (* b 2)))
If you are coming from Haskell, mlet represents the do-syntax.

If you want to use regular (non-monadic) let bindings inside an mlet block, you can do so using :let and a binding vector inside the mlet bindings:

(m/mlet [a (maybe/just 1)
         b (maybe/just (inc a))
         :let [z (+ a b)]]
  (m/return (* z 2)))

5.6. Monad Transformers

5.6.1. Motivation

We can combine two functors and get a new one automatically. Given any two functors a and b, we can implement a generic fmap for the type a (b Any), we’ll call it fmap2:

(ns functor.example
  (:require [cats.core :refer [fmap]]
            [cats.builtin]
            [cats.monad.maybe :refer [just]]))

(defn fmap2
  [f fv]
  (fmap (partial fmap f) fv))

; Here, 'a' is [] and 'b' is Maybe, so the type of the
; combined functor is a vector of Maybe values that could
; contain a value of any type.
(fmap2 inc [(maybe/just 1) (maybe/just 2)])
;;=> [#<Just 2> #<Just 3>]

However, monads don’t compose as nicely as functors do. We have to actually implement the composition ourselves.

In some circumstances we would like combine the effects of two monads into another one. We call the resulting monad a monad transformer, which is the composition of a "base" and "inner" monad. A monad transformer is itself a monad.

5.6.2. Using monad transformers

Let’s combine the effects of two monads: State and Maybe. We’ll create the transformer using State as the base monad since we want the resulting type to be a stateful computation that may fail: s → Maybe (a, s).

Almost every monad implemented in cats has a monad transformer for combining it with any other monad. The transformer functions take a Monad as their argument and they return a reified MonadTrans:

(ns transformers.example
  (:require [cats.core :as m]
            [cats.data :as data]
            [cats.monad.maybe :as maybe]
            [cats.monad.state :as state]))

(def maybe-state
  (state/state-transformer maybe/maybe-monad))

(m/with-monad maybe-state
  (state/run-state (m/return 42) {}))
;; => #<Just #<Pair [42 {}]>>

As we can see in the example below, the return of the maybe-state monad creates a stateful function that yields a Maybe containing a pair (value, next state).

You probably noticed that we had to wrap the state function invocation with cats.core/with-monad. When working with monad transformers, we have to be explicit about what monad we are using to implement the binding policy since there is no way to distinguish values from a transformer type from those of a regular monad.

The maybe-state monad combines the semantics of both State and Maybe.

6. Types

6.1. Maybe

This is one of the two most used monad types (also known as Optional in other programming languages).

The Maybe monad represents encapsulation of an optional value; e.g. it is used as the return type of functions which may or may not return a meaningful value when they are applied. It consists of either an empty constructor (called None or Nothing), or a constructor encapsulating the original data type A (e.g. Just A or Some A).

cats, implements two types:

  • Just that represents a value in a context.

  • Nothing that represents the abscense of value.

Example creating instances of Just and Nothing types:
(maybe/just 1)
;; => #<Just 1>

(maybe/nothing)
;; => #<Nothing >

There are other useful functions for working with maybe monad types in the same namespace. See the API documentation for a full list of them. But here we will explain a little relevant subset of them.

We mentioned above that fmap extracts the value from a functor context. You will also want to extract values wrapped by just and you can do that with from-maybe.

As we said previously, the Just or Nothing instances, act like wrappers and in some circumstances you will want extract the plain value from them. cats offers the from-maybe function for that.

Example using from-maybe to extract values wrapped by just.
(maybe/from-maybe (maybe/just 1))
;; => 1

(maybe/from-maybe (maybe/nothing))
;; => nil

(maybe/from-maybe (maybe/nothing) 42)
;; => 42

The from-maybe function is a specialized version of a more generic one: cats.core/extract. The generic version is a polymorphic function and will also work with different types of different monads.

6.2. Either

Either is another type that represents a result of a computation, but (in contrast with maybe) it can return some data with a failed computation result.

In cats it has two constructors:

  • (left v): represents a failure.

  • (right v): represents a successful result.

Usage example of Either constructors.
(require '[cats.monad.either :refer :all])

(right :valid-value)
;; => #<Right [:valid-value :right]>

(left "Error message")
;; => #<Either [Error message :left]>
Either is also (like Maybe) a Functor, Applicative Functor and Monad.

6.3. Exception

Also known as the Try monad, as popularized by Scala.

It represents a computation that may either result in an exception or return a successfully computed value. Is very similar to the Either monad, but is semantically different.

It consists of two types: Success and Failure. The Success type is a simple wrapper, like Right of the Either monad. But the Failure type is slightly different from Left, because it always wraps an instance of Throwable (or Error in cljs).

The most common use case of this monad is to wrap third party libraries that use standard Exception based error handling. In normal circumstances, however, you should use Either instead.

It is an analogue of the try-catch block: it replaces try-catch’s stack-based error handling with heap-based error handling. Instead of having an exception thrown and having to deal with it immediately in the same thread, it disconnects the error handling and recovery.

Usage example of try-on macro.
(require '[cats.monad.exception :as exc])

(exc/try-on 1)
;; => #<Success [1]>

(exc/try-on (+ 1 nil))
;; => #<Failure [#<NullPointerException java.lang.NullPointerException>]>

cats comes with other syntactic sugar macros: try-or-else that returns a default value if a computation fails, and try-or-recover that lets you handle the return value when executing a function with the exception as first parameter.

Usage example of try-or-else macro.
(exc/try-or-else (+ 1 nil) 2)
;; => #<Success [2]>
Usage example of try-or-recover macro.
(exc/try-or-recover (+ 1 nil)
                    (fn [e]
                      (cond
                        (instance? NullPointerException e) 0
                        :else 100)))
;; => #<Success [0]>

The types defined for the Exception monad (Success and Failure) also implement the Clojure IDeref interface, which allows library development using monadic composition without forcing a user of that library to use or understand monads.

That is because when you dereference the failure instance, it will reraise the enclosed exception.

Example dereferencing a failure instance
(def f (exc/try-on (+ 1 nil)))

@f
;; => NullPointerException   clojure.lang.Numbers.ops (Numbers.java:961)

6.4. State

The State monad is one of the special cases of monads most commonly used in Haskell. It has several purposes including: lazy computation, composition, and maintaining state without explicit state.

The de-facto monadic type of the state monad is a plain function. A function represents a computation as is (without executing it). Obviously, a function should have some special characteristics to work in monad state composition.

Valid function for valid state monad
(fn [state]
  "Takes a state as argument and returns a vector
  with the first element being the processed value and
  the second element being the new transformed state."
  (let [newvalue (first state)
        newstate (next state)]
    [newvalue newstate]))

You just saw an example of the low-level primitive state monad. For basic usage you do not need to write your own functions, just use some helpers that cats provides.

Let’s look at one example before explaining the details:

Lazy composition of computations
(require '[cats.monad.state :as st])
(m/mlet [state (st/get-state)
         _     (st/put-state (next state))]
  (return (first state)))
;;=> #<State cats.monad.state.State@2eebabb6>

At the moment of evaluation in the previous expression, nothing of what we have defined is executed. But instead of returning the unadorned final value of the computation, a strange/unknown object of type State is returned.

The State type is simply a wrapper for Clojure functions, nothing more.

Now, it’s time to execute the composed computation. We can use one of the following functions exposed by cats for that: run-state, eval-state and exec-state.

  • run-state executes the composed computation and returns both the value and the result state.

  • eval-state executes the composed computation and returns the resulting value, discarding the state.

  • exec-state executes the composed computation and returns only the resulting state, ignoring the resulting value.

Example of resuls of using the previosly listed functions
(m/run-state s [1 2 3])
;;=> #<Pair [1 (2 3)]>

(m/eval-state s [1 2 3])
;;=> 1

(m/exec-state s [1 2 3])
;;=> (2 3)

The run-state function returns an instance of the Pair type. The Pair type acts like any other seq in Clojure with the exception that it can only contain two values.

6.10. Set

TODO

6.11. Map

TODO

6.12. Validation

The validation type is similar to the Either or Exception types except that it doesn’t implement a Monad instance. It has two constructors: ok and fail, representing success and failure respectively.

(require '[cats.applicative.validation :as v])
(require '[cats.core :as m])

(v/ok 42)
;;=> #<Ok 42>

(v/fail [])
;;=> #<Fail []>

It implements the Applicative protocol, and its intended usage is as an Applicative. Applying Validation values together errs on the side of the failure, and applying failures together aggregates their values using the Semigroup’s mappend function.

(require '[cats.applicative.validation :as v])
(require '[cats.core :as m])

(m/fapply (v/ok 42) (v/fail "OH NOES"))
;;=> #<Fail "OH NOES">

;; Note that `<*>` is a variadic fapply
(m/<*> (v/ok 42)
       (v/fail {:foo "bar"})
       (v/fail {:baz "fubar"})
       (v/ok 99))
;;=> #<Fail {:baz "fubar", :foo "bar"}>

6.13. Complementary libraries

Some monads are defined as separated package to avoid additional and unnecesary dependencies to cats. Also, there are some libraries that build higher-level abstractions on top of what cats offers.

7. FAQ

7.1. What Clojure types implement some of the Category Theory abstractions?

In contrast to other similar libraries in Clojure, cats doesn’t intend to extend Clojure types that don’t act like containers. For example, Clojure keywords are values but can not be containers so they should not extend any of the previously explained protocols.

Table 1. Summary of Clojure types and implemented protocols
NameImplemented protocols

sequence

Semigroup, Monoid, Functor, Applicative, Monad, MonadZero, MonadPlus

vector

Semigroup, Monoid, Functor, Applicative, Monad, MonadZero, MonadPlus

hash-set

Semigroup, Monoid, Functor, Applicative, Monad, MonadZero, MonadPlus

hash-map

Semigroup, Monoid

8. How to Contribute?

8.1. Philosophy

Five most important rules:

  • Beautiful is better than ugly.

  • Explicit is better than implicit.

  • Simple is better than complex.

  • Complex is better than complicated.

  • Readability counts.

All contributions to cats should keep these important rules in mind.

8.2. Procedure

cats does not have many restrictions for contributions. Just follow these steps depending on the situation:

Bugfix:

  • Fork the GitHub repo.

  • Fix a bug/typo on a new branch.

  • Make a pull-request to master.

New feature:

  • Open new issue with the new feature proposal.

  • If it is accepted, follow the same steps as "bugfix".

8.3. License

Copyright (c) 2014-2015 Andrey Antukh <niwi@niwi.be>
Copyright (c) 2014-2015 Alejandro Gómez <alejandro@dialelo.com>

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close