{-# LANGUAGE RankNTypes #-}
module Data.MLens
    ( -- * Monadic lenses data type
      MLens (MLens)

    -- * Side-effect free lenses
    , Lens
    , fromLens, toLens

    -- * Lens construction
    , lens

    -- * Lens operations
    , getL, setL, modL

    -- * Lens transformations
    , (***)
    , mapMLens
    , joinML, joinLens

    -- * Pure lenses
    , unitLens
    , fstLens, sndLens
    , maybeLens
    , listLens
    , ithLens

    -- * Impure lenses
    , forkLens
    , justLens
    , showLens

    -- * Auxiliary definitions
    , Morph
    ) where

import Data.Monoid
import Control.Category
import qualified Control.Arrow as Arrow
import Control.Monad
import Control.Monad.Identity
import Data.Maybe
import Prelude hiding ((.), id)

{-|
Monadic lenses.

The following representations would be also good for @(MLens m a b)@:

 *  @a -> m (Store b (m a))@

 *  @forall f . Functor f => (b -> m (f (m b))) -> a -> m (f (m a))@

 *  @(a -> m b, b -> a -> m a)@

The last representation has no efficient composition operation
(the set operation on composition of n lenses use O(n * n) get operations with the last representation).

Using lenses which do not fulfil the lens laws are safe,
but one should take extra care when doing program transformations
or reasoning about code with impure lenses.

The following law is a minimum, but some lenses (which do logging) do not fulfil this:

 *  get-no-effect: @(getL k a >> return ())@ === @(return ())@

TODO: List laws, document which laws hold for each lenses.
-}
newtype MLens m a b
    = MLens (a -> m (b, b -> m a))

{-|
Side-effect free lenses.

The following representations would be also good for @(Lens a b)@:

 *  @forall m . Monad m => MLens m a b@

Laws for pure monadic lenses:

 *  set-get: @(setL l b a >>= getL l)@ === @(setL l b a >> return b)@

 *  get-set: @(getL l a >>= \b -> setL l b a)@  ===  @(return a)@

 *  set-set: @(setL l b a >>= setL l b')@ ===  @(setL l b' a)@

For example, @fstLens@ and @(fstLens . fstLens)@ fulfil these laws.
-}
type Lens a b
    = MLens Identity a b

fromLens :: Monad m => Lens a b -> MLens m a b
fromLens (MLens f) = MLens $ \x -> do
    let (a, b) = runIdentity $ f x
    return (a, \y -> return $ runIdentity $ b y)

toLens :: (forall m . Monad m => MLens m a b) -> Lens a b
toLens k = k

-- | Impure (but effect-free) lens constuctor
lens :: Monad m => (a -> b) -> (b -> a -> a) -> MLens m a b
lens get set = MLens $ \a -> return (get a, return . flip set a)

getL :: Monad m => MLens m a b -> a -> m b
getL (MLens f) a = f a >>= return . fst

setL :: Monad m => MLens m a b -> b -> a -> m a
setL (MLens f) b a = f a >>= ($ b) . snd

modL :: Monad m => MLens m b a -> (a -> a) -> b -> m b
modL (MLens g) f b = do
    (x, h) <- g b
    h (f x)

instance Monad m => Category (MLens m) where
    id = MLens $ \a -> return (a, return)
    MLens r1 . MLens r2 = MLens $ \a -> do
        (g2, s2) <- r2 a
        (g1, s1) <- r1 g2
        return (g1, s1 >=> s2)

-- | Tensor product
--
-- could be defined as
--
-- @instance Monad m => Tensor (MLens m)@
--
-- @Tensor@ is defined in "Control.Category.Product" in the data-lens package.
(***) :: Monad m => MLens m a b -> MLens m c d -> MLens m (a, c) (b, d)
MLens r1 *** MLens r2 = MLens $ \(a1, a2) -> do
        (g1, s1) <- r1 a1
        (g2, s2) <- r2 a2
        return
            ( (g1, g2)
            , uncurry (liftM2 (,)) . (s1 Arrow.*** s2)
            )

infixr 3 ***

mapMLens :: (Monad m, Monad n) => Morph m n -> MLens m a b -> MLens n a b
mapMLens f (MLens r) = MLens $ \a -> do
    (x, s) <- f (r a)
    return (x, f . s)

joinML :: Monad m => (a -> m (MLens m a b)) -> MLens m a b
joinML r = MLens $ \x -> do
    MLens q <- r x
    q x

-- | It would be possible to define a @Monad@ instance for @(MLens m a)@ too, but monad laws would not hold.
joinLens :: Monad m => MLens m a (MLens m a b) -> MLens m a b
joinLens = joinML . getL

unitLens :: Monad m => MLens m a ()
unitLens = lens (const ()) (const id)

fstLens :: Monad m => MLens m (a,b) a
fstLens = MLens $ \(a,b) -> return (a, \a' -> return (a', b))

sndLens :: Monad m => MLens m (a,b) b
sndLens = MLens $ \(a,b) -> return (b, \b' -> return (a, b'))

maybeLens :: Monad m => MLens m (Bool, a) (Maybe a)
maybeLens = lens (\(b,a) -> if b then Just a else Nothing)
              (\x (_,a) -> maybe (False, a) (\a' -> (True, a')) x)

listLens :: Monad m => MLens m (Bool, (a, [a])) [a]
listLens = lens get set where
    get (False, _) = []
    get (True, (l, r)) = l: r
    set [] (_, x) = (False, x)
    set (l: r) _ = (True, (l, r))

-- | @ithLens@ is pure only with proper preconditions.
ithLens :: Monad m => Int -> MLens m [a] a
ithLens i = lens (!!i) $ \x xs -> take i xs ++ x : drop (i+1) xs

forkLens :: (Monoid a, Monad m) => MLens m a (a, a)
forkLens = MLens $ \a ->
    return ((a, a), \(a1, a2) -> return $ a1 `mappend` a2)

justLens :: Monad m => a -> MLens m (Maybe a) a
justLens a = lens (maybe a id) (const . Just)

showLens :: (Monad m, Show a, Read a) => MLens m a String
showLens = lens show $ \s def -> maybe def fst $ listToMaybe $ reads s


type Morph m n = forall a . m a -> n a