{-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} {- | Module : Control.SIArrow Description : Categories of reversible computations. Copyright : (c) Paweł Nowak License : MIT Maintainer : Paweł Nowak Stability : experimental Categories of reversible computations. -} module Control.SIArrow ( -- * Arrow. SIArrow(..), (^>>), (>>^), (^<<), (<<^), (#>>), (>>#), (#<<), (<<#), -- * Functor and applicative. (/$/), (/$~), (/*/), (/*), (*/), -- * Signaling errors. sifail, (/?/), -- * Combinators. sisequence, sisequence_, sireplicate, sireplicate_ ) where import Control.Arrow (Kleisli(..)) import Control.Category import Control.Category.Structures import Control.Lens.Cons import Control.Lens.Empty import Control.Lens.Iso import Control.Lens.SemiIso import Control.Monad import Data.Semigroupoid.Dual import Data.Tuple.Morph import Prelude hiding (id, (.)) infixr 1 ^>>, ^<<, #>>, #<< infixr 1 >>^, <<^, >>#, <<# infixl 4 /$/, /$~ infixl 5 /*/, */, /* infixl 3 /?/ -- | A category equipped with an embedding 'siarr' from @SemiIso@ into @cat@ and some -- additional structure. -- -- SIArrow abstracts categories of reversible computations -- (with reversible side effects). -- -- The category @cat@ should contain @SemiIso@ as a sort of -- \"subcategory of pure computations\". class (Products cat, Coproducts cat, CatPlus cat) => SIArrow cat where -- | Allows you to lift a SemiIso into @cat@. The resulting arrow should be -- in some sense minimal or \"pure\", similiar to 'pure', 'return' and -- 'arr' from "Control.Category". siarr :: ASemiIso' a b -> cat a b siarr = sipure . rev -- | Reversed version of 'siarr'. -- -- Use this where you would use 'pure'. sipure :: ASemiIso' b a -> cat a b sipure = siarr . rev -- | Allows a computation to depend on a its input value. -- -- I am not sure if this is the right way to get that ArrowApply or Monad -- like power. It seems quite easy to break the parser/pretty-printer inverse -- guarantee using this. On the other hand we have to be careful only when -- constructing the SemiIso using 'iso'/'semiIso' - and with an invalid SemiIso -- we could break everything anyway using 'siarr'. sibind :: ASemiIso a (cat a b) (cat a b) b -> cat a b -- | @sisome v@ repeats @v@ as long as possible, but no less then once. sisome :: cat () b -> cat () [b] sisome v = _Cons /$/ v /*/ simany v -- | @simany v@ repeats @v@ as long as possible. simany :: cat () b -> cat () [b] simany v = sisome v /+/ sipure _Empty {-# MINIMAL (siarr | sipure), sibind #-} instance MonadPlus m => SIArrow (Kleisli m) where siarr ai = Kleisli $ either fail return . apply ai sibind ai = Kleisli $ \a -> either fail (($ a) . runKleisli) $ apply ai a instance SIArrow cat => SIArrow (Dual cat) where siarr = Dual . sipure sibind ai = Dual $ sibind (iso id getDual . rev ai . iso getDual id) instance SIArrow ReifiedSemiIso' where siarr = reifySemiIso sibind ai = ReifiedSemiIso' $ semiIso (\a -> apply ai a >>= flip apply a . runSemiIso) (\b -> unapply ai b >>= flip unapply b . runSemiIso) -- | Composes a SemiIso with an arrow. (^>>) :: SIArrow cat => ASemiIso' a b -> cat b c -> cat a c f ^>> a = a . siarr f -- | Composes an arrow with a SemiIso. (>>^) :: SIArrow cat => cat a b -> ASemiIso' b c -> cat a c a >>^ f = siarr f . a -- | Composes a SemiIso with an arrow, backwards. (^<<) :: SIArrow cat => ASemiIso' b c -> cat a b -> cat a c f ^<< a = siarr f . a -- | Composes an arrow with a SemiIso, backwards. (<<^) :: SIArrow cat => cat b c -> ASemiIso' a b -> cat a c a <<^ f = a . siarr f -- | Composes a reversed SemiIso with an arrow. (#>>) :: SIArrow cat => ASemiIso' b a -> cat b c -> cat a c f #>> a = a . sipure f -- | Composes an arrow with a reversed SemiIso. (>>#) :: SIArrow cat => cat a b -> ASemiIso' c b -> cat a c a >># f = sipure f . a -- | Composes a reversed SemiIso with an arrow, backwards. (#<<) :: SIArrow cat => ASemiIso' c b -> cat a b -> cat a c f #<< a = sipure f . a -- | Composes an arrow with a reversed SemiIso, backwards. (<<#) :: SIArrow cat => cat b c -> ASemiIso' b a -> cat a c a <<# f = a . sipure f -- | Postcomposes an arrow with a reversed SemiIso. -- The analogue of '<$>' and synonym for '#<<'. (/$/) :: SIArrow cat => ASemiIso' b' b -> cat a b -> cat a b' (/$/) = (#<<) -- | Convenient fmap. -- -- > ai /$~ f = ai . morphed /$/ f -- -- This operator handles all the hairy stuff with uncurried application: -- it reassociates the argument tuple and removes unnecessary (or adds necessary) -- units to match the function type. You don't have to use @/*@ and @*/@ with this -- operator. (/$~) :: (SIArrow cat, HFoldable b', HFoldable b, HUnfoldable b', HUnfoldable b, Rep b' ~ Rep b) => ASemiIso' a b' -> cat c b -> cat c a ai /$~ h = cloneSemiIso ai . morphed /$/ h -- | The product of two arrows with duplicate units removed. Side effect are -- sequenced from left to right. -- -- The uncurried analogue of '<*>'. (/*/) :: SIArrow cat => cat () b -> cat () c -> cat () (b, c) a /*/ b = unit ^>> (a *** b) -- | The product of two arrows, where the second one has no input and no output -- (but can have side effects), with duplicate units removed. Side effect are -- sequenced from left to right. -- -- The uncurried analogue of '<*'. (/*) :: SIArrow cat => cat () a -> cat () () -> cat () a f /* g = unit /$/ f /*/ g -- | The product of two arrows, where the first one has no input and no output -- (but can have side effects), with duplicate units removed. Side effect are -- sequenced from left to right. -- -- The uncurried analogue of '*>'. (*/) :: SIArrow cat => cat () () -> cat () a -> cat () a f */ g = unit . swapped /$/ f /*/ g -- | An arrow that fails with an error message. sifail :: SIArrow cat => String -> cat a b sifail = siarr . alwaysFailing -- | Provides an error message in the case of failure. (/?/) :: SIArrow cat => cat a b -> String -> cat a b f /?/ msg = f /+/ sifail msg -- | Equivalent of 'sequence'. sisequence :: SIArrow cat => [cat () a] -> cat () [a] sisequence [] = sipure _Empty sisequence (x:xs) = _Cons /$/ x /*/ sisequence xs -- | Equivalent of 'sequence_', restricted to units. sisequence_ :: SIArrow cat => [cat () ()] -> cat () () sisequence_ [] = sipure _Empty sisequence_ (x:xs) = unit /$/ x /*/ sisequence_ xs -- | Equivalent of 'replicateM'. sireplicate :: SIArrow cat => Int -> cat () a -> cat () [a] sireplicate n f = sisequence (replicate n f) -- | Equivalent of 'replicateM_', restricted to units. sireplicate_ :: SIArrow cat => Int -> cat () () -> cat () () sireplicate_ n f = sisequence_ (replicate n f)