\documentclass{article} \usepackage{fge} \usepackage{mathabx} \usepackage{hyperref} \usepackage{marvosym} %include polycode.fmt \begin{document} \title{Fly Like an Arrow} \author{Jonathan Fischoff \hskip 2pt \Letter \hskip 4pt \} \maketitle The great thing about {\tt Arrows} is you can write code that works for morphisms in different categories. For example, you can write code for functions and later use monad actions, or Kleisli arrows, instead. This is useful for error handling, and of course, adding {\tt IO}. If the underlying category uses isomorphisms (things with inverses) exclusively then it is called a {\sl groupoid}. Groupoids cause cracks to show in the {\tt Arrow} abstraction. {\tt Arrow} assumes that you can lift any function into the category you are writing code for, by requiring a definition for @arr :: (b -> c) -> a b c@ function. This is out for groupoids, because not all functions are isomorphisms. To remedy this, among other issues, Adam Megacz came up with. \href{http://arxiv.org/pdf/1007.2885v2.pdf}{Generalized Arrows.}. In \href{https://www.cs.indiana.edu/~rpjames/papers/rc.pdf}{\sl Dagger Traced Symmetric Monoidal Categories and Reversible Programming} the authors show how to construct an reversible language out of the sum and product types along with related combinators to form a commutative semiring, at the type level. Both approaches are similar. Error handling and \href{http://www.informatik.uni-marburg.de/~rendel/rendel10invertible.pdf}{\sl partial isomorphisms} are possible with Generalized Arrows. However, I find the algebraic approach of {\sl DTSMCRP} more elegant. So I am going to try to get the same combinators as {\sl DTSMCRP} but for an arbitrary category, as I would with Generalized Arrows. This is a Literate Haskell file, which means it can be executed as Haskell code. First, I need to start with a simple Haskell preamble. \begin{code} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} -- |Categorical semirings (my term, but maybe the correct one) are an alternative to Arrows, but play nicely with groupoids. | -- See the source or the latex source for more background. module Data.Semiring ( -- * Endofunctors for construction Ctor(..) -- ** first/right like functions , promote , swap_promote -- * Laws (Axioms) for building algebraic structures , Absorbs(..) , Assocative(..) , Commutative(..) , Annihilates(..) , Distributes(..) -- * Categorical Algebraic Structures , Monoidial , CommutativeMonoidial , Semiring -- * Arrow like functions for semiring categories , first , second , left , right -- * A groupoid class that is a category. Maybe this is a bad idea? , Groupoid(..) , Iso(..) -- * Alegraic laws as isomorphism for groupoid instances , biject_sum_absorb , biject_sum_assoc , biject_product_absorb , biject_product_assoc , biject_distributes , kbiject_sum_absorb , kbiject_sum_assoc , kbiject_product_absorb , kbiject_product_assoc , kbiject_distributes ) where import Prelude hiding ((.), id) import Control.Category ((.), id, Category(..)) import Data.Void(Void) import Control.Arrow (Kleisli(..)) import Generics.Pointless.MonadCombinators (mfuse) import Control.Monad (liftM) import Control.Newtype \end{code} \newpage \noindent I start with an abstraction for both sum and product constructors. \begin{code} -- |An endofunctor for combining two morphisms| class Category k => Ctor k constr | constr -> k where selfmap :: k a b -> k c d -> k (constr a c) (constr b d) \end{code} \noindent With Ctor I can write a generic {\tt first} or {\tt left} \begin{code} -- |construct a new morphism with identity| promote :: Ctor k op => k a b -> k (op a c) (op b c) promote = flip selfmap id -- |construct a new morphism with identity with the arguments reversed| swap_promote :: Ctor k op => k a b -> k (op c a) (op c b) swap_promote = selfmap id \end{code} It is probably not clear at this point but depending on the type of {\sl op} we can get either the {\tt Arrow} $\ast\ast\ast$ or the {\tt ArrowChoice} $\interleave$ function. If we make a semiring we can get them both. That's what we are going to do. I use type classes to encode the algebraic laws of semirings, with a class per law. %format \<-\> = "\leftrightarrow " \vskip 5pt \begin{code} -- |The absorbtion law => x+0 \<-\> x | class Ctor k op => Absorbs k op id | op -> k, op -> id where absorb :: k (op id a) a unabsorb :: k a (op id a) \end{code} \begin{code} -- |The commutative law => x + y \<-\> y + x| class Ctor k op => Commutative k op | op -> k where commute :: k (op a b) (op b a) \end{code} \begin{code} -- |The assocative law => (x + y) + z \<-\> x + (y + z)| class Ctor k op => Assocative k op | op -> k where assoc :: k (op (op a b) c) (op a (op b c)) unassoc :: k (op a (op b c)) (op (op a b) c) \end{code} \begin{code} -- |The annihilation law => 0 * x \<-\> 0| class Ctor k op => Annihilates k op zero | op zero -> k where annihilates :: k (op zero a) zero \end{code} \begin{code} -- |The distribution law => (a + b) * c \<-\> (a * c) + (b * c)| class (Ctor k add, Ctor k multi) => Distributes k add multi | add multi -> k where distribute :: k (multi (add a b) c) (add (multi a c) (multi b c)) undistribute :: k (add (multi a c) (multi b c)) (multi (add a b) c) \end{code} I collect these into groups of laws to make different algebraic structures. \begin{code} -- |Monoidial Category class| class (Assocative k dot, Absorbs k dot id) => Monoidial k dot id | dot id -> k where \end{code} \begin{code} -- |Commutative Monoidial Category class| class (Monoidial k dot id, Commutative k dot) => CommutativeMonoidial k dot id | dot id -> k where \end{code} \begin{code} -- |Semiring Category class| class (CommutativeMonoidial k add zero, CommutativeMonoidial k multi one, Annihilates k multi zero, Distributes k add multi) => Semiring k add zero multi one | add zero multi one -> k where \end{code} From which I regain {\tt Arrow} and {\tt ArrowChoice} functionality. Although, because of {\tt promote}, I already had this capability. \begin{code} -- |Apply the multi monoid operator to the morphism and identity| first :: Semiring a add zero multi one => a b c -> a (multi b d) (multi c d) first = promote -- |Apply the multi monoid operator to identity and the morphism| second :: Semiring a add zero multi one => a b c -> a (multi d b) (multi d c) second = swap_promote -- |Apply the add monoid operator to the morphism and identity| left :: Semiring a add zero multi one => a b c -> a (add b d) (add c d) left = promote -- |Apply the add monoid operator to identity and the morphism| right :: Semiring a add zero multi one => a b c -> a (add d b) (add d c) right = swap_promote \end{code} Many of the Generic Arrow functions can be included through absorption ({\tt cancel}, {\tt uncancel}) and commutativity ({\tt swap}). I'm not interested in adding looping at this point. This also makes clear the relationship between {\tt Arrow} and {\tt ArrowChoice} as has been noted else where. Basically the same thing with a different endofunctor or constructor ({\tt Arrow} uses product types, {\tt ArrowChoice} uses sum types) as the monoid operator of a type level commutative monoid. Making instances is a little onerous because of the use of multiparameter classes and the functional dependencies I have chosen. When I begin actually using these classes, it could result in massive changes. I am open to any suggestions on better designs. \newpage The rest of the code is basically boilerplate. I used Djinn to write some of the functions (hopefully they work :)). \section{Small Category Instances} \subsection{Function Instances} \subsubsection{Sum Commutative Monoid Instances} %format Sum = "\bigplus " %format Zero = "\MVZero " \begin{code} --Sugar type Sum = Either type Zero = Void --Instances instance Ctor (->) Sum where selfmap f g = either (Left . f) (Right . g) instance Absorbs (->) Sum Zero where absorb (Right x) = x absorb _ = error "Absorbs,->,Sum,Zero Semiring.lhs absorb:impossible!" unabsorb = Right instance Assocative (->) Sum where assoc = either (either Left (Right . Left)) (Right . Right) unassoc = either (Left . Left) (either (Left . Right) Right) instance Monoidial (->) Sum Zero where instance Commutative (->) Sum where commute = either Right Left instance CommutativeMonoidial (->) Sum Zero where \end{code} \subsubsection{Product Commutative Monoid Instances} %format Product = "\ast " %format One = "\MVOne " \begin{code} type Product = (,) type One = () --Instances instance Ctor (->) Product where selfmap f g (x, y) = (f x, g y) instance Absorbs (->) Product One where absorb ((), x) = x unabsorb x = ((), x) instance Assocative (->) Product where assoc ((x, y), z) = (x, (y, z)) unassoc (x, (y, z)) = ((x, y), z) instance Monoidial (->) Product One where instance Commutative (->) Product where commute (x, y) = (y, x) instance CommutativeMonoidial (->) Product One where \end{code} \subsubsection{Function Semiring Instance} \begin{code} instance Annihilates (->) Product Zero where annihilates (_, _) = undefined instance Distributes (->) Sum Product where distribute (Left x, z) = Left (x, z) distribute (Right y, z) = Right (y, z) undistribute (Left (x, z)) = (Left x, z) undistribute (Right (y, z)) = (Right y, z) instance Semiring (->) Sum Zero Product One where \end{code} \subsection{Kleisli Instances} The functional dependencies of the classes require alternate versions of the sum and product types used for \to instances. %format KSum = "\bigplus " %format KZero = "\MVZero " \subsection{Sum Commutative Monoid Instances} \begin{code} data KSum a b = KLeft a | KRight b newtype KZero = KZ Void --Instances instance Monad m => Ctor (Kleisli m) KSum where selfmap (Kleisli f) (Kleisli g) = Kleisli $ \e -> case e of KLeft x -> KLeft `liftM` f x KRight x -> KRight `liftM` g x instance Monad m => Absorbs (Kleisli m) KSum KZero where absorb = Kleisli $ \e -> case e of KRight x -> return x _ -> error "Absorbs,Kleisli,KSum,KZero Semiring.lhs absorb:impossible!" unabsorb = Kleisli $ \x -> return $ KRight x instance Monad m => Assocative (Kleisli m) KSum where assoc = Kleisli $ \e -> case e of KLeft x -> case x of KLeft y -> return $ KLeft y KRight y -> return $ KRight $ KLeft y KRight x -> return $ KRight $ KRight x unassoc = Kleisli $ \e -> case e of KLeft x -> return $ KLeft $ KLeft x KRight x -> case x of KLeft y -> return $ KLeft $ KRight y KRight y -> return $ KRight y instance Monad m => Monoidial (Kleisli m) KSum KZero where instance Monad m => Commutative (Kleisli m) KSum where commute = Kleisli $ \e -> case e of KLeft x -> return $ KRight x KRight x -> return $ KLeft x instance Monad m => CommutativeMonoidial (Kleisli m) KSum KZero where \end{code} %format KProduct = "\ast " %format KOne = "\MVOne " \subsubsection{Product Commutative Monoid Instances} \begin{code} data KProduct a b = KP a b newtype KOne = KO () --Instances instance Monad m => Ctor (Kleisli m) KProduct where selfmap (Kleisli f) (Kleisli g) = Kleisli $ \(KP x y) -> uncurry KP `liftM` mfuse (f x, g y) instance Monad m => Absorbs (Kleisli m) KProduct KOne where absorb = Kleisli $ \(KP (KO ()) x) -> return x unabsorb = Kleisli $ \x -> return $ KP (KO ()) x instance Monad m => Assocative (Kleisli m) KProduct where assoc = Kleisli $ \(KP (KP x y) z) -> return $ KP x (KP y z) unassoc = Kleisli $ \(KP x (KP y z)) -> return $ KP (KP x y) z instance Monad m => Monoidial (Kleisli m) KProduct KOne where instance Monad m => Commutative (Kleisli m) KProduct where commute = Kleisli $ \(KP x y) -> return $ KP y x instance Monad m => CommutativeMonoidial (Kleisli m) KProduct KOne where \end{code} \subsubsection{Function Semiring Instance} \begin{code} instance Monad m => Annihilates (Kleisli m) KProduct KZero where annihilates = Kleisli $ \(KP _ _) -> return undefined instance Monad m => Distributes (Kleisli m) KSum KProduct where distribute = Kleisli $ \e -> case e of KP (KLeft x) z -> return $ KLeft $ KP x z KP (KRight y) z -> return $ KRight $ KP y z undistribute = Kleisli $ \e -> case e of KLeft (KP x z) -> return $ KP (KLeft x) z KRight (KP x z) -> return $ KP (KRight x) z instance Monad m => Semiring (Kleisli m) KSum KZero KProduct KOne where \end{code} \section{Groupoid Class} \begin{code} class (Category g) => Groupoid g where inv :: g a b -> g b a \end{code} \section{Groupoid Instances} \begin{code} data Iso k a b = Iso { embed :: k a b, project :: k b a } instance (Category k) => Category (Iso k) where id = Iso id id (Iso f g) . (Iso h i) = Iso (f . h) (i . g) instance Newtype (Iso k a b) (k a b, k b a) where pack (f, g) = Iso f g unpack (Iso f g) = (f, g) instance (Category k) => Groupoid (Iso k) where inv (Iso f g) = Iso g f \end{code} \subsection{Helper Code} \begin{code} type Biject = Iso (->) type KBiject m = Iso (Kleisli m) (<->) :: k a b -> k b a -> Iso k a b (<->) = Iso \end{code} \subsection{Groupoid Semirings Instances} \subsection{Groupoid with a Function as the base category} \subsubsection{Sum Commutative Monoid Instances} %format BSum = "\bigplus " %format BZero = "\MVZero " \begin{code} data BSum a b = BLeft a | BRight b newtype BZero = BZ Void instance Ctor Biject BSum where selfmap f g = fw <-> bk where fw (BLeft x) = BLeft $ embed f x fw (BRight x) = BRight $ embed g x bk (BLeft x) = BLeft $ project f x bk (BRight x) = BRight $ project g x instance Absorbs Biject BSum BZero where absorb = biject_sum_absorb unabsorb = inv biject_sum_absorb biject_sum_absorb :: Biject (BSum BZero a) a biject_sum_absorb = fw <-> bk where fw (BRight x) = x fw _ = error "biject_sum_absorb fw: impossible" bk = BRight instance Assocative Biject BSum where assoc = biject_sum_assoc unassoc = inv biject_sum_assoc biject_sum_assoc :: Biject (BSum (BSum a b) c) (BSum a (BSum b c)) biject_sum_assoc = fw <-> bk where fw (BLeft (BLeft x)) = BLeft x fw (BLeft (BRight x)) = BRight $ BLeft x fw (BRight x) = BRight $ BRight x bk (BLeft x) = BLeft (BLeft x) bk (BRight (BLeft x)) = BLeft (BRight x) bk (BRight (BRight x)) = BRight x instance Monoidial Biject BSum BZero where instance Commutative Biject BSum where commute = fw <-> bk where fw (BRight x) = BLeft x fw (BLeft x) = BRight x bk (BRight x) = BLeft x bk (BLeft x) = BRight x instance CommutativeMonoidial Biject BSum BZero where \end{code} \subsubsection{Product Commutative Monoid Instances} %format BProduct = "\ast " %format BOne = "\MVOne " \begin{code} data BProduct a b = BP a b newtype BOne = BO () --Instances instance Ctor Biject BProduct where selfmap (Iso f_fw f_bk ) (Iso g_fw g_bk) = Iso (\(BP x y) -> BP (f_fw x) (g_fw y)) (\(BP x y) -> BP (f_bk x) (g_bk y)) instance Absorbs Biject BProduct BOne where absorb = biject_product_absorb unabsorb = inv biject_product_absorb biject_product_absorb :: Biject (BProduct BOne a) a biject_product_absorb = fw <-> bk where fw (BP (BO ()) x) = x bk x = BP (BO ()) x instance Assocative Biject BProduct where assoc = biject_product_assoc unassoc = inv biject_product_assoc biject_product_assoc :: Biject (BProduct (BProduct a b) c) (BProduct a (BProduct b c)) biject_product_assoc = fw <-> bk where fw (BP (BP x y) z) = BP x (BP y z) bk (BP x (BP y z)) = BP (BP x y) z instance Monoidial Biject BProduct BOne where instance Commutative Biject BProduct where commute = (\(BP x y) -> BP y x) <-> (\(BP x y) -> BP y x) instance CommutativeMonoidial Biject BProduct BOne where \end{code} \subsubsection{Semiring Instance} \begin{code} instance Annihilates Biject BProduct BZero where annihilates = (\(BP _ _) -> undefined) <-> (`BP` undefined) instance Distributes Biject BSum BProduct where distribute = biject_distributes undistribute = inv biject_distributes biject_distributes :: Biject (BProduct (BSum a b) c) (BSum (BProduct a c) (BProduct b c)) biject_distributes = fw <-> bk where fw (BP (BLeft x) z) = BLeft (BP x z) fw (BP (BRight y) z) = BRight (BP y z) bk (BLeft (BP x z)) = BP (BLeft x) z bk (BRight (BP y z)) = BP (BRight y) z instance Semiring Biject BSum BZero BProduct BOne where \end{code} \subsection{Groupoid with a Klesli arrow as the base category} \subsubsection{Sum Commutative Monoid Instances} %format KBSum = "\bigplus " %format KBZero = "\MVZero " \begin{code} data KBSum a b = KBLeft a | KBRight b newtype KBZero = KBZ Void --Instances instance Monad m => Ctor (KBiject m) KBSum where selfmap f g = fw <-> bk where fw = Kleisli $ run_pair (embed f) (embed g) bk = Kleisli $ run_pair (project f) (project g) run_pair t _ (KBLeft x) = KBLeft `liftM` runKleisli t x run_pair _ u (KBRight x) = KBRight `liftM` runKleisli u x instance Monad m => Absorbs (KBiject m) KBSum KBZero where absorb = kbiject_sum_absorb unabsorb = inv kbiject_sum_absorb kbiject_sum_absorb :: Monad m => (KBiject m) (KBSum KBZero a) a kbiject_sum_absorb = fw <-> bk where fw = Kleisli $ \e -> case e of KBRight x -> return x _ -> error "kbiject_sum_absorb fw: impossible" bk = Kleisli $ \x -> return $ KBRight x instance Monad m => Assocative (KBiject m) KBSum where assoc = kbiject_sum_assoc unassoc = inv kbiject_sum_assoc kbiject_sum_assoc :: Monad m => (KBiject m) (KBSum (KBSum a b) c) (KBSum a (KBSum b c)) kbiject_sum_assoc = fw <-> bk where fw = Kleisli $ \e -> case e of KBLeft x -> case x of KBLeft y -> return $ KBLeft y KBRight y -> return $ KBRight $ KBLeft y KBRight x -> return $ KBRight $ KBRight x bk = Kleisli $ \e -> case e of KBLeft x -> return $ KBLeft $ KBLeft x KBRight x -> case x of KBLeft y -> return $ KBLeft $ KBRight y KBRight y -> return $ KBRight y instance Monad m => Monoidial (KBiject m) KBSum KBZero where instance Monad m => Commutative (KBiject m) KBSum where commute = fw <-> fw where fw = Kleisli $ \e -> case e of KBLeft x -> return $ KBRight x KBRight x -> return $ KBLeft x instance (Monad m) => CommutativeMonoidial (KBiject m) KBSum KBZero where \end{code} \subsubsection{Product Commutative Monoid Instances} %format KBProduct = "\ast " %format KBOne = "\MVOne " \begin{code} data KBProduct a b = KBP a b newtype KBOne = KBO () --Instances instance Monad m => Ctor (KBiject m) KBProduct where selfmap f g = fw <-> bk where fw = Kleisli $ run_pair (embed f) (embed g) bk = Kleisli $ run_pair (project f) (project g) run_pair t u (KBP x y) = uncurry KBP `liftM` mfuse (runKleisli t x, runKleisli u y) instance Monad m => Absorbs (KBiject m) KBProduct KBOne where absorb = kbiject_product_absorb unabsorb = inv kbiject_product_absorb kbiject_product_absorb :: Monad m => (KBiject m) (KBProduct KBOne a) a kbiject_product_absorb = fw <-> bk where fw = Kleisli $ \(KBP (KBO ()) x) -> return x bk = Kleisli $ \x -> return $ KBP (KBO ()) x instance Monad m => Assocative (KBiject m) KBProduct where assoc = kbiject_product_assoc unassoc = inv kbiject_product_assoc kbiject_product_assoc :: Monad m => (KBiject m) (KBProduct (KBProduct a b) c) (KBProduct a (KBProduct b c)) kbiject_product_assoc = fw <-> bk where fw = Kleisli $ \(KBP (KBP f g) h) -> return $ KBP f (KBP g h) bk = Kleisli $ \(KBP f (KBP g h)) -> return $ KBP (KBP f g) h instance Monad m => Monoidial (KBiject m) KBProduct KBOne where instance Monad m => Commutative (KBiject m) KBProduct where commute = fw <-> fw where fw = Kleisli $ \(KBP x y) -> return $ KBP y x instance (Monad m) => CommutativeMonoidial (KBiject m) KBProduct KBOne where \end{code} \subsubsection{Semiring Instance} \begin{code} instance (Monad m) => Annihilates (KBiject m) KBProduct KBZero where annihilates = fw <-> bk where fw = Kleisli $ \(KBP _ _) -> return undefined bk = Kleisli $ \x -> return $ KBP x undefined instance (Monad m) => Distributes (KBiject m) KBSum KBProduct where distribute = kbiject_distributes undistribute = inv kbiject_distributes kbiject_distributes :: Monad m => (KBiject m) (KBProduct (KBSum a b) c) (KBSum (KBProduct a c) (KBProduct b c)) kbiject_distributes = fw <-> bk where fw = Kleisli $ \e -> case e of KBP (KBLeft x) z -> return $ KBLeft (KBP x z) KBP (KBRight y) z -> return $ KBRight (KBP y z) bk = Kleisli $ \e -> case e of KBLeft (KBP x z) -> return $ KBP (KBLeft x) z KBRight (KBP y z) -> return $ KBP (KBRight y) z instance Monad m => Semiring (KBiject m) KBSum KBZero KBProduct KBOne where \end{code} \end{document}