Pass it to the right...
Skew-monoidal category normalization
Introduction
While trying to normalize arrow expression produced by a custom quasiquoter I asked around (thanks edwardk) and was pointed toward Coherence for Skew-Monoidal Categories. I do not have a rigorous background in category theory and notation, so I took this as the chance to implement and learn. You can follow along with the paper as I tried to keep as close as possible to its order of presenetation.
Skew
Standard monoidal categories include many functions that are balanced in that when there is a bifunctor (a type like (a,b) or Either a b) there is no preference for one or the other component of it. This lack of preference leads to some ambiguity and inability to define a clear path to a normal form. There are multiple ways to create to any form and multiple ways to get to them. So to resolve this Tarmo Uustalu, the author, picks a preferred direction.
Setup
At first I started with trying to defined a class SkewMonoidal, but it turns out to be not needed yet. Though I believe it would be helpful to make this a usable normalization procedure. Instead let's start on pg. 3 section 3.
First the author defines a free symetric monoidal category with Objects from a set Var.

data Tm = X' Var | I | Tm :-: TmThere is also a map between two objects by a set of rule expressions. This tells us that we will build this datatype from primitive rules and some combinators:

data Rule = IdRule | Dot Rule Rule | Cross Rule Rule | La | Rh | Asc deriving Show
-- | Interpretation of rules and their composition.
evalRule :: Rule -> Tm -> Tm
evalRule IdRule = id
evalRule (Dot a b) = evalRule a . evalRule b
evalRule (Cross a b) = \(c :-: d) -> evalRule a c :-: evalRule b d
evalRule La = \(I :-: a) -> a
evalRule Rh = \a -> a :-: I
evalRule Asc = \case
((a :-: b) :-: c) -> (a :-: (b :-: c))
a -> error $ show a
-- These lambdas are NOT total! Bad parens later on caused some errors.Normal forms.
Next we define a normal form to be isomorphic to a list of Terms:

data Nf = J | Var :.: Nf deriving ShowNext we write a function to move from a the normal form back to the original Terms:

emb :: Nf -> Tm
emb J = I
emb (a :.: n) = X' a :-: emb nWe will need a helper function which walks along a structure of terms and folds it into a Nf. I could not figure out a way to get outfix operators in Haskell, so I callted it splay.

