{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DeriveFunctor #-}

module Data.Piso (

  -- * Partial isomorphisms
  Piso(..), forward, backward,
  FromPiso(..),
  (:-)(..)

  ) where


import Prelude hiding (id, (.))

import Control.Monad ((>=>))
import Control.Category (Category(..))


-- | Bidirectional isomorphism that is partial in the backward direction.
--
-- This can be used to express constructor-deconstructor pairs. For example:
--
-- > nil :: Piso t ([a] :- t)
-- > nil = Piso f g
-- >   where
-- >     f        t  = [] :- t
-- >     g ([] :- t) = Just t
-- >     g _         = Nothing
-- >
-- > cons :: Piso (a :- [a] :- t) ([a] :- t)
-- > cons = Piso f g
-- >   where
-- >     f (x :- xs  :- t) = (x : xs) :- t
-- >     g ((x : xs) :- t) = Just (x :- xs :- t)
-- >     g _               = Nothing
--
-- Here ':-' can be read as \'cons\', forming a stack of values. For example,
-- @nil@ pushes @[]@ onto the stack; or, in the backward direction, tries to
-- remove @[]@ from the stack. Representing constructor-destructor pairs as
-- stack manipulators allows them to be composed more easily.
--
-- Module @Data.Piso.Common@ contains @Piso@s for some common datatypes.
--
-- Modules @Data.Piso.Generic@ and @Data.Piso.TH@ offer generic ways of deriving @Piso@s for custom datatypes.

data Piso a b = Piso (a -> b) (b -> Maybe a)

instance Category Piso where
  id                          = Piso id Just
  ~(Piso f1 g1) . ~(Piso f2 g2) = Piso (f1 . f2) (g1 >=> g2)


-- | Apply an isomorphism in forward direction.
forward :: Piso a b -> a -> b
forward (Piso f _) = f

-- | Apply an isomorphism in backward direction.
backward :: Piso a b -> b -> Maybe a
backward (Piso _ g) = g


-- | A type class that expresses that a category is able to embed 'Piso' values.
class Category cat => FromPiso cat where
  fromPiso :: Piso a b -> cat a b

instance FromPiso Piso where
  fromPiso = id


-- | Heterogenous stack with a head and a tail. Or: an infix way to write @(,)@.
data h :- t = h :- t
  deriving (Eq, Show, Functor)
infixr 5 :-