This post is about some very simple category theory constructions and how one can model them in C++.
First a type for binary products.
template <class A, class B> using product = std::pair<A, B>; //`fst (x, y)` is the projection `x` template <class A, class B> inline auto fst (product<A, B> const& p) { return p.first; } //`snd (x, y)` is the projection `y` template <class A, class B> inline auto snd (product<A, B> const& p) { return p.second; } //`mk_product (a, b) computes the product of `a` and `b` template <class A, class B> inline product<A, B> mk_product (A&& a, B&& b) { return std::make_pair (std::forward<A> (a), std::forward<B> (b)); }
Now $dup : X \rightarrow X \times X$ defined by $x \mapsto (x,\; x)$.
//`dup a` computes the product `(a, a)` template <class A> inline product<A, A> dup (A const& x) { return mk_product (x, x); }
Next up, $twist : X \times Y \rightarrow Y \times X$ defined by $(x,\; y) \mapsto (y,\; x)$.
//`twist (x, y)` is the product `(y, x)` template <class A, class B> inline product<B, A> twist (product<A, B> const& p) { return mk_product (snd (p), fst (p)); }
If $f : U \rightarrow R$ and $g : V \rightarrow S$ then we have $ravel : U \times V \rightarrow R \times S$ defined by $(x,\; y) \mapsto (f (x),\; g (y))$.
//`ravel f g (x, y)` is the product `(f x, g y)` (credit to Max //Skaller on the name) auto ravel = [=](auto f) { return [=](auto g) { return [=](auto const& x) { return mk_product (f (fst (x)), g (snd (x))); }; }; };
If $X \times Y$ is a (binary) product with projections $\pi_{x}$ and $\pi_{y}$, $Z$ an object, morphisms $f : Z \rightarrow X$, $g : Z \rightarrow Y$ then $\left\langle f, g \right\rangle : Z \rightarrow X \times Y$ is the mediating arrow $z \mapsto (f (z),\;g (z))$.
//The product of morphisms <`f`, `g`> (see //https://en.wikipedia.org/wiki/Product_(category_theory)) auto prod = [=](auto f, auto g) { return [=](auto const& z) { return mk_product (f (z), g (z)); }; };
We can use the Pretty good sum type library to define a type suitable for modeling (binary) coproducts.
template <class A> struct Left { A a; template <class U> explicit Left (U&& u) : a {std::forward<U> (u)} {} A const& value () const { return a; } }; template <class B> struct Right { B b; template <class U> explicit Right (U&& u) : b {std::forward<U> (u)} {} B const& value () const { return b; } }; template <class A, class B> using sum = pgs::sum_type<Left<A>, Right<B>>; template <class> struct sum_fst_type; template <class A, class B> struct sum_fst_type<sum<A, B>> { using type = A; }; template <class S> struct sum_snd_type; template <class A, class B> struct sum_snd_type<sum<A, B>> { using type = B; }; template <class S> using sum_fst_t = typename sum_fst_type<S>::type; template <class S> using sum_snd_t = typename sum_snd_type<S>::type;
If $X + Y$ is a (binary) sum with injections $i_{x}$ and $i_{y}$, $Z$ an object, morphisms $f : X \rightarrow Z$, $g : Y \rightarrow Z$ then $\left[ f, g \right] : X \times Y \rightarrow Z $ is the mediating arrow $\begin{align*} e \mapsto \begin{cases} f (e) & \text{if $e \in X$} \\ g (e) & \text{if $e \in Y$} \end{cases} \end{align*}$.
//The coproduct of morphisms [`f, `g`] (see //https://en.wikipedia.org/wiki/Coproduct) template <class S> auto co_product = [=](auto f) { return [=](auto g) { return [=](S const& s) { using A = sum_fst_t<S>; using B = sum_snd_t<S>; using lres_t = decltype (f (std::declval<A>())); using rres_t = decltype (g (std::declval<B>())); static_assert ( std::is_same<lres_t, rres_t>::value , "co_product : result types differ"); using res_t = lres_t; return s.match<lres_t>( [=](Left<A> const& l) { return f (l.value ()); }, [=](Right<B> const& r) { return g (r.value ()); } ); }; }; };
That's it. Here's some example usage of all this stuff.
auto succ=[](int i) { return i + 1; }; auto pred=[](int i) { return i - 1; }; auto twice=[](int i) { return 2 * i; }; auto square=[](int i) { return i * i; }; auto add=[](product<int, int> const& s) { return (fst (s) + snd (s)); }; //-- //Products //`dup` auto p = dup (1); std::cout << p << std::endl; //Prints '(1, 1)' //`prod` p = prod (succ, pred) (4); std::cout << p << std::endl; //Prints '(5, 3)' //`twist` p = twist (p); std::cout << p << std::endl; //Prints '(3, 5)' //`ravel` p = ravel (succ) (pred) (p); std::cout << p << std::endl; //Prints '(4, 4)' //-- //Sums sum<int, float> l{pgs::constructor<Left<int>>{}, 1}; sum<int, float> r{pgs::constructor<Right<float>>{}, 1.0f}; std::cout << co_product<sum<int, float>> ([=](int i) { return std::to_string(i); }) ([=](float f) { return std::to_string(f); }) (l) << std::endl; ;//Prints '1' std::cout << co_product<sum<int, float>> ([=](int i) { return std::to_string(i); }) ([=](float f) { return std::to_string(f); }) (r) << std::endl; ;//Prints '1.000000' //--