splay :: Tm -> Nf -> Nf
splay (X' x) n = x :.: n
splay I n = n
splay (a :-: b) n = splay a (splay b n)
-- | Every object expression is assigned a normal form using the helper.
nf :: Tm -> Nf
nf a = splay a JHome Stretch
By starting with Identity in Nf, the nf function creates the normal form for the expression via manually unpacking the structure of the expression. Next we define a new operator. Don't ask me how to create this from scratch, I merely translated:

splat :: Tm -> Nf -> Rule
splat (X' _) _ = IdRule
splat I _ = La
splat (a :-: b) n = splat a (splay b n)
`Dot` (IdRule `Cross` splat b n `Dot` Asc)That is the helper function which creates the rule expression. It is used in the normalizing map expression function defined next; nm. The J and Rh are to allow splat to do its job, but then to remove it.

nm :: Tm -> Rule
nm a = splat a J `Dot` RhThe idea is that nm and nf should produce the same normal form. This is proved by the author elsewhere. 5 laws are required to be satisfied by the skew-monoidal category, so I should write those down in a nice Haskell format soon. With this, we are done! The nm function will produce a rule expression which can be evaluated with evalRule along with a Term to produce a new Term.
Testing
Let's give it a shot
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeOperators #-}
import Prelude hiding (id,(.))
import Control.Category
-- show
s,t,u,v,w :: Tm
s = X' A
t = X' A :-: I :-: X' B
u = I :-: I :-: (X' A :-: I) :-: X' B
v = I :-: u
w = t :-: t
main :: IO ()
main = do
printTest s
printTest t
printTest u
printTest v
printTest w
-- /show
printTest :: Tm -> IO ()
printTest a = do
let b = nf a
c = nm a
d = evalRule c a
putStrLn $ "Original Object: " ++ show a
putStrLn $ "Normal Form : " ++ show b
putStrLn $ "Rewrite Rules : " ++ if length (show c) > 70 then take 65 (show c) ++ " ..." else show c
putStrLn $ "Rewriten Object: " ++ show d
putStrLn ""
-- A Skew-monoidal category is a category `k` together with a
-- distinguished object `Id`, a functor `bimap`, and three
-- natural transformations, `lambda`, rho, and disassociate.
-- It is half of an Associative, Monoidal, Cartesian
-- | Objects of free symetric monoidal category with Objects from Var
data Var = A | B | C deriving Show
data Tm = I | X' Var | Tm :-: Tm deriving Show
-- | Maps between two objects.
data Rule = IdRule | Dot Rule Rule | Cross Rule Rule | La | Rh | Asc deriving Show
-- | Interpretation of rules and their composition.
evalRule :: Rule -> Tm -> Tm
evalRule IdRule = id
evalRule (Dot a b) = evalRule a . evalRule b
evalRule (Cross a b) = \(c :-: d) -> evalRule a c :-: evalRule b d
evalRule La = \(I :-: a) -> a
evalRule Rh = \a -> a :-: I
evalRule Asc = \case
((a :-: b) :-: c) -> (a :-: (b :-: c))
a -> error $ show a
-- Do we need to encode all the rules on page 4? Or are those just expected laws?
-- | We define "normal forms" of object expression as Nf
data Nf = J | Var :.: Nf deriving Show
-- | Normal forms embed into object expressions.
emb :: Nf -> Tm
emb J = I
emb (a :.: n) = X' a :-: emb n
-- | Let ||-|| be a function ... i'm calling it splay
splay :: Tm -> Nf -> Nf
splay (X' x) n = x :.: n
splay I n = n
splay (a :-: b) n = splay a (splay b n)
-- | Every object expression is assigned a normal form.
nf :: Tm -> Nf
nf a = splay a J
-- | In Lemma 3, Let now `<<->>` ... i'm calling it splat
splat :: Tm -> Nf -> Rule
splat (X' _) _ = IdRule
splat I _ = La
splat (a :-: b) n = splat a (splay b n)
`Dot` (IdRule `Cross` splat b n `Dot` Asc)
-- | "Normalizing" map expression.
nm :: Tm -> Rule
nm a = splat a J `Dot` RhWe can see that the normal form and rewritten form match. We got this result by applying our rewrite rules, the skewed portions of the monoidal category.
Ending note
This was a bit of pleasant translation. Is this construct useful, should I include it in Hackage? Any corrections comments or improvements are very welcome.
Entire code:
{-# START_FILE Skew.hs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeOperators #-}
{- |
Module : Control.Category.Monoidal.Skew
Description : skew-monoidal
Copyright : (c) 2015 Thomas Bereknyei
License : BSD3
Maintainer : Thomas Bereknyei <[email protected]>
Stability : unstable
Portability : GADTs,LambdaCase
Implements normalization from: <http://arxiv.org/pdf/1406.2064v1.pdf>
The following is almost directly pulled out from the paper.
This served as a good exercise in translating category theory
notation into Haskell code. I welcome any corrections or
suggestions for improvement.
-}
module Skew where
import Prelude hiding (id,(.))
import Control.Category
-- A Skew-monoidal category is a category `k` together with a
-- distinguished object `Id`, a functor `bimap`, and three
-- natural transformations, `lambda`, rho, and disassociate.
-- It is half of an Associative, Monoidal, Cartesian
-- | Objects of free symetric monoidal category with Objects from Var
data Var = A | B | C deriving Show
data Tm = I | X' Var | Tm :-: Tm deriving Show
-- | Maps between two objects.
data Rule = IdRule | Dot Rule Rule | Cross Rule Rule | La | Rh | Asc deriving Show
-- | Interpretation of rules and their composition.
evalRule :: Rule -> Tm -> Tm
evalRule IdRule = id
evalRule (Dot a b) = evalRule a . evalRule b
evalRule (Cross a b) = \(c :-: d) -> evalRule a c :-: evalRule b d
evalRule La = \(I :-: a) -> a
evalRule Rh = \a -> a :-: I
evalRule Asc = \case
((a :-: b) :-: c) -> (a :-: (b :-: c))
a -> error $ show a
-- Do we need to encode all the rules on page 4? Or are those just expected laws?
-- | We define "normal forms" of object expression as Nf
data Nf = J | Var :.: Nf deriving Show
-- | Normal forms embed into object expressions.
emb :: Nf -> Tm
emb J = I
emb (a :.: n) = X' a :-: emb n
-- | Let ||-|| be a function ... i'm calling it splay
splay :: Tm -> Nf -> Nf
splay (X' x) n = x :.: n
splay I n = n
splay (a :-: b) n = splay a (splay b n)
-- | Every object expression is assigned a normal form.
nf :: Tm -> Nf
nf a = splay a J
-- | In Lemma 3, Let now `<<->>` ... i'm calling it splat
splat :: Tm -> Nf -> Rule
splat (X' _) _ = IdRule
splat I _ = La
splat (a :-: b) n = splat a (splay b n)
`Dot` (IdRule `Cross` splat b n `Dot` Asc)
-- | "Normalizing" map expression.
nm :: Tm -> Rule
nm a = splat a J `Dot` Rh
{- Would this be useful? Can translate into our Tm as an intermediate, normalize, translate back?
import Control.Categorical.Bifunctor
class Bifunctor p k k k => SkewMonoidal (k :: * -> * -> *) (p :: * -> * -> *) where
type Id (k :: * -> * -> *) (p :: * -> * -> *) :: *
lam :: (Id k p `p` a) `k` a
rho :: a `k` (a `p` Id k p)
dis :: ((a `p` b) `p` c) `k` (a `p` (b `p` c))
-- Free Skew-monoidal Category includes embedding for
-- normalization.
data SkewF f where
Id :: SkewF f
XXX :: f -> f -> SkewF f
Lam :: f -> SkewF f
Rho :: f -> SkewF f
Dis :: f -> SkewF f
Lift :: Var -> SkewF f
deriving Show
newtype Fix f = Fix (f (Fix f))
type Skew = Fix SkewF
-- Smart Constructors
i :: Skew
i = Fix Id
x :: (f -> SkewF Skew) -> f -> Skew
x a b = Fix $ a b
a *.* b = Fix (a `XXX` b)
l,r,d :: Skew -> Skew
l = x Lam
r = x Rho
d = x Dis
lift :: Var -> Skew
lift = Fix . Lift
instance (Show (f (Fix f))) => Show (Fix f) where
showsPrec p (Fix x) = showParen (p >= 11) (showString "Fix " . showsPrec 11 x)
---}